mirror of
https://github.com/aydencharles/onionHEN.git
synced 2026-10-22 11:53:07 -05:00
274 lines
8.6 KiB
C
274 lines
8.6 KiB
C
/* Host tests for libonion_platform fs helpers. */
|
|
#include "test_harness.h"
|
|
|
|
#include <onion/fs.h>
|
|
#include <onion/tree.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
static int make_temp_dir(char *buf, size_t buflen) {
|
|
char tmpl[] = "/tmp/onion-fs-XXXXXX";
|
|
if (!mkdtemp(tmpl)) {
|
|
return -1;
|
|
}
|
|
if (strlen(tmpl) + 1 > buflen) {
|
|
return -1;
|
|
}
|
|
memcpy(buf, tmpl, strlen(tmpl) + 1);
|
|
return 0;
|
|
}
|
|
|
|
static int test_if_exists_null_and_missing(void) {
|
|
TEST_ASSERT_TRUE(!if_exists(NULL));
|
|
TEST_ASSERT_TRUE(!if_exists("/tmp/onion-fs-definitely-missing-xyz-9f3a"));
|
|
return 0;
|
|
}
|
|
|
|
static int read_file(const char *path, char *buf, size_t buf_size, size_t *out_len) {
|
|
FILE *fp = fopen(path, "rb");
|
|
if (!fp)
|
|
return -1;
|
|
const size_t n = fread(buf, 1, buf_size, fp);
|
|
fclose(fp);
|
|
if (out_len)
|
|
*out_len = n;
|
|
return 0;
|
|
}
|
|
|
|
static int test_write_file_atomic(void) {
|
|
char dir[64];
|
|
char path[128];
|
|
char buf[64];
|
|
size_t n = 0;
|
|
const char first[] = "hello";
|
|
const char second[] = "replaced-content";
|
|
|
|
TEST_ASSERT_TRUE(!write_file_atomic(NULL, first, sizeof(first) - 1));
|
|
TEST_ASSERT_TRUE(!write_file_atomic("", first, sizeof(first) - 1));
|
|
TEST_ASSERT_TRUE(!write_file_atomic("/tmp/x", NULL, 4));
|
|
|
|
TEST_ASSERT_EQ_INT(0, make_temp_dir(dir, sizeof(dir)));
|
|
snprintf(path, sizeof(path), "%s/blob", dir);
|
|
|
|
TEST_ASSERT_TRUE(write_file_atomic(path, first, sizeof(first) - 1));
|
|
TEST_ASSERT_TRUE(if_exists(path));
|
|
TEST_ASSERT_EQ_INT(0, read_file(path, buf, sizeof(buf), &n));
|
|
TEST_ASSERT_EQ_U64(sizeof(first) - 1, n);
|
|
TEST_ASSERT_TRUE(memcmp(buf, first, n) == 0);
|
|
|
|
TEST_ASSERT_TRUE(write_file_atomic(path, second, sizeof(second) - 1));
|
|
TEST_ASSERT_EQ_INT(0, read_file(path, buf, sizeof(buf), &n));
|
|
TEST_ASSERT_EQ_U64(sizeof(second) - 1, n);
|
|
TEST_ASSERT_TRUE(memcmp(buf, second, n) == 0);
|
|
|
|
TEST_ASSERT_TRUE(write_file_atomic(path, NULL, 0));
|
|
TEST_ASSERT_EQ_INT(0, read_file(path, buf, sizeof(buf), &n));
|
|
TEST_ASSERT_EQ_U64(0, n);
|
|
|
|
TEST_ASSERT_TRUE(rmtree(dir));
|
|
return 0;
|
|
}
|
|
|
|
static int test_touch_and_exists(void) {
|
|
char dir[64];
|
|
char path[128];
|
|
TEST_ASSERT_EQ_INT(0, make_temp_dir(dir, sizeof(dir)));
|
|
snprintf(path, sizeof(path), "%s/flag", dir);
|
|
|
|
TEST_ASSERT_TRUE(!if_exists(path));
|
|
TEST_ASSERT_TRUE(touch_file(path));
|
|
TEST_ASSERT_TRUE(if_exists(path));
|
|
TEST_ASSERT_TRUE(touch_file(path)); /* truncate ok */
|
|
|
|
TEST_ASSERT_TRUE(!touch_file(NULL));
|
|
|
|
unlink(path);
|
|
rmdir(dir);
|
|
return 0;
|
|
}
|
|
|
|
static int test_mkdir_tree(void) {
|
|
char dir[64];
|
|
char nested[160];
|
|
char too_long[1025];
|
|
struct stat st;
|
|
|
|
TEST_ASSERT_TRUE(!mkdir_tree(NULL));
|
|
TEST_ASSERT_TRUE(!mkdir_tree(""));
|
|
memset(too_long, 'a', sizeof(too_long) - 1);
|
|
too_long[sizeof(too_long) - 1] = '\0';
|
|
TEST_ASSERT_TRUE(!mkdir_tree(too_long));
|
|
|
|
TEST_ASSERT_EQ_INT(0, make_temp_dir(dir, sizeof(dir)));
|
|
snprintf(nested, sizeof(nested), "%s/a/b/c", dir);
|
|
TEST_ASSERT_TRUE(mkdir_tree(nested));
|
|
TEST_ASSERT_TRUE(if_exists(nested));
|
|
TEST_ASSERT_EQ_INT(0, stat(nested, &st));
|
|
TEST_ASSERT_TRUE(S_ISDIR(st.st_mode));
|
|
TEST_ASSERT_TRUE(mkdir_tree(nested));
|
|
|
|
TEST_ASSERT_TRUE(rmtree(dir));
|
|
return 0;
|
|
}
|
|
|
|
static int test_rmtree_nested(void) {
|
|
char dir[64];
|
|
char sub[128];
|
|
char file[160];
|
|
TEST_ASSERT_EQ_INT(0, make_temp_dir(dir, sizeof(dir)));
|
|
snprintf(sub, sizeof(sub), "%s/nested", dir);
|
|
TEST_ASSERT_EQ_INT(0, mkdir(sub, 0777));
|
|
snprintf(file, sizeof(file), "%s/a.txt", sub);
|
|
TEST_ASSERT_TRUE(touch_file(file));
|
|
snprintf(file, sizeof(file), "%s/b.txt", dir);
|
|
TEST_ASSERT_TRUE(touch_file(file));
|
|
|
|
TEST_ASSERT_TRUE(rmtree(dir));
|
|
TEST_ASSERT_TRUE(!if_exists(dir));
|
|
TEST_ASSERT_TRUE(!rmtree(NULL));
|
|
TEST_ASSERT_TRUE(!rmtree("/tmp/onion-fs-missing-tree-xyz"));
|
|
return 0;
|
|
}
|
|
|
|
static size_t progress_calls;
|
|
static size_t progress_completed;
|
|
static size_t progress_total;
|
|
|
|
static void capture_rmtree_progress(size_t completed, size_t total,
|
|
void *user) {
|
|
(void)user;
|
|
++progress_calls;
|
|
progress_completed = completed;
|
|
progress_total = total;
|
|
}
|
|
|
|
static int test_rmtree_progress(void) {
|
|
char dir[64];
|
|
char sub[128];
|
|
char file[160];
|
|
TEST_ASSERT_EQ_INT(0, make_temp_dir(dir, sizeof(dir)));
|
|
snprintf(sub, sizeof(sub), "%s/nested", dir);
|
|
TEST_ASSERT_EQ_INT(0, mkdir(sub, 0777));
|
|
snprintf(file, sizeof(file), "%s/a.txt", sub);
|
|
TEST_ASSERT_TRUE(touch_file(file));
|
|
snprintf(file, sizeof(file), "%s/b.txt", dir);
|
|
TEST_ASSERT_TRUE(touch_file(file));
|
|
|
|
progress_calls = 0;
|
|
progress_completed = 0;
|
|
progress_total = 0;
|
|
TEST_ASSERT_TRUE(
|
|
rmtree_with_progress(dir, capture_rmtree_progress, NULL));
|
|
TEST_ASSERT_TRUE(!if_exists(dir));
|
|
TEST_ASSERT_EQ_U64(4, progress_total);
|
|
TEST_ASSERT_EQ_U64(progress_total, progress_completed);
|
|
TEST_ASSERT_EQ_U64(5, progress_calls);
|
|
return 0;
|
|
}
|
|
|
|
static int test_read_file_alloc(void) {
|
|
char dir[64];
|
|
char path[128];
|
|
const char payload[] = "read-me-back";
|
|
const size_t payload_len = sizeof(payload) - 1;
|
|
uint8_t *buf = (uint8_t *)0x1;
|
|
size_t size = 42;
|
|
|
|
TEST_ASSERT_TRUE(!read_file_alloc(NULL, 0, &buf, &size));
|
|
TEST_ASSERT_TRUE(!read_file_alloc("", 0, &buf, &size));
|
|
TEST_ASSERT_TRUE(!read_file_alloc("/tmp/x", 0, NULL, &size));
|
|
TEST_ASSERT_TRUE(!read_file_alloc("/tmp/x", 0, &buf, NULL));
|
|
/* Every rejection must leave the caller's out-params untouched. */
|
|
TEST_ASSERT_TRUE(buf == (uint8_t *)0x1);
|
|
TEST_ASSERT_EQ_U64(42, size);
|
|
|
|
TEST_ASSERT_EQ_INT(0, make_temp_dir(dir, sizeof(dir)));
|
|
snprintf(path, sizeof(path), "%s/blob", dir);
|
|
|
|
/* A missing file and a directory are both failures, not empty reads. */
|
|
TEST_ASSERT_TRUE(!read_file_alloc(path, 0, &buf, &size));
|
|
TEST_ASSERT_TRUE(!read_file_alloc(dir, 0, &buf, &size));
|
|
TEST_ASSERT_TRUE(buf == (uint8_t *)0x1);
|
|
|
|
/* Round trip against the symmetric writer. */
|
|
TEST_ASSERT_TRUE(write_file_atomic(path, payload, payload_len));
|
|
buf = NULL;
|
|
size = 0;
|
|
TEST_ASSERT_TRUE(read_file_alloc(path, 0, &buf, &size));
|
|
TEST_ASSERT_TRUE(buf != NULL);
|
|
TEST_ASSERT_EQ_U64(payload_len, size);
|
|
TEST_ASSERT_MEMEQ(payload, buf, payload_len);
|
|
/* The spare byte is NUL-terminated so text callers can reuse this buffer.
|
|
* Contract check, not a regression guard: this platform's allocator zeroes
|
|
* recycled blocks, so a missing terminator would read back as 0 anyway. */
|
|
TEST_ASSERT_EQ_INT(0, buf[size]);
|
|
free(buf);
|
|
buf = NULL;
|
|
|
|
/* The cap is inclusive; 0 means no cap. */
|
|
TEST_ASSERT_TRUE(!read_file_alloc(path, payload_len - 1, &buf, &size));
|
|
TEST_ASSERT_TRUE(buf == NULL);
|
|
TEST_ASSERT_TRUE(read_file_alloc(path, payload_len, &buf, &size));
|
|
free(buf);
|
|
|
|
/* An empty file is not a readable payload. */
|
|
TEST_ASSERT_TRUE(write_file_atomic(path, NULL, 0));
|
|
buf = (uint8_t *)0x1;
|
|
TEST_ASSERT_TRUE(!read_file_alloc(path, 0, &buf, &size));
|
|
TEST_ASSERT_TRUE(buf == (uint8_t *)0x1);
|
|
|
|
TEST_ASSERT_TRUE(rmtree(dir));
|
|
return 0;
|
|
}
|
|
|
|
static int test_read_file_alloc_str(void) {
|
|
char dir[64];
|
|
char path[128];
|
|
const char payload[] = "text-payload";
|
|
const size_t payload_len = sizeof(payload) - 1;
|
|
char *text = (char *)0x1;
|
|
size_t size = 42;
|
|
|
|
TEST_ASSERT_TRUE(!read_file_alloc_str(NULL, 0, &text, &size));
|
|
TEST_ASSERT_TRUE(!read_file_alloc_str("/tmp/x", 0, NULL, &size));
|
|
/* Rejections leave the caller's out-params untouched. */
|
|
TEST_ASSERT_TRUE(text == (char *)0x1);
|
|
TEST_ASSERT_EQ_U64(42, size);
|
|
|
|
TEST_ASSERT_EQ_INT(0, make_temp_dir(dir, sizeof(dir)));
|
|
snprintf(path, sizeof(path), "%s/text", dir);
|
|
TEST_ASSERT_TRUE(write_file_atomic(path, payload, payload_len));
|
|
|
|
TEST_ASSERT_TRUE(read_file_alloc_str(path, 0, &text, &size));
|
|
TEST_ASSERT_EQ_U64(payload_len, size);
|
|
/* The point of this variant: a C string with no cast and no extra copy. */
|
|
TEST_ASSERT_EQ_INT(0, strcmp(payload, text));
|
|
TEST_ASSERT_EQ_U64(payload_len, strlen(text));
|
|
free(text);
|
|
|
|
/* The cap is the same one read_file_alloc() applies. */
|
|
text = NULL;
|
|
TEST_ASSERT_TRUE(!read_file_alloc_str(path, payload_len - 1, &text, &size));
|
|
TEST_ASSERT_TRUE(text == NULL);
|
|
|
|
TEST_ASSERT_TRUE(rmtree(dir));
|
|
return 0;
|
|
}
|
|
|
|
int test_platform_fs_suite(void) {
|
|
int failures = 0;
|
|
failures += onion_test_run("fs_if_exists_null_missing", test_if_exists_null_and_missing);
|
|
failures += onion_test_run("fs_touch_and_exists", test_touch_and_exists);
|
|
failures += onion_test_run("fs_write_file_atomic", test_write_file_atomic);
|
|
failures += onion_test_run("fs_read_file_alloc", test_read_file_alloc);
|
|
failures += onion_test_run("fs_read_file_alloc_str", test_read_file_alloc_str);
|
|
failures += onion_test_run("fs_mkdir_tree", test_mkdir_tree);
|
|
failures += onion_test_run("fs_rmtree_nested", test_rmtree_nested);
|
|
failures += onion_test_run("fs_rmtree_progress", test_rmtree_progress);
|
|
return failures;
|
|
}
|