/* test_bm_bootdefault.c — 宿主自测:启动菜单「默认项从哪来」+ 一次性指示(M69)。 * * 覆盖的验收推演: * ① 文件全不存在 → 编译默认 MeowOS(DEF_BOOT_DEFAULT=0); * ② /data/meow_boot_default(设置里的「默认系统」)压过 meow.conf 兜底; * ③ 一次性指示 /data/meow_boot_once 压过持久默认,且**读一次即删**; * ④ 删掉之后的下一次开机回落持久默认; * ⑤ 内容非法 → 忽略并删除; * ⑥ 删不掉时「能用就用、不许开机失败」,并尽量清空内容让下一次开机不生效。 * * 编译/运行(Termux 上 /tmp 不可写,带 TMPDIR 最稳): * mkdir -p "$HOME/tmp/m69test" * cc -Wall -Wextra -Os -I../meow_launcherd -Wno-unused-function -Wno-unused-parameter \ * -Wno-unused-variable test_bm_bootdefault.c ../meow_launcherd/meow_touch.c \ * -o "$HOME/tmp/m69test/bm_bt" && * TMPDIR=$HOME/tmp/m69test "$HOME/tmp/m69test/bm_bt" # 全 PASS -> exit 0 */ #define BM_NO_MAIN #include "meow_bootmenu.c" #include #include #include #include #include static int g_pass = 0, g_fail = 0; static void check(const char *name, int got, int want) { if (got == want) { g_pass++; printf("PASS %s\n", name); } else { g_fail++; printf("FAIL %s: got %d want %d\n", name, got, want); } } static char g_dir[512], g_conf[600], g_def[600], g_once[600]; /* 临时目录:$TMPDIR → /tmp → $HOME/tmp/m69test(Termux 上 /tmp 属别的用户,不可写)。 */ static int mk_tmp_dir(void) { const char *cand[3]; char homebuf[512] = ""; int n = 0; const char *t = getenv("TMPDIR"); if (t && t[0]) cand[n++] = t; cand[n++] = "/tmp"; const char *home = getenv("HOME"); if (home && home[0]) { snprintf(homebuf, sizeof homebuf, "%s/tmp/m69test", home); mkdir(homebuf, 0700); cand[n++] = homebuf; } for (int i = 0; i < n; i++) { snprintf(g_dir, sizeof g_dir, "%s/m69_bm_XXXXXX", cand[i]); if (!mkdtemp(g_dir)) continue; snprintf(g_conf, sizeof g_conf, "%s/meow.conf", g_dir); snprintf(g_def, sizeof g_def, "%s/boot_default", g_dir); snprintf(g_once, sizeof g_once, "%s/boot_once", g_dir); g_conf_path = g_conf; g_boot_default_path = g_def; g_boot_once_path = g_once; return 0; } return -1; } static void put(const char *path, const char *body) { FILE *f = fopen(path, "w"); if (!f) { g_fail++; printf("FAIL cannot write %s\n", path); return; } fputs(body, f); fclose(f); } static void reset(void) { unlink(g_conf); unlink(g_def); unlink(g_once); } static long file_size(const char *path) { struct stat st; if (stat(path, &st) != 0) return -1; return (long)st.st_size; } /* ---- ① 什么都没有 → 编译默认 MeowOS ---- */ static void case_compile_default(void) { printf("== no files -> compile default ==\n"); reset(); load_config(); check("compile default is MeowOS (0)", cfg_boot_default, 0); check("DEF_BOOT_DEFAULT == 0", DEF_BOOT_DEFAULT, 0); } /* ---- ② 持久默认 = 设置里的「默认系统」,压过 conf 兜底 ---- */ static void case_persistent_is_authoritative(void) { printf("== persistent boot default ==\n"); reset(); put(g_def, "1\n"); load_config(); check("persistent=1 -> Hiby", cfg_boot_default, 1); put(g_conf, "boot_default=0\n"); load_config(); check("persistent=1 overrides conf boot_default=0", cfg_boot_default, 1); put(g_def, "0\n"); load_config(); check("persistent=0 -> MeowOS", cfg_boot_default, 0); } /* ---- ③④ 一次性指示:优先、读一次即删、下次回落持久默认 ---- */ static void case_once_overrides_and_is_consumed(void) { printf("== one-shot boot_once ==\n"); reset(); put(g_def, "0\n"); /* 用户设的默认 = MeowOS */ put(g_once, "1\n"); /* 「切换到 HibyOS」= 这一次去 Hiby */ load_config(); check("once=1 overrides persistent=0", cfg_boot_default, 1); check("once file removed after use", access(g_once, F_OK), -1); load_config(); /* 模拟下一次开机 */ check("next boot falls back to persistent 0", cfg_boot_default, 0); } /* ---- ⑤ 非法内容:忽略 + 删除(不留垃圾反复干扰) ---- */ static void case_invalid_once(void) { printf("== invalid boot_once ==\n"); reset(); put(g_def, "1\n"); put(g_once, "garbage\n"); load_config(); check("invalid once ignored (persistent=1 wins)", cfg_boot_default, 1); check("invalid once removed", access(g_once, F_OK), -1); } /* ---- ⑥ 删不掉:能用就用(不许开机失败),并尽量清空让下次不生效 ---- * 构造「目录不可写 + 文件可写」:unlink 需要目录写权限 → 失败;O_TRUNC 只需文件写权限 → 成功。 * (以 root 跑时 unlink 会成功,本用例自动 SKIP。) */ static void case_unlink_failure(void) { printf("== unlink failure is survivable ==\n"); reset(); put(g_def, "0\n"); put(g_once, "1\n"); chmod(g_dir, 0555); if (unlink(g_once) == 0) { /* 探测:权限没生效(多半是 root) */ chmod(g_dir, 0755); put(g_once, "1\n"); printf("SKIP unlink-failure case (unlink succeeded, uid=%d)\n", (int)getuid()); return; } load_config(); check("unremovable once still used this boot", cfg_boot_default, 1); check("once content truncated to empty", (int)file_size(g_once), 0); chmod(g_dir, 0755); load_config(); /* 模拟下一次开机 */ check("next boot ignores emptied once (persistent=0)", cfg_boot_default, 0); } /* ---- 组件契约:两个组件必须用同一对生产路径 ---- */ static void case_prod_paths(void) { printf("== production paths ==\n"); check("BOOT_DEFAULT_FILE", strcmp(BOOT_DEFAULT_FILE, "/data/meow_boot_default"), 0); check("BOOT_ONCE_FILE", strcmp(BOOT_ONCE_FILE, "/data/meow_boot_once"), 0); check("CONF_FILE on TF card", strcmp(CONF_FILE, "/usr/data/mnt/sd_0/MeowAPPs/meow.conf"), 0); } int main(void) { if (mk_tmp_dir() != 0) { printf("FATAL: no writable tmp dir\n"); return 1; } case_compile_default(); case_persistent_is_authoritative(); case_once_overrides_and_is_consumed(); case_invalid_once(); case_unlink_failure(); case_prod_paths(); reset(); rmdir(g_dir); if (g_fail) { printf("\nBOOTMENU BOOTDEFAULT TEST: %d FAIL (%d pass)\n", g_fail, g_pass); return 1; } printf("\nBOOTMENU BOOTDEFAULT TEST: ALL PASS (%d checks)\n", g_pass); return 0; }