Files
MeowOS-For-R3P2/patch_v2/files_app/test/test_fsx.c
T
MeowOS c187b7bce3 feat(files): 新增外置系统文件管理器 os.meow.files (agent-183) — 目录浏览(虚拟化列表/空态)+文本预览(≤256KB截断)+删除/重命名/新建/属性(BottomSheet+IME);路径写操作限 TF 卡内(拒 .. 越界);多文件结构(app.c+ui/{browser,viewer}.c+ui/common/chrome.c+logic/fsx.c) 按批次②范本
files md5 6f6b0a6d; 0 warning; 宿主 test_fsx 全过(路径安全/格式化/排序/虚拟化/写操作/文本嗅探); qemu 离屏冒烟通过
SDK 缺口记录: listview 仅单行 label(本 App 用 custom 画布自绘行); common 档字库中文缺字(未声明 fonts:full)
真机待验证: 列表渲染/滚动手感/IME 重命名/弹窗交互
2026-08-31 04:03:56 +08:00

218 lines
8.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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;
}