/* test/font_resolve_smoke.c — M31 字体路径记忆化宿主单测(纯 POSIX,不依赖设备/SD 卡)。 * * 覆盖缓存的三条红线: * F1 没找到 → NULL,且**不缓存失败**:之后拷入字库,下一次调用就能找到; * F2 命中 → 后续调用返回同一路径; * F3 命中后字库被删/改名 → access() 校验失败 → 重扫,立刻回到「没有字库」的真实答案; * 重新放回后又能找到(缓存不会一直错下去)。 * 另测与加缓存前一致的扫描语义(深度上限、首个字库、忽略隐藏文件/非字体扩展名)。 * * 用法:./font_resolve_smoke_host [临时目录],默认用 TMPDIR。 */ #include "font_resolve.h" #include #include #include #include #include static int g_pass = 0, g_fail = 0; #define CHECK(cond, msg) do { \ if (cond) g_pass++; \ else { g_fail++; printf("FAIL %s:%d %s\n", __FILE__, __LINE__, msg); } \ } while (0) static void mkdirs(const char *path) { char t[512]; snprintf(t, sizeof t, "%s", path); for (char *p = t + 1; *p; p++) { if (*p == '/') { *p = 0; mkdir(t, 0755); *p = '/'; } } mkdir(t, 0755); } static void write_file(const char *path, const char *body) { FILE *f = fopen(path, "wb"); if (!f) { printf("无法创建 %s\n", path); exit(2); } fputs(body, f); fclose(f); } /* 真机形态:MeowOS/fonts 下 35 个目录,每个目录若干非字体文件 + 1 个字体。 * 字体放在子目录里,让「首个命中」落在 walk 较深处(接近 survey 的真机耗时场景)。 */ static void make_fonts_tree(const char *root, int ndirs) { char p[512]; for (int i = 0; i < ndirs; i++) { snprintf(p, sizeof p, "%s/family_%02d/sub", root, i); mkdirs(p); for (int j = 0; j < 3; j++) { snprintf(p, sizeof p, "%s/family_%02d/readme_%d.txt", root, i, j); write_file(p, "x"); } snprintf(p, sizeof p, "%s/family_%02d/sub/Font%02d.ttf", root, i, i); write_file(p, "x"); } } int main(void) { const char *base = getenv("TMPDIR"); if (!base || !base[0]) base = "/tmp"; char tmpl[512]; snprintf(tmpl, sizeof tmpl, "%s/font_resolve_XXXXXX", base); char *tmp = mkdtemp(tmpl); if (!tmp) { printf("mkdtemp 失败\n"); return 2; } char root[512]; snprintf(root, sizeof root, "%s/fonts", tmp); mkdirs(root); /* F1 空目录:没找到,且失败不缓存 */ CHECK(font_resolve(root) == NULL, "空目录 → NULL"); CHECK(font_resolve(root) == NULL, "紧接着再调仍 NULL(失败未缓存成「找到」)"); /* F1b 空目录之后再拷入字库:下一次调用就能找到(「用户后来放了字库」) */ char f1[512]; snprintf(f1, sizeof f1, "%s/late.ttf", root); write_file(f1, "x"); const char *p = font_resolve(root); CHECK(p && strcmp(p, f1) == 0, "失败后拷入字库 → 下次调用即找到"); /* F2 命中:连续调用返回同一路径(缓存生效,不重新 walk) */ const char *q = font_resolve(root); CHECK(q && strcmp(q, f1) == 0, "命中后返回同一路径"); /* 命中期间新增字库不改选(与「取 readdir 顺序第一个」的既有语义一致; * 显式换字体走顶栏「刷新」→ font_cache_invalidate) */ char f1b[512]; snprintf(f1b, sizeof f1b, "%s/aaa.otf", root); write_file(f1b, "x"); CHECK(font_resolve(root) && strcmp(font_resolve(root), f1) == 0, "命中期间新增字库不改选(保持一致,刷新可强制重选)"); /* F3 字库被删:access() 校验失败 → 重扫 → NULL(不许「明明没有却说有」,也不许反过来卡死) */ unlink(f1); unlink(f1b); CHECK(font_resolve(root) == NULL, "缓存路径被删 → 下一次调用即重扫得到 NULL"); /* F3b 放回不同名的字库:仍能找到(缓存失效状态不会卡住) */ char f3[512]; snprintf(f3, sizeof f3, "%s/back.ttf", root); write_file(f3, "x"); p = font_resolve(root); CHECK(p && strcmp(p, f3) == 0, "删掉后放回字库 → 重新找到"); /* F4 多目录树:首个命中 = walk 顺序第一个(35 个目录时的行为与缓存前一致) */ char big[512]; snprintf(big, sizeof big, "%s/bigfonts", tmp); make_fonts_tree(big, 35); font_cache_invalidate(); /* 换树前清缓存(等价于顶栏刷新) */ const char *b = font_resolve(big); CHECK(b && strstr(b, "/family_") != NULL && strcmp(b + strlen(b) - 4, ".ttf") == 0, "35 目录树 → 返回字体文件路径"); const char *b2 = font_resolve(big); CHECK(b2 && strcmp(b2, b) == 0, "大树上命中后路径稳定"); /* F5 深度上限(相对 root:目录深度 > 3 的字库不收,与缓存前一致) */ char dfroot[512]; snprintf(dfroot, sizeof dfroot, "%s/deepfonts", tmp); char deep[512]; snprintf(deep, sizeof deep, "%s/a/b/c/d", dfroot); mkdirs(deep); char dp[512]; snprintf(dp, sizeof dp, "%s/too_deep.ttf", deep); /* root/a/b/c/d/ → 深度 4 */ write_file(dp, "x"); font_cache_invalidate(); CHECK(font_resolve(dfroot) == NULL, "深度 > 3 的字库不收(与缓存前扫描口径一致)"); /* 第 3 层仍收 */ char ok3[512]; snprintf(ok3, sizeof ok3, "%s/a/b/c/ok3.ttf", dfroot); /* root/a/b/c/ → 深度 3 */ write_file(ok3, "x"); font_cache_invalidate(); p = font_resolve(dfroot); CHECK(p && strcmp(p, ok3) == 0, "第 3 层目录的字库收(深度上限边界)"); /* F6 忽略隐藏文件/非字体扩展名 */ char hid[512]; snprintf(hid, sizeof hid, "%s/hidden", tmp); mkdirs(hid); char hp[512]; snprintf(hp, sizeof hp, "%s/.hidden.ttf", hid); write_file(hp, "x"); snprintf(hp, sizeof hp, "%s/notafont.txt", hid); write_file(hp, "x"); font_cache_invalidate(); CHECK(font_resolve(hid) == NULL, "隐藏文件/非字体扩展名不被当作字库"); /* F7 root 非法 */ font_cache_invalidate(); CHECK(font_resolve(NULL) == NULL && font_resolve("") == NULL, "root=NULL/空 → NULL"); char missing[512]; snprintf(missing, sizeof missing, "%s/no_such_dir", tmp); CHECK(font_resolve(missing) == NULL, "root 不存在 → NULL"); printf("font_resolve_smoke: %d pass, %d fail\n", g_pass, g_fail); char cmd[600]; snprintf(cmd, sizeof cmd, "rm -rf '%s'", tmp); if (system(cmd) != 0) { /* 忽略 */ } return g_fail ? 1 : 0; }