pcloudcc-lneely/tests/unit-tests/test_plocalscan_helpers.c

515 lines
18 KiB
C

/*
* Test: plocalscan_helpers.c — sorted-list merge algorithm
*
* Covers:
* 1. plocalscan_folderlist_cmp: name ordering
* 2. plocalscan_compare_sizeinodemtime: field ordering
* 3. plocalscan_compare_inode: inode ordering
* 4. plocalscan_merge_folder_lists:
* - new entries (disk only) → NEWFILES / NEWFOLDERS
* - deleted entries (db only) → DELFILES / DELFOLDERS
* - modified file (same name, different metadata) → MODFILES
* - unchanged file (same name, same metadata) → nothing added
* - mixed: new + deleted + modified in one call
*
* External deps wrapped via --wrap:
* psync_is_name_to_ignore → always 0 (don't ignore any name)
* psync_send_backup_del_event → no-op
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include "plocalscan_helpers.h"
/* ------------------------------------------------------------------ */
static int passes = 0, failures = 0;
#define PASS(n) do { printf("PASS: %s\n", n); passes++; } while (0)
#define FAIL(n, ...) do { printf("FAIL: %s — ", n); \
printf(__VA_ARGS__); printf("\n"); failures++; } while (0)
/* ------------------------------------------------------------------ */
/* Wrap stubs */
/* ------------------------------------------------------------------ */
int __wrap_psync_is_name_to_ignore(const char *name) {
(void)name;
return 0; /* never ignore */
}
void __wrap_psync_send_backup_del_event(psync_fileorfolderid_t id) {
(void)id;
}
/* ------------------------------------------------------------------ */
/* Helper: allocate a sync_folderlist entry */
/* ------------------------------------------------------------------ */
static sync_folderlist *make_entry(const char *name, uint8_t isfolder,
uint64_t inode, uint64_t size,
uint64_t mtimenat, uint64_t deviceid) {
size_t namelen = strlen(name) + 1;
size_t sz = offsetof(sync_folderlist, name) + namelen;
sync_folderlist *e = (sync_folderlist *)malloc(sz);
memset(e, 0, sz);
e->isfolder = isfolder;
e->inode = inode;
e->size = size;
e->mtimenat = mtimenat;
e->deviceid = deviceid;
psync_list_init(&e->list);
memcpy(e->name, name, namelen);
return e;
}
/* Append entry to a list head */
static void list_append(psync_list *head, sync_folderlist *e) {
psync_list_add_tail(head, &e->list);
}
/* Count elements in a psync_list */
static int list_count(psync_list *head) {
int n = 0;
psync_list *cur;
psync_list_for_each(cur, head) n++;
return n;
}
/* Get the nth element name from an output list */
static const char *list_nth_name(psync_list *head, int n) {
psync_list *cur;
int i = 0;
psync_list_for_each(cur, head) {
if (i++ == n)
return psync_list_element(cur, sync_folderlist, list)->name;
}
return NULL;
}
/* Free all elements in a list (allocated via plocalscan_copy_element → pmem_malloc).
* Must use pmem_free, not free(), because pmem_malloc prepends a header. */
static void free_list(psync_list *head) {
psync_list *cur, *tmp;
psync_list_for_each_safe(cur, tmp, head)
pmem_free(PMEM_SUBSYS_SYNC,
psync_list_element(cur, sync_folderlist, list));
psync_list_init(head);
}
/* Initialise PLOCALSCAN_SCAN_LIST_CNT output list heads */
static void init_out(psync_list out[]) {
int i;
for (i = 0; i < PLOCALSCAN_SCAN_LIST_CNT; i++)
psync_list_init(&out[i]);
}
static void free_out(psync_list out[]) {
int i;
for (i = 0; i < PLOCALSCAN_SCAN_LIST_CNT; i++)
free_list(&out[i]);
}
/* ------------------------------------------------------------------ */
/* Test 1: comparators */
/* ------------------------------------------------------------------ */
static void test_comparators(void) {
sync_folderlist *a, *b;
/* folderlist_cmp: strcmp on name */
a = make_entry("alpha", 0, 1, 100, 1000, 0);
b = make_entry("beta", 0, 2, 200, 2000, 0);
if (plocalscan_folderlist_cmp(&a->list, &b->list) >= 0)
FAIL("folderlist_cmp: alpha < beta", "expected negative, got non-negative");
else
PASS("folderlist_cmp: alpha < beta → negative");
if (plocalscan_folderlist_cmp(&b->list, &a->list) <= 0)
FAIL("folderlist_cmp: beta > alpha", "expected positive, got non-positive");
else
PASS("folderlist_cmp: beta > alpha → positive");
if (plocalscan_folderlist_cmp(&a->list, &a->list) != 0)
FAIL("folderlist_cmp: alpha == alpha", "expected 0");
else
PASS("folderlist_cmp: alpha == alpha → 0");
free(a); free(b);
/* compare_sizeinodemtime */
{
sync_folderlist *s1, *s2;
s1 = make_entry("f", 0, 10, 100, 1000, 0);
s2 = make_entry("f", 0, 20, 200, 2000, 0);
/* size differs: s1.size < s2.size */
if (plocalscan_compare_sizeinodemtime(&s1->list, &s2->list) >= 0)
FAIL("compare_sim: size s1 < s2", "expected negative");
else
PASS("compare_sim: size s1(100) < s2(200) → negative");
if (plocalscan_compare_sizeinodemtime(&s2->list, &s1->list) <= 0)
FAIL("compare_sim: size s2 > s1", "expected positive");
else
PASS("compare_sim: size s2(200) > s1(100) → positive");
/* same size, compare inode */
s2->size = s1->size;
if (plocalscan_compare_sizeinodemtime(&s1->list, &s2->list) >= 0)
FAIL("compare_sim: inode s1(10) < s2(20)", "expected negative");
else
PASS("compare_sim: inode s1(10) < s2(20) → negative");
/* same size + inode, compare mtime */
s2->inode = s1->inode;
if (plocalscan_compare_sizeinodemtime(&s1->list, &s2->list) >= 0)
FAIL("compare_sim: mtime s1(1000) < s2(2000)", "expected negative");
else
PASS("compare_sim: mtime s1(1000) < s2(2000) → negative");
/* all equal */
s2->mtimenat = s1->mtimenat;
if (plocalscan_compare_sizeinodemtime(&s1->list, &s2->list) != 0)
FAIL("compare_sim: all equal", "expected 0");
else
PASS("compare_sim: all fields equal → 0");
free(s1); free(s2);
}
/* compare_inode */
{
sync_folderlist *i1, *i2;
i1 = make_entry("d", 1, 5, 0, 0, 0);
i2 = make_entry("d", 1, 15, 0, 0, 0);
if (plocalscan_compare_inode(&i1->list, &i2->list) >= 0)
FAIL("compare_inode: 5 < 15", "expected negative");
else
PASS("compare_inode: inode 5 < 15 → negative");
if (plocalscan_compare_inode(&i2->list, &i1->list) <= 0)
FAIL("compare_inode: 15 > 5", "expected positive");
else
PASS("compare_inode: inode 15 > 5 → positive");
i2->inode = i1->inode;
if (plocalscan_compare_inode(&i1->list, &i2->list) != 0)
FAIL("compare_inode: equal", "expected 0");
else
PASS("compare_inode: equal inodes → 0");
free(i1); free(i2);
}
}
/* ------------------------------------------------------------------ */
/* Test 2: new entries (disk has entries DB doesn't) */
/* ------------------------------------------------------------------ */
static void test_merge_new_entries(void) {
psync_list disklist, dblist;
psync_list out[PLOCALSCAN_SCAN_LIST_CNT];
size_t added;
psync_list_init(&disklist);
psync_list_init(&dblist);
init_out(out);
/* Two new files on disk, not in DB */
list_append(&disklist, make_entry("file_a.txt", 0, 1, 100, 1000, 1));
list_append(&disklist, make_entry("file_b.txt", 0, 2, 200, 2000, 1));
added = plocalscan_merge_folder_lists(&disklist, &dblist, out,
10, 20, 1, 1, 1);
if (added != 2)
FAIL("new files: added count", "expected 2, got %zu", added);
else
PASS("new files: 2 elements added");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFILES]) != 2)
FAIL("new files: NEWFILES count",
"expected 2, got %d",
list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFILES]));
else
PASS("new files: both in NEWFILES list");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_DELFILES]) != 0)
FAIL("new files: DELFILES should be empty",
"got %d", list_count(&out[PLOCALSCAN_SCAN_LIST_DELFILES]));
else
PASS("new files: DELFILES empty");
free_out(out);
/* disklist entries were NOT copied (only their copies are in out[]) */
psync_list_for_each_element_call(&disklist, sync_folderlist, list, free);
}
/* ------------------------------------------------------------------ */
/* Test 3: new folder entry */
/* ------------------------------------------------------------------ */
static void test_merge_new_folder(void) {
psync_list disklist, dblist;
psync_list out[PLOCALSCAN_SCAN_LIST_CNT];
size_t added;
psync_list_init(&disklist);
psync_list_init(&dblist);
init_out(out);
/* New folder: deviceid must match for folders to be classified as new */
list_append(&disklist, make_entry("newdir", 1, 5, 0, 0, 42));
added = plocalscan_merge_folder_lists(&disklist, &dblist, out,
10, 20, 1, 1, 42 /* same deviceid */);
if (added != 1)
FAIL("new folder: added count", "expected 1, got %zu", added);
else
PASS("new folder: 1 element added");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFOLDERS]) != 1)
FAIL("new folder: NEWFOLDERS count",
"expected 1, got %d",
list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFOLDERS]));
else
PASS("new folder: in NEWFOLDERS list");
free_out(out);
psync_list_for_each_element_call(&disklist, sync_folderlist, list, free);
}
/* ------------------------------------------------------------------ */
/* Test 4: deleted entries (DB has entries disk doesn't) */
/* ------------------------------------------------------------------ */
static void test_merge_deleted_entries(void) {
psync_list disklist, dblist;
psync_list out[PLOCALSCAN_SCAN_LIST_CNT];
size_t added;
psync_list_init(&disklist);
psync_list_init(&dblist);
init_out(out);
/* One deleted file and one deleted folder in DB */
list_append(&dblist, make_entry("gone_file.txt", 0, 3, 300, 3000, 1));
list_append(&dblist, make_entry("gone_dir", 1, 4, 0, 0, 1));
added = plocalscan_merge_folder_lists(&disklist, &dblist, out,
10, 20, 1, 1, 1);
if (added != 2)
FAIL("deleted entries: added count", "expected 2, got %zu", added);
else
PASS("deleted entries: 2 elements added");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_DELFILES]) != 1)
FAIL("deleted entries: DELFILES count",
"expected 1, got %d",
list_count(&out[PLOCALSCAN_SCAN_LIST_DELFILES]));
else
PASS("deleted entries: 1 in DELFILES");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_DELFOLDERS]) != 1)
FAIL("deleted entries: DELFOLDERS count",
"expected 1, got %d",
list_count(&out[PLOCALSCAN_SCAN_LIST_DELFOLDERS]));
else
PASS("deleted entries: 1 in DELFOLDERS");
free_out(out);
psync_list_for_each_element_call(&dblist, sync_folderlist, list, free);
}
/* ------------------------------------------------------------------ */
/* Test 5: modified file */
/* ------------------------------------------------------------------ */
static void test_merge_modified_file(void) {
psync_list disklist, dblist;
psync_list out[PLOCALSCAN_SCAN_LIST_CNT];
size_t added;
sync_folderlist *disk_e, *db_e;
psync_list_init(&disklist);
psync_list_init(&dblist);
init_out(out);
/* Same name "data.bin" but different mtime → modified */
disk_e = make_entry("data.bin", 0, 7, 512, 9999, 1);
db_e = make_entry("data.bin", 0, 7, 512, 8888, 1);
list_append(&disklist, disk_e);
list_append(&dblist, db_e);
added = plocalscan_merge_folder_lists(&disklist, &dblist, out,
10, 20, 1, 1, 1);
if (added != 1)
FAIL("modified file: added count", "expected 1, got %zu", added);
else
PASS("modified file: 1 element added");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_MODFILES]) != 1)
FAIL("modified file: MODFILES count",
"expected 1, got %d",
list_count(&out[PLOCALSCAN_SCAN_LIST_MODFILES]));
else
PASS("modified file: in MODFILES list");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFILES]) != 0 ||
list_count(&out[PLOCALSCAN_SCAN_LIST_DELFILES]) != 0)
FAIL("modified file: no spurious new/del entries",
"NEWFILES=%d DELFILES=%d",
list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFILES]),
list_count(&out[PLOCALSCAN_SCAN_LIST_DELFILES]));
else
PASS("modified file: no spurious NEWFILES/DELFILES");
free_out(out);
free(disk_e);
free(db_e);
}
/* ------------------------------------------------------------------ */
/* Test 6: unchanged file → nothing added */
/* ------------------------------------------------------------------ */
static void test_merge_unchanged_file(void) {
psync_list disklist, dblist;
psync_list out[PLOCALSCAN_SCAN_LIST_CNT];
size_t added;
sync_folderlist *disk_e, *db_e;
psync_list_init(&disklist);
psync_list_init(&dblist);
init_out(out);
/* Same name, same inode/size/mtime → no change */
disk_e = make_entry("stable.txt", 0, 42, 1024, 5000, 1);
db_e = make_entry("stable.txt", 0, 42, 1024, 5000, 1);
list_append(&disklist, disk_e);
list_append(&dblist, db_e);
added = plocalscan_merge_folder_lists(&disklist, &dblist, out,
10, 20, 1, 1, 1);
if (added != 0)
FAIL("unchanged file: added count", "expected 0, got %zu", added);
else
PASS("unchanged file: 0 elements added");
{
int i, total = 0;
for (i = 0; i < PLOCALSCAN_SCAN_LIST_CNT; i++)
total += list_count(&out[i]);
if (total != 0)
FAIL("unchanged file: all out[] lists empty",
"total=%d", total);
else
PASS("unchanged file: all output lists remain empty");
}
free_out(out);
free(disk_e);
free(db_e);
}
/* ------------------------------------------------------------------ */
/* Test 7: mixed scenario */
/* ------------------------------------------------------------------ */
static void test_merge_mixed(void) {
psync_list disklist, dblist;
psync_list out[PLOCALSCAN_SCAN_LIST_CNT];
size_t added;
psync_list_init(&disklist);
psync_list_init(&dblist);
init_out(out);
/*
* disk: "aaa" (new file), "bbb" (modified), "ccc" (unchanged folder)
* db: "bbb" (old version), "ccc" (folder, same), "ddd" (deleted file)
*
* Expected:
* NEWFILES: "aaa"
* MODFILES: "bbb"
* DELFILES: "ddd"
* everything else empty
*/
list_append(&disklist, make_entry("aaa", 0, 1, 100, 1000, 1)); /* new */
list_append(&disklist, make_entry("bbb", 0, 2, 200, 2000, 1)); /* modified */
list_append(&disklist, make_entry("ccc", 1, 3, 0, 0, 1)); /* unchanged dir */
list_append(&dblist, make_entry("bbb", 0, 2, 200, 1111, 1)); /* old mtime */
list_append(&dblist, make_entry("ccc", 1, 3, 0, 0, 1)); /* same dir */
list_append(&dblist, make_entry("ddd", 0, 4, 400, 4000, 1)); /* deleted */
/* Lists are pre-sorted by name (alphabetical) */
added = plocalscan_merge_folder_lists(&disklist, &dblist, out,
10, 20, 1, 1, 1);
if (added != 3) /* aaa=new, bbb=modified, ddd=deleted */
FAIL("mixed: added count", "expected 3, got %zu", added);
else
PASS("mixed: 3 elements classified");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFILES]) != 1 ||
strcmp(list_nth_name(&out[PLOCALSCAN_SCAN_LIST_NEWFILES], 0), "aaa") != 0)
FAIL("mixed: NEWFILES has aaa",
"count=%d name=%s",
list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFILES]),
list_nth_name(&out[PLOCALSCAN_SCAN_LIST_NEWFILES], 0));
else
PASS("mixed: NEWFILES contains aaa");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_MODFILES]) != 1 ||
strcmp(list_nth_name(&out[PLOCALSCAN_SCAN_LIST_MODFILES], 0), "bbb") != 0)
FAIL("mixed: MODFILES has bbb",
"count=%d name=%s",
list_count(&out[PLOCALSCAN_SCAN_LIST_MODFILES]),
list_nth_name(&out[PLOCALSCAN_SCAN_LIST_MODFILES], 0));
else
PASS("mixed: MODFILES contains bbb");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_DELFILES]) != 1 ||
strcmp(list_nth_name(&out[PLOCALSCAN_SCAN_LIST_DELFILES], 0), "ddd") != 0)
FAIL("mixed: DELFILES has ddd",
"count=%d name=%s",
list_count(&out[PLOCALSCAN_SCAN_LIST_DELFILES]),
list_nth_name(&out[PLOCALSCAN_SCAN_LIST_DELFILES], 0));
else
PASS("mixed: DELFILES contains ddd");
if (list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFOLDERS]) != 0 ||
list_count(&out[PLOCALSCAN_SCAN_LIST_DELFOLDERS]) != 0)
FAIL("mixed: no spurious folder entries",
"NEWFOLDERS=%d DELFOLDERS=%d",
list_count(&out[PLOCALSCAN_SCAN_LIST_NEWFOLDERS]),
list_count(&out[PLOCALSCAN_SCAN_LIST_DELFOLDERS]));
else
PASS("mixed: NEWFOLDERS and DELFOLDERS empty (ccc unchanged)");
free_out(out);
psync_list_for_each_element_call(&disklist, sync_folderlist, list, free);
psync_list_for_each_element_call(&dblist, sync_folderlist, list, free);
}
/* ------------------------------------------------------------------ */
int main(void) {
test_comparators();
test_merge_new_entries();
test_merge_new_folder();
test_merge_deleted_entries();
test_merge_modified_file();
test_merge_unchanged_file();
test_merge_mixed();
printf("\n%d passed, %d failed\n", passes, failures);
return failures ? 1 : 0;
}