Files

204 lines
6.3 KiB
C

#include "test_hardlink.h"
#include "hardlink.h"
#include "test_utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
/* A fresh table starts empty and destroy accepts NULL / a fresh table. */
static void test_hardlink_create_destroy() {
hardlink_table_destroy(NULL);
HardLinkTable* table = hardlink_table_create();
EXPECT_NOT_NULL(table);
EXPECT_EQ_INT((int)table->count, 0);
EXPECT_EQ_INT((int)table->capacity, 0);
EXPECT_EQ_INT(table->next_gid, 1);
hardlink_table_destroy(table);
}
/* The first member of an (dev, ino) group is data-carrying and owns the group;
* every later member gets the SAME gid, is not first, and points back at the
* first member's wire path. */
static void test_hardlink_grouping() {
HardLinkTable* table = hardlink_table_create();
EXPECT_NOT_NULL(table);
int gid_a = -1, gid_b = -1;
bool first_a = false, first_b = false;
char* first_path_a = NULL;
char* first_path_b = NULL;
EXPECT_TRUE(
hardlink_table_assign(table, "dir/first.txt", 7, 42, &gid_a, &first_a, &first_path_a));
EXPECT_TRUE(first_a);
EXPECT_EQ_INT(gid_a, 1);
EXPECT_NOT_NULL(first_path_a);
EXPECT_EQ_STR(first_path_a, "dir/first.txt");
EXPECT_TRUE(
hardlink_table_assign(table, "dir/second.txt", 7, 42, &gid_b, &first_b, &first_path_b));
EXPECT_FALSE(first_b);
EXPECT_EQ_INT(gid_b, gid_a);
EXPECT_NOT_NULL(first_path_b);
EXPECT_EQ_STR(first_path_b, "dir/first.txt");
/* Two members map onto a single stored group. */
EXPECT_EQ_INT((int)table->count, 1);
EXPECT_EQ_INT(table->next_gid, 2);
free(first_path_a);
free(first_path_b);
hardlink_table_destroy(table);
}
/* A different inode on the same device is a distinct group with a fresh gid. */
static void test_hardlink_distinct_inode() {
HardLinkTable* table = hardlink_table_create();
EXPECT_NOT_NULL(table);
int gid1 = -1, gid2 = -1;
bool first1 = false, first2 = false;
char* path1 = NULL;
char* path2 = NULL;
EXPECT_TRUE(hardlink_table_assign(table, "a", 7, 100, &gid1, &first1, &path1));
EXPECT_TRUE(first1);
EXPECT_TRUE(hardlink_table_assign(table, "b", 7, 101, &gid2, &first2, &path2));
EXPECT_TRUE(first2);
EXPECT_TRUE(gid1 != gid2);
EXPECT_EQ_INT(gid1, 1);
EXPECT_EQ_INT(gid2, 2);
EXPECT_EQ_STR(path1, "a");
EXPECT_EQ_STR(path2, "b");
free(path1);
free(path2);
hardlink_table_destroy(table);
}
/* Identical (dev, ino) on a DIFFERENT device must never be conflated: inode
* numbers are only unique per filesystem, so grouping is scoped by st_dev. */
static void test_hardlink_distinct_device() {
HardLinkTable* table = hardlink_table_create();
EXPECT_NOT_NULL(table);
int gid1 = -1, gid2 = -1;
bool first1 = false, first2 = false;
char* path1 = NULL;
char* path2 = NULL;
EXPECT_TRUE(hardlink_table_assign(table, "dev_a/one", 1, 55, &gid1, &first1, &path1));
EXPECT_TRUE(hardlink_table_assign(table, "dev_b/one", 2, 55, &gid2, &first2, &path2));
EXPECT_TRUE(first1);
EXPECT_TRUE(first2);
EXPECT_TRUE(gid1 != gid2);
EXPECT_EQ_INT((int)table->count, 2);
free(path1);
free(path2);
hardlink_table_destroy(table);
}
/* The table owns deep copies of every path: mutating (or freeing) the caller's
* buffer after assign must not affect the stored / returned paths. */
static void test_hardlink_path_ownership() {
HardLinkTable* table = hardlink_table_create();
EXPECT_NOT_NULL(table);
char caller[] = "owned/path";
int gid = -1;
bool is_first = false;
char* out = NULL;
EXPECT_TRUE(hardlink_table_assign(table, caller, 3, 9, &gid, &is_first, &out));
EXPECT_TRUE(is_first);
/* The returned pointer is a distinct allocation, not the caller's buffer. */
EXPECT_TRUE(out != caller);
EXPECT_TRUE(table->items[0].first_path != caller);
memset(caller, 'X', sizeof(caller) - 1);
EXPECT_EQ_STR(out, "owned/path");
EXPECT_EQ_STR(table->items[0].first_path, "owned/path");
/* Later members get their own independent copy of the first path. */
char second_caller[] = "owned/second";
int gid2 = -1;
bool first2 = true;
char* out2 = NULL;
EXPECT_TRUE(hardlink_table_assign(table, second_caller, 3, 9, &gid2, &first2, &out2));
EXPECT_FALSE(first2);
EXPECT_EQ_STR(out2, "owned/path");
EXPECT_TRUE(out2 != table->items[0].first_path);
EXPECT_TRUE(out2 != out);
free(out);
free(out2);
hardlink_table_destroy(table);
}
/* Bad arguments must be rejected without touching the table. */
static void test_hardlink_reject_bad_args() {
HardLinkTable* table = hardlink_table_create();
EXPECT_NOT_NULL(table);
int gid = 0;
bool is_first = false;
char* out = NULL;
EXPECT_FALSE(hardlink_table_assign(NULL, "x", 1, 1, &gid, &is_first, &out));
EXPECT_FALSE(hardlink_table_assign(table, NULL, 1, 1, &gid, &is_first, &out));
EXPECT_FALSE(hardlink_table_assign(table, "x", 1, 1, NULL, &is_first, &out));
EXPECT_FALSE(hardlink_table_assign(table, "x", 1, 1, &gid, NULL, &out));
EXPECT_FALSE(hardlink_table_assign(table, "x", 1, 1, &gid, &is_first, NULL));
EXPECT_EQ_INT((int)table->count, 0);
hardlink_table_destroy(table);
}
/* Many distinct groups grow the item array through its realloc path and keep
* gid assignment stable and monotonic. */
static void test_hardlink_many_groups() {
HardLinkTable* table = hardlink_table_create();
EXPECT_NOT_NULL(table);
const int n = 200;
for (int i = 0; i < n; i++) {
int gid = -1;
bool is_first = false;
char* out = NULL;
char path[32];
snprintf(path, sizeof(path), "file_%d", i);
EXPECT_TRUE(hardlink_table_assign(table, path, 1, (ino_t)(1000 + i), &gid, &is_first, &out));
EXPECT_TRUE(is_first);
EXPECT_EQ_INT(gid, i + 1);
EXPECT_EQ_STR(out, path);
free(out);
}
EXPECT_EQ_INT((int)table->count, n);
EXPECT_EQ_INT(table->next_gid, n + 1);
/* Re-querying an existing inode still reports the original gid. */
int gid = -1;
bool is_first = true;
char* out = NULL;
EXPECT_TRUE(hardlink_table_assign(table, "file_7_again", 1, 1007, &gid, &is_first, &out));
EXPECT_FALSE(is_first);
EXPECT_EQ_INT(gid, 8);
EXPECT_EQ_STR(out, "file_7");
free(out);
hardlink_table_destroy(table);
}
void test_hardlink() {
test_hardlink_create_destroy();
test_hardlink_grouping();
test_hardlink_distinct_inode();
test_hardlink_distinct_device();
test_hardlink_path_ownership();
test_hardlink_reject_bad_args();
test_hardlink_many_groups();
}