/* * test_meow_ui_str.c — meow_str_* 宿主单测(2026-09-02,§13.i18n 雏形)。 * * 编译(宿主,meow_ui_str 为纯 libc,无需 SDK 其它对象): * gcc -Wall -Wextra -o test_meow_ui_str test_meow_ui_str.c meow_ui_str.c -I. * * 两种模式(覆盖表按 locale 懒加载,分两次跑): * ./test_meow_ui_str tmp → unsetenv MEOW_DATA_DIR,仅验证内嵌双语表(无覆盖表) * ./test_meow_ui_str env → 设 MEOW_DATA_DIR=/data,写 /strings/en_us.txt, * 验证 TF 覆盖表优先级(覆盖>内嵌)+ locale 即时生效 * * 覆盖矩阵: * T1 内嵌回退(默认 zh_cn):common.ok/loading/no_data 等取 zh * T2 切 locale 即时生效:set_locale(en_us) → 内嵌 en;set_locale(zh_cn) → 内嵌 zh(无需重初始化) * T3 未知 id 回退:meow_str("nope.missing") 原样返回 id;meow_str(NULL) 返回 "" * T4 TF 覆盖优先级:override > 内嵌当前语言;未覆盖 id 回落内嵌;自定义 id 命中 * T5 查找正确性:一批 id 的 zh/en 值逐一断言 * T6 meow_strf:取串并按 snprintf 语义写调用方缓冲(与 meow_str 一致) * * 注:set_locale 的 ui.locale 持久化目标是板载 /usr/data/meow/settings.conf,宿主无该目录 * → 静默失败(运行时仍切换,仅不跨启动);不影响本测试断言。 */ #include "meow_ui.h" #include #include #include #include #include static int g_fail = 0; #define CHECK(cond, msg) do { \ if (cond) { printf(" PASS: %s\n", msg); } \ else { printf(" FAIL: %s\n", msg); g_fail = 1; } \ } while (0) /* 找内嵌某 id 的 zh/en 期望值(供 T5 逐项对照)。不在表内返回 NULL。 */ static const char *expect(const char *id, int en) { /* 与 meow_ui_str.c 的 g_embed[] 对齐(此处只列 T5 抽查的子集) */ static const struct { const char *id; const char *zh; const char *en; } t[] = { { "common.ok", "确定", "OK" }, { "common.cancel", "取消", "Cancel" }, { "common.back", "返回", "Back" }, { "common.confirm", "确认", "Confirm" }, { "common.loading", "加载中...", "Loading..." }, { "common.no_data", "暂无数据", "No data" }, { "common.retry_fail", "重试失败", "Retry failed" }, { "common.tap_ok_edit", "点击确定编辑", "tap OK to edit..." }, { "ime.space", "空格", "Space" }, { "ime.enter", "确定", "OK" }, { "ime.esc", "取消", "Cancel" }, { "ime.clear", "清", "Clear" }, { "ime.pinyin_mode", "拼音", "Pinyin" }, { "ime.no_candidate", "(无候选)", "(none)" }, }; for (size_t i = 0; i < sizeof t / sizeof t[0]; i++) if (strcmp(t[i].id, id) == 0) return en ? t[i].en : t[i].zh; return NULL; } int main(int argc, char **argv) { const char *mode = (argc > 1) ? argv[1] : "tmp"; char *tmpdir = NULL; if (strcmp(mode, "env") == 0) { /* 建 /data + /strings/en_us.txt(meow_str 由 MEOW_DATA_DIR 去尾 /data * 得 /,再拼 strings/.txt)。 */ tmpdir = strdup("/tmp/meowstrtestXXXXXX"); if (!mkdtemp(tmpdir)) { printf("FATAL: mkdtemp failed\n"); return 2; } char datadir[512], strdir[512]; snprintf(datadir, sizeof datadir, "%s/data", tmpdir); snprintf(strdir, sizeof strdir, "%s/strings", tmpdir); mkdir(datadir, 0755); mkdir(strdir, 0755); char file[512]; snprintf(file, sizeof file, "%s/strings/en_us.txt", tmpdir); FILE *f = fopen(file, "w"); if (!f) { printf("FATAL: cannot write override file\n"); free(tmpdir); return 2; } /* 混合:正常行 / '=' 两侧带空格 / 空行 / 无 '=' 行(应被跳过) */ fprintf(f, "# comment line (no equals)\n"); fprintf(f, "common.ok=确认OK\n"); fprintf(f, "common.custom=自定义覆盖\n"); fprintf(f, "common.loading = 加载中覆盖\n"); fprintf(f, "badline-without-equals\n"); fprintf(f, "\n"); fclose(f); setenv("MEOW_DATA_DIR", datadir, 1); } else { unsetenv("MEOW_DATA_DIR"); } printf("== meow_str test [%s] ==\n", mode); printf(" locale = %s\n", meow_str_locale()); /* --- T1 内嵌回退(默认 zh_cn,无覆盖表/无 zh_cn.txt)--- */ CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T1 zh common.ok = 确定"); CHECK(strcmp(meow_str("common.cancel"), "取消") == 0, "T1 zh common.cancel = 取消"); CHECK(strcmp(meow_str("common.loading"), "加载中...") == 0, "T1 zh common.loading = 加载中..."); CHECK(strcmp(meow_str("common.no_data"), "暂无数据") == 0, "T1 zh common.no_data = 暂无数据"); CHECK(strcmp(meow_str("ime.space"), "空格") == 0, "T1 zh ime.space = 空格"); /* --- T3 未知 id 回退(永不 NULL)--- */ CHECK(strcmp(meow_str("nope.missing"), "nope.missing") == 0, "T3 未知 id 原样返回 id"); CHECK(strcmp(meow_str(NULL), "") == 0, "T3 meow_str(NULL) = \"\""); /* --- T6 meow_strf:与 meow_str 一致,snprintf 语义 --- */ { char buf[128]; int r = meow_strf(buf, sizeof buf, "common.ok"); CHECK(r == (int)strlen("确定") && strcmp(buf, "确定") == 0, "T6 meow_strf zh common.ok = 确定"); meow_strf(buf, sizeof buf, "common.no_data"); CHECK(strcmp(buf, meow_str("common.no_data")) == 0, "T6 meow_strf == meow_str"); CHECK(meow_strf(NULL, 10, "common.ok") == -1, "T6 meow_strf(out=NULL) = -1"); CHECK(meow_strf(buf, 0, "common.ok") == -1, "T6 meow_strf(outsz=0) = -1"); } /* --- T2 切 locale 即时生效(无需重初始化)--- * 用「未在覆盖表」的 id 证明 locale 即时切换(common.ok 等在 env 会被覆盖,留给 T4 验覆盖路径)。 */ meow_str_set_locale("en_us"); CHECK(strcmp(meow_str_locale(), "en_us") == 0, "T2 locale = en_us"); CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T2 en common.cancel = Cancel"); CHECK(strcmp(meow_str("common.no_data"), "No data") == 0, "T2 en common.no_data = No data"); CHECK(strcmp(meow_str("ime.space"), "Space") == 0, "T2 en ime.space = Space"); /* --- T4 TF 覆盖优先级(仅 env 模式)--- */ if (strcmp(mode, "env") == 0) { /* ① 覆盖内嵌当前语言(en_us*下 common.ok 应是覆盖值"确认OK"而非内嵌"OK") */ CHECK(strcmp(meow_str("common.ok"), "确认OK") == 0, "T4 @en override 覆盖 common.ok"); /* ② 覆盖自定义 id(内嵌无 → 覆盖命中) */ CHECK(strcmp(meow_str("common.custom"), "自定义覆盖") == 0, "T4 @en override 自定义 id"); /* ③ '=' 两侧带空格也可解析 */ CHECK(strcmp(meow_str("common.loading"), "加载中覆盖") == 0, "T4 @en override 带空格行"); /* ④ 未覆盖 id 回落内嵌当前语言 */ CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T4 @en 未覆盖 → 内嵌 en"); CHECK(strcmp(meow_str("ime.clear"), "Clear") == 0, "T4 @en ime.clear 未覆盖 → 内嵌 en"); /* ⑤ 自定义 id 在 zh(无 zh_cn.txt,覆盖表清空)→ 内嵌无 → 原样返回 id */ meow_str_set_locale("zh_cn"); CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T4 @zh 覆盖清除 → 内嵌 zh"); CHECK(strcmp(meow_str("common.custom"), "common.custom") == 0, "T4 @zh 自定义 id 原样回退"); meow_str_set_locale("en_us"); /* 回到 en 供 T5 */ } /* --- T5 查找正确性:一批 id 的 zh/en 逐一断言(仅 tmp 模式——无覆盖干扰, * 才能验证内嵌表本身;env 模式的内嵌 vs 覆盖已由 T2/T4 覆盖,此处跳过多余断言)--- */ if (strcmp(mode, "tmp") == 0) { const char *ids[] = { "common.ok", "common.cancel", "common.back", "common.confirm", "common.loading", "common.no_data", "common.retry_fail", "common.tap_ok_edit", "ime.space", "ime.enter", "ime.esc", "ime.clear", "ime.pinyin_mode", "ime.no_candidate" }; meow_str_set_locale("zh_cn"); for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) { if (expect(ids[i], 0)) { char msg[128]; snprintf(msg, sizeof msg, "T5 zh %s == 期望", ids[i]); CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 0)) == 0, msg); } } meow_str_set_locale("en_us"); for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) { if (expect(ids[i], 1)) { char msg[128]; snprintf(msg, sizeof msg, "T5 en %s == 期望", ids[i]); CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 1)) == 0, msg); } } } if (tmpdir) free(tmpdir); if (g_fail) { printf("\nRESULT: FAIL (%d)\n", g_fail); return 1; } printf("\nRESULT: ALL PASS\n"); return 0; }