Data yang hilang saat program ditutup tidak berguna untuk aplikasi nyata. Pada bagian ini kita mengimplementasikan persistensi: storage_simpan menulis daftar kontak ke file teks dengan format pipa-separated, dan storage_muat membaca kembali ke struktur ContactList. Format file yang kita pilih sederhana: baris pertama berisi jumlah kontak, lalu setiap baris berikutnya berisi satu kontak dengan field dipisahkan karakter |.
Konsep #
Pemilihan format file menentukan kemudahan debugging dan ketahanan terhadap data korup. Format pipa-separated (id|nama|telp|email) punya keuntungan dibanding CSV (comma-separated) karena karakter | jarang muncul di nama, telepon, atau email — tidak perlu escaping. JSON lebih fleksibel tetapi memerlukan parser yang lebih berat; untuk aplikasi kontak sederhana, format kita cukup.
Operasi tulis memakai fopen(path, "w") yang akan membuat file baru atau menimpa yang ada. Operasi baca memakai fopen(path, "r") yang gagal bila file tidak ada — kita tangani dengan kode STORAGE_ERR_BUKA_FILE. Setiap pemanggilan fopen wajib diimbangi fclose untuk membebaskan resource.
Parsing memakai sscanf dengan format %d|%49[^|]|%19[^|]|%59[^\n]. Specifier %[^\|] membaca karakter sampai bertemu |. Angka sebelum [ (seperti 49) membatasi panjang agar tidak overflow buffer. Specifier terakhir %[^\n] membaca sisa baris (email tidak mengandung |).
Implementasi #
Fungsi storage_simpan:
StorageStatus storage_simpan(const ContactList *list, const char *path) {
if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
FILE *f = fopen(path, "w");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
fprintf(f, "%d\n", list->jumlah);
for (int i = 0; i < list->jumlah; i++) {
const Contact *c = &list->data[i];
fprintf(f, "%d|%s|%s|%s\n",
c->id, c->nama, c->telp, c->email);
}
if (fclose(f) != 0) return STORAGE_ERR_TULIS;
return STORAGE_OK;
}Fungsi storage_muat (versi ringkas, lihat kode lengkap untuk versi penuh dengan validasi):
StorageStatus storage_muat(ContactList *list, const char *path) {
FILE *f = fopen(path, "r");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
int jumlah = 0;
if (fscanf(f, "%d\n", &jumlah) != 1) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
/* ... iterasi baca tiap baris dengan sscanf ... */
fclose(f);
return STORAGE_OK;
}Kode Lengkap #
Berkas storage.h:
#ifndef STORAGE_H
#define STORAGE_H
#include "contact.h"
typedef enum {
STORAGE_OK = 0,
STORAGE_ERR_BUKA_FILE = 1,
STORAGE_ERR_TULIS = 2,
STORAGE_ERR_FORMAT = 3
} StorageStatus;
StorageStatus storage_simpan(const ContactList *list, const char *path);
StorageStatus storage_muat(ContactList *list, const char *path);
#endif /* STORAGE_H */Berkas storage.c lengkap:
#include "storage.h"
#include <stdio.h>
#include <string.h>
static void potong_newline(char *s) {
size_t n = strlen(s);
while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) {
s[--n] = '\0';
}
}
StorageStatus storage_simpan(const ContactList *list, const char *path) {
if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
FILE *f = fopen(path, "w");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
fprintf(f, "%d\n", list->jumlah);
for (int i = 0; i < list->jumlah; i++) {
const Contact *c = &list->data[i];
fprintf(f, "%d|%s|%s|%s\n", c->id, c->nama, c->telp, c->email);
}
if (fclose(f) != 0) return STORAGE_ERR_TULIS;
return STORAGE_OK;
}
StorageStatus storage_muat(ContactList *list, const char *path) {
if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
FILE *f = fopen(path, "r");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
int jumlah = 0;
if (fscanf(f, "%d\n", &jumlah) != 1) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
if (jumlah < 0 || jumlah > CONTACT_MAKS) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
char baris[256];
int id_terbesar = list->id_berikutnya - 1;
for (int i = 0; i < jumlah; i++) {
if (fgets(baris, sizeof(baris), f) == 0) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
potong_newline(baris);
int id;
char nama[CONTACT_NAMA_MAKS];
char telp[CONTACT_TELP_MAKS];
char email[CONTACT_EMAIL_MAKS];
int parsed = sscanf(baris, "%d|%49[^|]|%19[^|]|%59[^\n]",
&id, nama, telp, email);
if (parsed < 4) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
if (list->jumlah >= CONTACT_MAKS) break;
Contact *c = &list->data[list->jumlah];
c->id = id;
strncpy(c->nama, nama, CONTACT_NAMA_MAKS - 1);
c->nama[CONTACT_NAMA_MAKS - 1] = '\0';
strncpy(c->telp, telp, CONTACT_TELP_MAKS - 1);
c->telp[CONTACT_TELP_MAKS - 1] = '\0';
strncpy(c->email, email, CONTACT_EMAIL_MAKS - 1);
c->email[CONTACT_EMAIL_MAKS - 1] = '\0';
list->jumlah++;
if (id > id_terbesar) id_terbesar = id;
}
list->id_berikutnya = id_terbesar + 1;
fclose(f);
return STORAGE_OK;
}Pengujian #
Uji round-trip simpan-muat:
cat > /tmp/p3b5_demo.c << 'EOF'
#include "contact.h"
#include "storage.h"
#include <stdio.h>
int main(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi", "0811", "[email protected]");
contact_tambah(&list, "Siti", "0812", "[email protected]");
StorageStatus ss = storage_simpan(&list, "/tmp/p3b5_kontak.txt");
printf("Simpan status = %d\n", ss);
ContactList dimuat;
contact_list_init(&dimuat);
ss = storage_muat(&dimuat, "/tmp/p3b5_kontak.txt");
printf("Muat status = %d, jumlah dimuat = %d, id_berikutnya = %d\n",
ss, dimuat.jumlah, dimuat.id_berikutnya);
contact_tampilkan(&dimuat, stdout);
return 0;
}
EOF
gcc -std=c11 -Wall -Wextra -I/home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/ -o /tmp/p3b5_demo /tmp/p3b5_demo.c /home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/contact.c /home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/storage.c && /tmp/p3b5_demo && echo "--- ISI FILE ---" && cat /tmp/p3b5_kontak.txtOutput yang Dihasilkan:
Simpan status = 0
Muat status = 0, jumlah dimuat = 2, id_berikutnya = 3
ID Nama Telp Email
1 Budi 0811 [email protected]
2 Siti 0812 [email protected]
Total: 2 kontak
--- ISI FILE ---
2
1|Budi|0811|[email protected]
2|Siti|0812|[email protected]Mari kita telaah baris-baris kunci pada storage_muat:
- Baris
if (fopen(path, "r") == 0): cek file bisa dibuka. Bila file tidak ada,fopenmemulangkanNULL. Kita pulangkan kode error yang berbeda dari error format agarmain.cdapat memberi pesan yang sesuai (“file tidak ada” vs “file korup”). - Baris
if (fscanf(f, "%d\n", &jumlah) != 1):fscanfmengembalikan jumlah field yang berhasil di-parse. Bila bukan 1, file rusak.\ndi format string menyisakan newline di stream sehinggafgetsberikutnya mulai dari baris kontak pertama. - Baris
if (jumlah < 0 || jumlah > CONTACT_MAKS): validasi rentang untuk mencegah alokasi berlebih atau loop negatif. File yang menyatakan jumlah999999akan ditolak. - Baris
int parsed = sscanf(baris, "%d|%49[^|]|%19[^|]|%59[^\n]", ...): specifier%49[^|]membaca maksimal 49 karakter hingga|. Batas panjang ini wajib untuk mencegah overflow buffernama. - Baris
if (parsed < 4): bila hanya 3 field terparse (mis. email hilang), file korup. Kita berhenti dan laporkan error. - Baris
if (id > id_terbesar) id_terbesar = id;: lacak ID maksimum saat memuat. Setelah loop,id_berikutnya = id_terbesar + 1agar ID kontak baru tidak bentrok dengan ID yang sudah ada di file.
Bagian 6 akan menyusun test runner dan Makefile akhir.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contact.c
Unduh berkas/* contact.c -- implementasi model dan operasi kontak. */
#include "contact.h"
#include <string.h>
void contact_list_init(ContactList *list) {
if (list == 0) return;
list->jumlah = 0;
list->id_berikutnya = 1;
}
int contact_valid_nama(const char *nama) {
if (nama == 0 || nama[0] == '\0') return 0;
/* Cek panjang maksimum. */
if (strlen(nama) >= CONTACT_NAMA_MAKS) return 0;
return 1;
}
ContactStatus contact_tambah(ContactList *list,
const char *nama,
const char *telp,
const char *email) {
if (list == 0) return CONTACT_ERR_INPUT_INVALID;
if (!contact_valid_nama(nama)) return CONTACT_ERR_INPUT_INVALID;
if (list->jumlah >= CONTACT_MAKS) return CONTACT_ERR_PENUH;
Contact *c = &list->data[list->jumlah];
c->id = list->id_berikutnya++;
strncpy(c->nama, nama, CONTACT_NAMA_MAKS - 1);
c->nama[CONTACT_NAMA_MAKS - 1] = '\0';
strncpy(c->telp, telp ? telp : "", CONTACT_TELP_MAKS - 1);
c->telp[CONTACT_TELP_MAKS - 1] = '\0';
strncpy(c->email, email ? email : "", CONTACT_EMAIL_MAKS - 1);
c->email[CONTACT_EMAIL_MAKS - 1] = '\0';
list->jumlah++;
return CONTACT_OK;
}
void contact_tampilkan(const ContactList *list, FILE *out) {
if (list == 0 || out == 0) return;
fprintf(out, "%-4s %-20s %-15s %-25s\n",
"ID", "Nama", "Telp", "Email");
for (int i = 0; i < list->jumlah; i++) {
const Contact *c = &list->data[i];
fprintf(out, "%-4d %-20s %-15s %-25s\n",
c->id, c->nama, c->telp, c->email);
}
fprintf(out, "Total: %d kontak\n", list->jumlah);
}
int contact_cari(const ContactList *list, const char *nama) {
if (list == 0 || nama == 0) return -1;
for (int i = 0; i < list->jumlah; i++) {
if (strcmp(list->data[i].nama, nama) == 0) {
return i;
}
}
return -1;
}
ContactStatus contact_ubah(ContactList *list, int id,
const char *nama_baru,
const char *telp_baru,
const char *email_baru) {
if (list == 0) return CONTACT_ERR_INPUT_INVALID;
for (int i = 0; i < list->jumlah; i++) {
if (list->data[i].id == id) {
if (nama_baru && !contact_valid_nama(nama_baru)) {
return CONTACT_ERR_INPUT_INVALID;
}
if (nama_baru) {
strncpy(list->data[i].nama, nama_baru, CONTACT_NAMA_MAKS - 1);
list->data[i].nama[CONTACT_NAMA_MAKS - 1] = '\0';
}
if (telp_baru) {
strncpy(list->data[i].telp, telp_baru, CONTACT_TELP_MAKS - 1);
list->data[i].telp[CONTACT_TELP_MAKS - 1] = '\0';
}
if (email_baru) {
strncpy(list->data[i].email, email_baru, CONTACT_EMAIL_MAKS - 1);
list->data[i].email[CONTACT_EMAIL_MAKS - 1] = '\0';
}
return CONTACT_OK;
}
}
return CONTACT_ERR_TIDAK_DITEMUKAN;
}
ContactStatus contact_hapus(ContactList *list, int id) {
if (list == 0) return CONTACT_ERR_INPUT_INVALID;
for (int i = 0; i < list->jumlah; i++) {
if (list->data[i].id == id) {
/* Geser elemen setelahnya ke depan. */
for (int j = i; j < list->jumlah - 1; j++) {
list->data[j] = list->data[j + 1];
}
list->jumlah--;
return CONTACT_OK;
}
}
return CONTACT_ERR_TIDAK_DITEMUKAN;
}
main.c
Unduh berkas/* main.c -- titik masuk aplikasi manajemen kontak. */
#include "contact.h"
#include "storage.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DB_PATH "kontak.txt"
/* Membaca satu baris dari stdin, memotong newline. */
static void baca_baris(const char *prompt, char *buf, size_t ukuran) {
printf("%s", prompt);
if (fgets(buf, (int)ukuran, stdin) == 0) {
buf[0] = '\0';
return;
}
size_t n = strlen(buf);
while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) {
buf[--n] = '\0';
}
}
static int baca_int(const char *prompt, int *out) {
char buf[32];
baca_baris(prompt, buf, sizeof(buf));
char *sisa = 0;
long v = strtol(buf, &sisa, 10);
if (sisa == buf) return 0;
*out = (int)v;
return 1;
}
static void menu_tambah(ContactList *list) {
char nama[CONTACT_NAMA_MAKS];
char telp[CONTACT_TELP_MAKS];
char email[CONTACT_EMAIL_MAKS];
baca_baris("Nama : ", nama, sizeof(nama));
baca_baris("Telp : ", telp, sizeof(telp));
baca_baris("Email : ", email, sizeof(email));
ContactStatus s = contact_tambah(list, nama, telp, email);
switch (s) {
case CONTACT_OK:
printf("Kontak ditambahkan (id=%d).\n", list->data[list->jumlah - 1].id);
break;
case CONTACT_ERR_PENUH:
printf("Daftar penuh.\n"); break;
case CONTACT_ERR_INPUT_INVALID:
printf("Input tidak valid (nama wajib diisi).\n"); break;
default:
printf("Error tidak dikenal.\n"); break;
}
}
static void menu_ubah(ContactList *list) {
int id;
if (!baca_int("ID kontak yang diubah: ", &id)) {
printf("ID tidak valid.\n"); return;
}
char nama[CONTACT_NAMA_MAKS];
char telp[CONTACT_TELP_MAKS];
char email[CONTACT_EMAIL_MAKS];
baca_baris("Nama baru (kosongkan jika tidak diubah) : ", nama, sizeof(nama));
baca_baris("Telp baru (kosongkan jika tidak diubah) : ", telp, sizeof(telp));
baca_baris("Email baru (kosongkan jika tidak diubah): ", email, sizeof(email));
ContactStatus s = contact_ubah(list, id,
nama[0] ? nama : 0,
telp[0] ? telp : 0,
email[0] ? email : 0);
if (s == CONTACT_OK) printf("Kontak diubah.\n");
else if (s == CONTACT_ERR_TIDAK_DITEMUKAN) printf("Kontak tidak ditemukan.\n");
else printf("Input tidak valid.\n");
}
static void menu_hapus(ContactList *list) {
int id;
if (!baca_int("ID kontak yang dihapus: ", &id)) {
printf("ID tidak valid.\n"); return;
}
ContactStatus s = contact_hapus(list, id);
if (s == CONTACT_OK) printf("Kontak dihapus.\n");
else if (s == CONTACT_ERR_TIDAK_DITEMUKAN) printf("Kontak tidak ditemukan.\n");
}
static void menu_cari(const ContactList *list) {
char nama[CONTACT_NAMA_MAKS];
baca_baris("Nama yang dicari: ", nama, sizeof(nama));
int idx = contact_cari(list, nama);
if (idx < 0) {
printf("Kontak dengan nama '%s' tidak ditemukan.\n", nama);
} else {
const Contact *c = &list->data[idx];
printf("Ditemukan: [%d] %s | %s | %s\n",
c->id, c->nama, c->telp, c->email);
}
}
int main(void) {
ContactList list;
contact_list_init(&list);
StorageStatus sm = storage_muat(&list, DB_PATH);
if (sm == STORAGE_OK) {
printf("Memuat %d kontak dari %s.\n", list.jumlah, DB_PATH);
} else if (sm == STORAGE_ERR_BUKA_FILE) {
printf("Belum ada file %s. Memulai dengan daftar kosong.\n", DB_PATH);
} else {
printf("Peringatan: format file rusak (kode %d).\n", sm);
}
int lanjut = 1;
while (lanjut) {
printf("\n=== Manajemen Kontak ===\n");
printf("1. Tambah\n2. Tampilkan\n3. Cari\n4. Ubah\n5. Hapus\n6. Keluar\n");
int pilihan = 0;
if (!baca_int("Pilih: ", &pilihan)) { printf("Pilihan tidak valid.\n"); continue; }
switch (pilihan) {
case 1: menu_tambah(&list); break;
case 2: contact_tampilkan(&list, stdout); break;
case 3: menu_cari(&list); break;
case 4: menu_ubah(&list); break;
case 5: menu_hapus(&list); break;
case 6: lanjut = 0; break;
default: printf("Pilihan tidak dikenal.\n"); break;
}
}
StorageStatus ss = storage_simpan(&list, DB_PATH);
if (ss == STORAGE_OK) {
printf("Menyimpan %d kontak ke %s.\n", list.jumlah, DB_PATH);
} else {
printf("Gagal menyimpan (kode %d).\n", ss);
}
printf("Terima kasih.\n");
return 0;
}
storage.c
Unduh berkas/* storage.c -- persistensi kontak ke file teks. */
#include "storage.h"
#include <stdio.h>
#include <string.h>
/* Memotong newline di akhir string (kalau ada). */
static void potong_newline(char *s) {
size_t n = strlen(s);
while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) {
s[--n] = '\0';
}
}
StorageStatus storage_simpan(const ContactList *list, const char *path) {
if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
FILE *f = fopen(path, "w");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
fprintf(f, "%d\n", list->jumlah);
for (int i = 0; i < list->jumlah; i++) {
const Contact *c = &list->data[i];
fprintf(f, "%d|%s|%s|%s\n",
c->id, c->nama, c->telp, c->email);
}
if (fclose(f) != 0) return STORAGE_ERR_TULIS;
return STORAGE_OK;
}
StorageStatus storage_muat(ContactList *list, const char *path) {
if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
FILE *f = fopen(path, "r");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
int jumlah = 0;
if (fscanf(f, "%d\n", &jumlah) != 1) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
if (jumlah < 0 || jumlah > CONTACT_MAKS) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
char baris[256];
int id_terbesar = list->id_berikutnya - 1;
for (int i = 0; i < jumlah; i++) {
if (fgets(baris, sizeof(baris), f) == 0) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
potong_newline(baris);
int id;
char nama[CONTACT_NAMA_MAKS];
char telp[CONTACT_TELP_MAKS];
char email[CONTACT_EMAIL_MAKS];
/* sscanf dengan %[^\|] untuk membaca sampai '|'. */
int parsed = sscanf(baris, "%d|%49[^|]|%19[^|]|%59[^\n]",
&id, nama, telp, email);
if (parsed < 4) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
if (list->jumlah >= CONTACT_MAKS) break;
Contact *c = &list->data[list->jumlah];
c->id = id;
strncpy(c->nama, nama, CONTACT_NAMA_MAKS - 1);
c->nama[CONTACT_NAMA_MAKS - 1] = '\0';
strncpy(c->telp, telp, CONTACT_TELP_MAKS - 1);
c->telp[CONTACT_TELP_MAKS - 1] = '\0';
strncpy(c->email, email, CONTACT_EMAIL_MAKS - 1);
c->email[CONTACT_EMAIL_MAKS - 1] = '\0';
list->jumlah++;
if (id > id_terbesar) id_terbesar = id;
}
list->id_berikutnya = id_terbesar + 1;
fclose(f);
return STORAGE_OK;
}
tests.c
Unduh berkas/* tests.c -- rangkaian test untuk modul contact dan storage. */
#include "contact.h"
#include "storage.h"
#include <stdio.h>
#include <string.h>
static int lulus = 0;
static int gagal = 0;
#define PERIKSA(kondisi, nama) \
do { \
if (kondisi) { lulus++; printf("[LULUS] %s\n", nama); } \
else { gagal++; printf("[GAGAL] %s\n", nama); } \
} while (0)
static void test_tambah_dan_tampilkan(void) {
ContactList list;
contact_list_init(&list);
ContactStatus s1 = contact_tambah(&list, "Budi", "0811", "[email protected]");
ContactStatus s2 = contact_tambah(&list, "Siti", "0812", "[email protected]");
PERIKSA(s1 == CONTACT_OK && s2 == CONTACT_OK, "Tambah 2 kontak berhasil");
PERIKSA(list.jumlah == 2, "Jumlah = 2 setelah tambah");
PERIKSA(list.data[0].id == 1 && list.data[1].id == 2,
"ID auto-increment 1, 2");
}
static void test_tambah_nama_kosong(void) {
ContactList list;
contact_list_init(&list);
ContactStatus s = contact_tambah(&list, "", "0811", "[email protected]");
PERIKSA(s == CONTACT_ERR_INPUT_INVALID, "Nama kosong ditolak");
PERIKSA(list.jumlah == 0, "Tidak ada kontak ditambahkan");
}
static void test_cari(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi", "0811", "[email protected]");
contact_tambah(&list, "Siti", "0812", "[email protected]");
int idx = contact_cari(&list, "Siti");
PERIKSA(idx == 1, "Cari 'Siti' -> indeks 1");
PERIKSA(contact_cari(&list, "Tidak Ada") == -1, "Cari nama tidak ada -> -1");
}
static void test_ubah(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi", "0811", "[email protected]");
ContactStatus s = contact_ubah(&list, 1, "Budi Santoso", 0, 0);
PERIKSA(s == CONTACT_OK, "Ubah kontak id=1 berhasil");
PERIKSA(strcmp(list.data[0].nama, "Budi Santoso") == 0,
"Nama berubah menjadi 'Budi Santoso'");
PERIKSA(strcmp(list.data[0].telp, "0811") == 0,
"Telp tetap '0811' (tidak diubah)");
}
static void test_hapus(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi", "0811", "[email protected]");
contact_tambah(&list, "Siti", "0812", "[email protected]");
contact_tambah(&list, "Andi", "0813", "[email protected]");
ContactStatus s = contact_hapus(&list, 2);
PERIKSA(s == CONTACT_OK, "Hapus id=2 berhasil");
PERIKSA(list.jumlah == 2, "Jumlah = 2 setelah hapus");
PERIKSA(strcmp(list.data[1].nama, "Andi") == 0,
"Setelah hapus, elemen 1 = 'Andi'");
PERIKSA(contact_hapus(&list, 999) == CONTACT_ERR_TIDAK_DITEMUKAN,
"Hapus id tidak ada -> error");
}
static void test_penyimpanan(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi", "0811", "[email protected]");
contact_tambah(&list, "Siti", "0812", "[email protected]");
StorageStatus ss = storage_simpan(&list, "/tmp/test_kontak.txt");
PERIKSA(ss == STORAGE_OK, "Simpan ke /tmp/test_kontak.txt");
ContactList dimuat;
contact_list_init(&dimuat);
ss = storage_muat(&dimuat, "/tmp/test_kontak.txt");
PERIKSA(ss == STORAGE_OK, "Muat dari /tmp/test_kontak.txt");
PERIKSA(dimuat.jumlah == 2, "Jumlah dimuat = 2");
PERIKSA(strcmp(dimuat.data[0].nama, "Budi") == 0,
"Kontak pertama dimuat: 'Budi'");
PERIKSA(dimuat.id_berikutnya == 3,
"id_berikutnya = 3 setelah muat");
}
int main(void) {
test_tambah_dan_tampilkan();
test_tambah_nama_kosong();
test_cari();
test_ubah();
test_hapus();
test_penyimpanan();
printf("\nRingkasan: %d lulus, %d gagal\n", lulus, gagal);
return gagal == 0 ? 0 : 1;
}