/* test_bm_fw.c — 宿主单测:meow_bootmenu 的亮度决策(M23 亮度单一权威源)。 * * 覆盖真机 0.8.46 两个症状的判定矩阵: * ① 开机菜单/选单亮度 = 面板继承值 60(用户设置 1)—— 选单路径 bootmenu_settings_brightness() * ② HibyOS 亮/灭屏瞬间爆亮 —— supervisor 起始 saved、扫描窗保底、热键熄屏恢复三处决策 * * 编译(build_bootmenu.sh host;与 meow_watchdog 的 test_wd_fw.c 同款 include 打法): * gcc -Wall -Wextra -Os -I../meow_launcherd -DBM_NO_MAIN \ * -Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \ * test_bm_fw.c ../meow_launcherd/meow_touch.c -o test_bm_fw * 运行:./test_bm_fw;全 PASS -> exit 0。 * env MEOW_BACKLIGHT / MEOW_SETTINGS_FILE 把背光与 settings 指到临时文件(真机无这两个 env, * 走 /sys 与 /usr/data 默认路径),因此这里的读写断言就是真机同一条代码路径。 */ #define BM_NO_MAIN #include "meow_bootmenu.c" #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); } } /* 临时目录根:优先 $TMPDIR(Termux/Android 上 /tmp 属 shell 用户、本进程不可写), * 否则 /tmp(Linux 构建容器)——与 test_wd_fw.c 一致。 */ static const char *tmp_root(void) { const char *t = getenv("TMPDIR"); return (t && t[0]) ? t : "/tmp"; } static void put_file(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 int get_file_int(const char *path) { FILE *f = fopen(path, "r"); int v = -1; if (!f) return -2; if (fscanf(f, "%d", &v) != 1) v = -1; fclose(f); return v; } /* ---- A) supervisor 起始 saved_brightness:settings 是唯一权威 ---- * 真机 bug:settings display.brightness=1,面板被播放器写成 60 → 旧代码 saved=60(>60→60 的 * 硬钉同宗),随后扫描窗保底/熄屏恢复把 60 写回 → 亮/灭屏爆亮。新语义必须取 1。 */ static void test_pick_saved(void) { const char *src = NULL; int v; v = bm_bri_pick_saved(1, 60, 1, 101, &src); /* ← 真机症状本体 */ check("saved: settings 1 wins over panel 60", v == 1 && !strcmp(src, "settings"), 1); v = bm_bri_pick_saved(1, 0, 1, 101, &src); /* 播放器已把面板写成 0 */ check("saved: settings 1 when panel dark", v == 1 && !strcmp(src, "settings"), 1); v = bm_bri_pick_saved(7, 7, 1, 101, &src); check("saved: settings 7", v == 7 && !strcmp(src, "settings"), 1); /* settings 不可读(降级路径):沿用面板读数,夹进 [brightness_min, max_brightness](M23 新语义) */ v = bm_bri_pick_saved(-1, 60, 1, 101, &src); check("saved: fallback keeps panel 60", v == 60 && !strcmp(src, "panel"), 1); v = bm_bri_pick_saved(-1, 80, 1, 101, &src); /* 用户真设 80:不再被旧 `>60→60` 顶成 60 */ check("saved: fallback keeps 80 (old code pinned 60)", v == 80 && !strcmp(src, "panel"), 1); v = bm_bri_pick_saved(-1, 200, 1, 101, &src); /* 越界读数夹到 max_brightness */ check("saved: fallback clamps 200 -> max 101", v == 101 && !strcmp(src, "panel"), 1); v = bm_bri_pick_saved(-1, 37, 1, 32, &src); /* 面板上限 32 时按 32 */ check("saved: fallback clamps to max_brightness 32", v == 32 && !strcmp(src, "panel"), 1); v = bm_bri_pick_saved(-1, 2, 6, 101, &src); /* 低于 brightness_min 时抬到 min(防黑屏) */ check("saved: fallback clamps up to brightness_min 6", v == 6 && !strcmp(src, "panel"), 1); v = bm_bri_pick_saved(0, 3, 1, 101, &src); /* 非正值按住「未知」处理 */ check("saved: user<=0 treated unknown", v == 3 && !strcmp(src, "panel"), 1); /* settings 与面板都不可知且面板黑:安全低亮,绝不再写 60 */ v = bm_bri_pick_saved(-1, 0, 1, 101, &src); check("saved: dark + no settings -> safe-low", v == BM_BRI_SAFE_LOW && v != 60 && v > 0 && !strcmp(src, "safe-low"), 1); v = bm_bri_pick_saved(-1, -1, 1, 0, &src); /* 读失败 + max 读不到 */ check("saved: read-fail -> safe-low", v == BM_BRI_SAFE_LOW && !strcmp(src, "safe-low"), 1); /* max 读不到(0)时只夹下限,不臆造上限 */ v = bm_bri_pick_saved(-1, 90, 1, 0, &src); check("saved: unknown max keeps 90", v == 90 && !strcmp(src, "panel"), 1); } /* ---- A2) clamp 语义单测:取代旧的 `>60 → 60` 硬钉 ---- */ static void test_clamp(void) { check("clamp: in-range untouched", bm_bri_clamp(37, 1, 101), 37); check("clamp: above max -> max", bm_bri_clamp(200, 1, 101), 101); check("clamp: 80 stays 80 (was pinned 60)", bm_bri_clamp(80, 1, 101), 80); check("clamp: below min -> min", bm_bri_clamp(0, 3, 101), 3); check("clamp: mn<1 -> 1", bm_bri_clamp(0, 0, 101), 1); check("clamp: unknown max only floors", bm_bri_clamp(90, 1, 0), 90); } /* ---- B) 扫描窗保底:只在「播放器忙 + 面板暗 + 面板未下电」时补,补的是用户值 ---- */ static void test_floor_decide(void) { const char *why = NULL; int v; v = bm_bri_floor_decide(1, 0, 1, 0, 5000, &why); /* settings=1 + 扫描忙把面板写 0 */ check("floor: writes user value 1 (not 60)", v == 1 && !strcmp(why, "scan-floor"), 1); v = bm_bri_floor_decide(80, 0, 1, 0, 5000, &why); /* 降级路径 saved=80 照写 80(旧码钉 60) */ check("floor: degraded saved 80 floors at 80", v == 80 && !strcmp(why, "scan-floor"), 1); v = bm_bri_floor_decide(0, 0, 1, 0, 5000, &why); /* saved 未知 -> 安全低亮 */ check("floor: unknown saved -> safe-low", v == BM_BRI_SAFE_LOW && !strcmp(why, "scan-floor"), 1); v = bm_bri_floor_decide(1, 5, 1, 0, 5000, &why); /* 面板已亮:不动 */ check("floor: visible -> no write", v == 0 && !strcmp(why, "visible"), 1); v = bm_bri_floor_decide(1, 0, 0, 0, 5000, &why); /* ← 用户主动熄屏(播放器不忙):不动 */ check("floor: user screen-off (idle) -> no write", v == 0 && !strcmp(why, "idle-blank"), 1); v = bm_bri_floor_decide(1, 0, 1, 1, 5000, &why); /* 面板已下电(bl_power>0):不动 */ check("floor: panel powered off -> no write", v == 0 && !strcmp(why, "panel-off"), 1); v = bm_bri_floor_decide(1, 0, 1, 0, 0, &why); /* 90s 窗口结束:放手 */ check("floor: window over -> no write", v == 0 && !strcmp(why, "window-over"), 1); v = bm_bri_floor_decide(1, 0, 1, 0, -1, &why); check("floor: negative window -> no write", v == 0 && !strcmp(why, "window-over"), 1); } /* ---- C) 热键冻结前的熄屏恢复值:面板真实值 > settings saved > 安全低亮 ---- */ static void test_hotkey_saved(void) { const char *src = NULL; int v; v = bm_bri_hotkey_saved(1, 0, 1, 101, &src); /* 电源已熄屏:恢复用户值 1,不再回 60 */ check("hotkey: blank -> saved (settings 1)", v == 1 && !strcmp(src, "saved"), 1); v = bm_bri_hotkey_saved(60, 0, 1, 101, &src); /* 降级 saved=60(settings 不可读)时才用 60 */ check("hotkey: blank -> degraded saved 60", v == 60 && !strcmp(src, "saved"), 1); v = bm_bri_hotkey_saved(1, 77, 1, 101, &src); /* 面板亮着:跟随面板(HibyOS 里改过亮度) */ check("hotkey: panel-now wins", v == 77 && !strcmp(src, "panel-now"), 1); v = bm_bri_hotkey_saved(1, 200, 1, 101, &src); /* 越界值 clamp 到 max_brightness */ check("hotkey: panel-now clamped to max", v == 101 && !strcmp(src, "panel-now"), 1); v = bm_bri_hotkey_saved(1, 1, 6, 101, &src); /* 低于 brightness_min 抬到 min */ check("hotkey: panel-now raised to min 6", v == 6 && !strcmp(src, "panel-now"), 1); v = bm_bri_hotkey_saved(0, 0, 1, 101, &src); check("hotkey: nothing known -> safe-low", v == BM_BRI_SAFE_LOW && !strcmp(src, "safe-low"), 1); } /* ---- D) settings.conf 解析(与 watchdog 同源同 clamp)+ 打开重试 ---- */ static void test_settings_parse(void) { char path[600]; snprintf(path, sizeof path, "%s/bmsetXXXXXX", tmp_root()); int fd = mkstemp(path); if (fd < 0) { g_fail++; printf("FAIL settings: mkstemp\n"); return; } close(fd); setenv("MEOW_SETTINGS_FILE", path, 1); put_file(path, "# meow-settings 1\ndisplay.brightness = 1\ndisplay.brightness_min = 1\n" "audio.volume = 10\n"); check("settings: device file -> 1", bootmenu_settings_brightness(NULL), 1); put_file(path, "display.brightness = 0\ndisplay.brightness_min = 3\n"); int mn = 0; check("settings: clamp up to min", bootmenu_settings_brightness(&mn), 3); check("settings: brightness_min parsed", mn, 3); put_file(path, "display.brightness = 200\ndisplay.brightness_min = 1\n"); check("settings: clamp down to 100", bootmenu_settings_brightness(NULL), 100); put_file(path, "audio.volume = 10\n"); check("settings: missing key -> 100", bootmenu_settings_brightness(NULL), 100); unlink(path); unsigned long long t0 = now_ms(); int r = bootmenu_settings_brightness(NULL); unsigned long long dt = now_ms() - t0; check("settings: missing -> -1", r, -1); check("settings: open retried (>=15ms)", dt >= 15, 1); unsetenv("MEOW_SETTINGS_FILE"); } /* ---- E) 端到端:真的读写一个「背光文件」,复现两个真机场景 ---- * 1) settings=1、播放器把面板写成 60 → supervisor 起始 saved 必须是 1,扫描窗保底写 1(不是 60) * 2) 用户按电源熄屏(播放器不忙)→ 保底决策返回 0,面板保持 0(不被抢亮 = 熄屏不爆亮) */ static void test_e2e_backlight(void) { char bl[600], st[600]; snprintf(bl, sizeof bl, "%s/bmblXXXXXX", tmp_root()); snprintf(st, sizeof st, "%s/bmstXXXXXX", tmp_root()); int fd = mkstemp(bl); if (fd < 0) { g_fail++; printf("FAIL e2e: mkstemp bl\n"); return; } close(fd); fd = mkstemp(st); if (fd < 0) { g_fail++; printf("FAIL e2e: mkstemp st\n"); unlink(bl); return; } close(fd); setenv("MEOW_BACKLIGHT", bl, 1); setenv("MEOW_SETTINGS_FILE", st, 1); put_file(st, "display.brightness = 1\ndisplay.brightness_min = 1\n"); put_file(bl, "60\n"); /* 播放器启动画面写的 60 */ int mn_e2e = 1; int user = bootmenu_settings_brightness(&mn_e2e); int panel = read_brightness(); const char *src = "?"; int saved = bm_bri_pick_saved(user, panel, mn_e2e, 101, &src); check("e2e: panel file read 60", panel, 60); check("e2e: saved from settings = 1", saved, 1); /* 扫描窗:播放器忙把背光写 0 → 保底写用户值 1 */ put_file(bl, "0\n"); const char *why = NULL; int floor = bm_bri_floor_decide(saved, read_brightness(), 1, 0, 5000, &why); if (floor > 0) write_brightness(floor); check("e2e: scan floor writes 1 (not 60)", get_file_int(bl), 1); /* 用户主动熄屏(不忙)→ 保底不写,面板保持 0 */ put_file(bl, "0\n"); floor = bm_bri_floor_decide(saved, read_brightness(), 0, 0, 5000, &why); if (floor > 0) write_brightness(floor); check("e2e: user screen-off stays dark", get_file_int(bl), 0); /* 热键确认框:面板黑着 → 按 saved(用户值 1)点亮,不是 60 */ int hk = bm_bri_hotkey_saved(saved, read_brightness(), mn_e2e, 101, &src); write_brightness(hk); check("e2e: confirm box lights at 1", get_file_int(bl), 1); /* settings 被改:saved 不再跟旧值(每次开机重读 settings) */ put_file(st, "display.brightness = 5\ndisplay.brightness_min = 1\n"); src = "?"; saved = bm_bri_pick_saved(bootmenu_settings_brightness(NULL), read_brightness(), mn_e2e, 101, &src); check("e2e: saved follows new settings 5", saved, 5); unlink(bl); unlink(st); unsetenv("MEOW_BACKLIGHT"); unsetenv("MEOW_SETTINGS_FILE"); } int main(void) { test_pick_saved(); test_clamp(); test_floor_decide(); test_hotkey_saved(); test_settings_parse(); test_e2e_backlight(); printf("\n%d passed, %d failed\n", g_pass, g_fail); return g_fail ? 1 : 0; }