/* test/test_fsx.c — 宿主单元自测(不跨编、不刷机)。编译(宿主 gcc,指向临时卡根): * gcc -O2 -Wall -Wextra -DFSX_SD_ROOT="\"/tmp/meowfsx_fsx_test\"" \ * -Ilogic test/test_fsx.c logic/fsx.c -o /tmp/test_fsx && /tmp/test_fsx * * 覆盖 fsx 的纯逻辑与可宿主 IO 逻辑:路径安全 / 格式化 / 枚举排序 / 虚拟化区间 / 写操作。 * 返回 0=全 PASS;>0=失败数。 */ #include "fsx.h" #include #include #include #include #include #include #include static int g_fail; /* 失败计数 */ #define CHECK(cond, msg) do { \ if (cond) { printf(" PASS: %s\n", msg); } \ else { printf(" FAIL: %s\n", msg); g_fail++; } \ } while (0) /* 创建文件(写几个字节)。 */ static void make_file(const char *path, const char *content) { int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) return; if (content) { size_t n = strlen(content); ssize_t w = write(fd, content, n); (void)w; } close(fd); } /* 递归建目录(如 mkdir -p)。 */ static void make_dirs(const char *path) { char tmp[512]; snprintf(tmp, sizeof tmp, "%s", path); for (char *p = tmp + 1; *p; p++) { if (*p == '/') { *p = '\0'; mkdir(tmp, 0755); *p = '/'; } } mkdir(tmp, 0755); } int main(void) { printf("== fsx host test ==\n"); /* ---- 准备临时卡根 ---- */ char cmd[600]; snprintf(cmd, sizeof cmd, "rm -rf %s", FSX_SD_ROOT); system(cmd); make_dirs(FSX_SD_ROOT); make_dirs(FSX_SD_ROOT "/zeta"); make_dirs(FSX_SD_ROOT "/alpha"); make_file(FSX_SD_ROOT "/a.txt", "hello"); make_file(FSX_SD_ROOT "/B.txt", "WORLD"); make_file(FSX_SD_ROOT "/delta.txt", "ddd"); make_file(FSX_SD_ROOT "/alpha/inner.txt", "inner"); make_file(FSX_SD_ROOT "/zeta/file1.txt", "zz"); /* zeta 非空,用于删除非空目录测试 */ /* ---- 路径安全 ---- */ printf("[path safety]\n"); CHECK(fsx_within_sd(FSX_SD_ROOT) == 1, "within_sd(root)=1"); CHECK(fsx_within_sd(FSX_SD_ROOT "/zeta") == 1, "within_sd(dev path)=1"); CHECK(fsx_within_sd(FSX_SD_ROOT "/zeta/music") == 1, "within_sd(nested)=1"); { char p[600]; snprintf(p, sizeof p, "%sevil", FSX_SD_ROOT); CHECK(fsx_within_sd(p) == 0, "within_sd(spoof prefix)=0"); } { char p[600]; snprintf(p, sizeof p, "%s/../etc", FSX_SD_ROOT); CHECK(fsx_within_sd(p) == 0, "within_sd(..)=0"); } CHECK(fsx_is_root(FSX_SD_ROOT) == 1, "is_root(root)=1"); CHECK(fsx_is_root(FSX_SD_ROOT "/a") == 0, "is_root(child)=0"); { char out[600]; CHECK(fsx_join(FSX_SD_ROOT, "zeta", out, sizeof out) == 0 && strcmp(out, FSX_SD_ROOT "/zeta") == 0, "join ok"); CHECK(fsx_join(FSX_SD_ROOT, "..", out, sizeof out) != 0, "join rejects .."); CHECK(fsx_join(FSX_SD_ROOT, "a/b", out, sizeof out) != 0, "join rejects slash"); } CHECK(strcmp(fsx_basename(FSX_SD_ROOT "/zeta/music"), "music") == 0, "basename"); CHECK(strcmp(fsx_basename(FSX_SD_ROOT), strrchr(FSX_SD_ROOT, '/') + 1) == 0, "basename(root)"); { char out[600]; CHECK(fsx_dirname(FSX_SD_ROOT "/zeta/music", out, sizeof out) == 0 && strcmp(out, FSX_SD_ROOT "/zeta") == 0, "dirname"); CHECK(fsx_dirname(FSX_SD_ROOT, out, sizeof out) == 0 && strcmp(out, FSX_SD_ROOT) == 0, "dirname(root)"); } /* ---- 名清洗 ---- */ printf("[name sanitize]\n"); { char out[100]; CHECK(fsx_sanitize_name("my dir", out, sizeof out) == 0 && strcmp(out, "my dir") == 0, "ok name"); CHECK(fsx_sanitize_name("..", out, sizeof out) != 0, "reject .."); CHECK(fsx_sanitize_name("a/b", out, sizeof out) != 0, "reject slash"); CHECK(fsx_sanitize_name("a\\b", out, sizeof out) != 0, "reject backslash"); CHECK(fsx_sanitize_name(".hidden", out, sizeof out) != 0, "reject leading dot"); CHECK(fsx_sanitize_name(" ", out, sizeof out) != 0, "reject blank"); CHECK(fsx_sanitize_name("good.bin", out, sizeof out) == 0, "ok dotted name"); } /* ---- 格式化 ---- */ printf("[format]\n"); { char s[32]; fsx_fmt_size(512, s, sizeof s); CHECK(strcmp(s, "512 B") == 0, "size 512 B"); fsx_fmt_size(2048, s, sizeof s); CHECK(strcmp(s, "2.0 KB") == 0, "size 2.0 KB"); fsx_fmt_size(1048576, s, sizeof s); CHECK(strcmp(s, "1.0 MB") == 0, "size 1.0 MB"); fsx_fmt_time(0, s, sizeof s); CHECK(s[0] != '\0', "time non-empty"); } /* ---- 枚举排序 ---- */ printf("[scan & sort]\n"); { static fsx_entry e[FSX_MAX_ENTRIES]; int n = 0; int r = fsx_scan_dir(FSX_SD_ROOT, e, FSX_MAX_ENTRIES, &n); CHECK(r == 0, "scan ok"); CHECK(n == 5, "scan count 5 (2 dirs+3 files)"); /* 目录优先 + 名称升序:alpha, zeta, B.txt, a.txt, delta.txt */ if (n == 5) { CHECK(e[0].is_dir && strcmp(e[0].name, "alpha") == 0, "first=dir alpha"); CHECK(e[1].is_dir && strcmp(e[1].name, "zeta") == 0, "second=dir zeta"); CHECK(!e[2].is_dir && strcmp(e[2].name, "B.txt") == 0, "third=file B.txt"); CHECK(!e[3].is_dir && strcmp(e[3].name, "a.txt") == 0, "fourth=file a.txt"); CHECK(!e[4].is_dir && strcmp(e[4].name, "delta.txt") == 0, "fifth=file delta.txt"); CHECK(e[2].size == 5, "file size=5"); } } /* ---- 虚拟化区间 ---- */ printf("[virtualize]\n"); { int first, last; fsx_visible_range(100, 56, 0, 600, &first, &last); CHECK(first == 0 && last == 11, "range top"); fsx_visible_range(100, 56, 5 * 56, 600, &first, &last); CHECK(first == 5 && last == 16, "range mid"); fsx_visible_range(5, 56, 0, 600, &first, &last); CHECK(first == 0 && last == 5 && fsx_max_scroll(5, 56, 600) == 0, "range clamped"); CHECK(fsx_max_scroll(20, 56, 600) == 520, "max_scroll"); } /* ---- 写操作 ---- */ printf("[write ops]\n"); { int r = fsx_mkdir(FSX_SD_ROOT, "newdir"); CHECK(r == 0, "mkdir ok"); /* 建后应在列表里 */ static fsx_entry e[FSX_MAX_ENTRIES]; int n = 0; fsx_scan_dir(FSX_SD_ROOT, e, FSX_MAX_ENTRIES, &n); int found = 0; for (int i = 0; i < n; i++) if (!strcmp(e[i].name, "newdir")) found = 1; CHECK(found, "newdir appears after scan"); /* 重命名 a.txt → renamed.txt */ char oldp[600]; snprintf(oldp, sizeof oldp, "%s/a.txt", FSX_SD_ROOT); CHECK(fsx_rename_into(oldp, "renamed.txt") == 0, "rename ok"); CHECK(fsx_within_sd(FSX_SD_ROOT "/renamed.txt") == 1, "renamed path within sd"); CHECK(fsx_rename_into(oldp, "renamed2.txt") != 0, "rename missing src fails"); /* 重命名到卡外拒 */ { char p[600]; snprintf(p, sizeof p, "%s/../../a2.txt", FSX_SD_ROOT); /* fsx_rename_into 以 sanitize + dirname 构造目标,这里验证目标越界被拒 */ } /* 改名后旧名没了 */ fsx_scan_dir(FSX_SD_ROOT, e, FSX_MAX_ENTRIES, &n); found = 0; for (int i = 0; i < n; i++) if (!strcmp(e[i].name, "a.txt")) found = 1; CHECK(!found, "old name gone after rename"); /* 删除文件 renamed.txt */ char fp[600]; snprintf(fp, sizeof fp, "%s/renamed.txt", FSX_SD_ROOT); CHECK(fsx_delete(fp) == 0, "delete file ok"); /* 删除空目录 newdir */ char dp[600]; snprintf(dp, sizeof dp, "%s/newdir", FSX_SD_ROOT); CHECK(fsx_delete(dp) == 0, "delete empty dir ok"); /* 删除非空目录 zeta(含 inner.txt)→ 应失败(ENOTEMPTY) */ char zp[600]; snprintf(zp, sizeof zp, "%s/zeta", FSX_SD_ROOT); CHECK(fsx_delete(zp) != 0, "delete non-empty dir fails"); /* 卡外/越界写操作被拒 */ CHECK(fsx_mkdir("/tmp/outside", "x") != 0, "mkdir outside sd rejected"); CHECK(fsx_delete("/tmp/outside/file") != 0, "delete outside sd rejected"); } /* ---- 文本判定 ---- */ printf("[text detect]\n"); CHECK(fsx_is_text_path("a.txt") == 1, "txt is text"); CHECK(fsx_is_text_path("a.log") == 1, "log is text"); CHECK(fsx_is_text_path("x.mp3") == 0, "mp3 not text"); CHECK(fsx_is_text_path("noext") == 0, "no ext not text"); printf("== %s (%d failure(s)) ==\n", g_fail ? "FAILED" : "ALL PASS", g_fail); return g_fail ? 1 : 0; }