files md5 6f6b0a6d; 0 warning; 宿主 test_fsx 全过(路径安全/格式化/排序/虚拟化/写操作/文本嗅探); qemu 离屏冒烟通过 SDK 缺口记录: listview 仅单行 label(本 App 用 custom 画布自绘行); common 档字库中文缺字(未声明 fonts:full) 真机待验证: 列表渲染/滚动手感/IME 重命名/弹窗交互
218 lines
8.5 KiB
C
218 lines
8.5 KiB
C
/* 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 <fcntl.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/types.h>
|
||
#include <unistd.h>
|
||
|
||
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;
|
||
}
|