Files
MeowOS-For-R3P2/patch_v2/reader_app/test/font_resolve_smoke.c
T
AskaEth dc9bfdbdaf perf(reader): M31 字体路径记忆化(开书字体查找 35 目录 walk → 1 次 stat)+ 「· 读」徽标即时刷新
字体:find_font_rec/resolve_font 从 reader.c 抽到 app/font_resolve.c 并加进程内记忆化。
只缓存成功路径;命中后仍做一次 access() 校验(字库被删即重扫);没找到不缓存,
用户之后拷入的字库下次开书即可找到。顶栏「刷新」额外调 font_cache_invalidate(),
给「新拷入字库、旧字库仍在」一条显式失效路径。宿主实测:35 目录树 walk 0.026~0.54ms
(=300+ 次系统调用)vs 命中 0.001ms(1 次 access);开书路径每次省一次完整 walk。

徽标:副题格式化抽成 shelf_fmt_sub()(扫描与「刚读完置顶」共用一份实现),
promote_just_read() 改 ts 后立即重算这本的 sub/sub_folder,「· 读」不再等下次全量刷新。

回归:test_reader_core 6846、shelf_group_smoke 63(+8)、font_resolve_smoke 14 全绿;
build_reader.sh 0 warning;扫描缓存/进度语义未改。app.json 0.1.3/code4 → 0.1.4/code5。
2026-11-10 13:39:32 +08:00

163 lines
6.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/font_resolve_smoke.c — M31 字体路径记忆化宿主单测(纯 POSIX,不依赖设备/SD 卡)。
*
* 覆盖缓存的三条红线:
* F1 没找到 → NULL,且**不缓存失败**:之后拷入字库,下一次调用就能找到;
* F2 命中 → 后续调用返回同一路径;
* F3 命中后字库被删/改名 → access() 校验失败 → 重扫,立刻回到「没有字库」的真实答案;
* 重新放回后又能找到(缓存不会一直错下去)。
* 另测与加缓存前一致的扫描语义(深度上限、首个字库、忽略隐藏文件/非字体扩展名)。
*
* 用法:./font_resolve_smoke_host [临时目录],默认用 TMPDIR。
*/
#include "font_resolve.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
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;
}