Files
MeowOS-For-R3P2/patch_v2/bootmenu/test_bm_fw.c
T
AskaEth a9281c2d64 fix(bootmenu): M23 评审 r2 — 电源键锁存取代「3s 抑制」,缺键视同不可信
P2-2(r2):删除「电源键按下后 3s 内不补」的定时抑制 —— 它到期就会把用户刚熄的屏点亮
(窗内可重复),且该行为被单测固定。改为**锁存**:扫描窗内一看到电源键按下边沿就置
scan_pwr_seen=1,本次窗口内此后一律不补(why="power-key-latched"),窗口结束才清零。
理由:Bug 4 保护的是「无人值守时媒体扫描导致黑屏几分钟」;用户已按键介入,需要亮屏时
自己再按一次即可。锁存同时覆盖「播放器只写 brightness=0 不改 bl_power」与「HibyOS 自动
超时熄屏」两种残余。

bl_power 门保留但降级为纯保险,注释改成诚实表述:据 patch_v2/meow_launcherd/backlight.c:90-95
实测,本机播放器熄屏(brightness 0 + FBIOBLANK 4)后 bl_power **仍为 0**,该门在播放器熄屏
场景不生效;真正的门是电源键锁存。(上一轮注释里的「播放器灭屏会置 bl_power>0」是反向推断,
已删。)

顺手关掉评审指出的竞态:亮度保底判定块从主循环前半(keypoll 之前)移到电源键边沿更新之后,
电源按下与播放器写 0 落在同一 poll 间隙时不会再出现一轮「没看到电源键就补亮」。

非阻塞 1(采纳):settings 文件存在但缺 display.brightness 键 → 返回 -1(视同不可信),
不再走 launcher 设置表的 101→100;否则扫描窗待机补 100 比老 bug 的 60 更亮。

单测(52 → 56 条):
- 删「power key expired -> floors」(把 bug 固定成期望的那条),换「latch 在窗口内永不过期」
  「下一轮窗口(锁存清零)恢复正常补」「latched 时面板暗也不写、why=power-key-latched」;
- 「missing key -> 100」改「missing key -> -1 (untrusted)」,并补 e2e「缺键 → saved=10 不是 100」;
- 两条名字去 degraded:`floor: saved 80 (from settings) floors at 80`、
  `hotkey: blank -> saved 60 (settings value)`(降级路径已封顶 10,80/60 只可能来自 settings)。

只交源码:不含二进制、不改 fw_content.conf、不动 build_bootmenu.sh。
2026-11-11 18:42:41 +08:00

335 lines
17 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_bm_fw.c — 宿主单测:meow_bootmenu 的亮度决策(M23 亮度单一权威源)。
*
* 覆盖真机 0.8.46 两个症状的判定矩阵:
* ① 开机菜单/选单亮度 = 面板继承值 60(用户设置 1)—— 选单路径 bootmenu_settings_brightness()
* ② HibyOS 亮/灭屏瞬间爆亮 —— supervisor 起始 saved、扫描窗保底、热键熄屏恢复三处决策
*
* 编译/运行(Termux 上 /tmp 不可写,**必须带 TMPDIR**,否则 mkstemp 两条用例会 FAIL):
* TMPDIR=$HOME/tmp/m23test mkdir -p "$HOME/tmp/m23test"
* gcc -Wall -Wextra -Os -I../meow_launcherd -Wno-unused-function \
* -Wno-unused-parameter -Wno-unused-variable \
* test_bm_fw.c ../meow_launcherd/meow_touch.c -o "$HOME/tmp/m23test/test_bm_fw"
* TMPDIR=$HOME/tmp/m23test "$HOME/tmp/m23test/test_bm_fw" # 全 PASS -> exit 0
* (TMPDIR 未设时本文件会自己退到可写的 /tmp 或 $HOME/tmp,但显式给最稳。)
* env MEOW_BACKLIGHT / MEOW_SETTINGS_FILE 把背光与 settings 指到临时文件(真机无这两个 env,
* 走 /sys 与 /usr/data 默认路径),因此这里的读写断言就是真机同一条代码路径。
*/
#define BM_NO_MAIN
#include "meow_bootmenu.c"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
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 属别的用户、本进程不可写)
* ② /tmp(Linux 构建容器)
* ③ $HOME/tmp/m23test(两者都不可写时的兜底,自动 mkdir)
* 评审 r1 反馈:只靠 $TMPDIR 会让没设环境变量的复跑出现 2 条 mkstemp FAIL,故这里自愈。 */
static const char *tmp_root(void)
{
static char buf[512];
if (buf[0]) return buf;
const char *t = getenv("TMPDIR");
if (t && t[0]) { snprintf(buf, sizeof buf, "%s", t); return buf; }
const char *home = getenv("HOME");
snprintf(buf, sizeof buf, "%s", "/tmp");
char probe[560];
snprintf(probe, sizeof probe, "%s/.bm_probeXXXXXX", buf);
int fd = mkstemp(probe);
if (fd >= 0) { close(fd); unlink(probe); return buf; }
if (home && home[0]) {
snprintf(buf, sizeof buf, "%s/tmp/m23test", home);
mkdir(buf, 0700);
return buf;
}
return buf; /* 实在不行也返回 /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 不可读(降级路径):**不**原样采信面板瞬时值,封顶 SAFE_LOW=10(P2-1 修复)。
* 这是 M2.9 闪屏防护的正确形态:宁可用安全低亮,也不把「启动瞬间的面板值」当用户亮度回放。 */
v = bm_bri_pick_saved(-1, 60, 1, 101, &src);
check("saved: fallback caps panel 60 -> SAFE_LOW 10",
v == BM_BRI_SAFE_LOW && v == 10 && !strcmp(src, "panel-capped"), 1);
v = bm_bri_pick_saved(-1, 80, 1, 101, &src); /* 用户可能真设 80,但降级时不敢信面板 → 10 */
check("saved: fallback caps 80 -> SAFE_LOW (old bug shape)",
v == BM_BRI_SAFE_LOW && !strcmp(src, "panel-capped"), 1);
v = bm_bri_pick_saved(-1, 200, 1, 101, &src); /* 越界(播放器瞬时 max)同样封顶 */
check("saved: fallback caps 200 -> SAFE_LOW",
v == BM_BRI_SAFE_LOW && !strcmp(src, "panel-capped"), 1);
v = bm_bri_pick_saved(-1, 90, 1, 0, &src); /* max 读不到也不抬高 */
check("saved: fallback caps 90 -> SAFE_LOW (unknown max)",
v == BM_BRI_SAFE_LOW && !strcmp(src, "panel-capped"), 1);
/* 比 SAFE_LOW 更暗的真实值保留(用户刻意调很暗,不要替用户调亮) */
v = bm_bri_pick_saved(-1, 3, 1, 101, &src);
check("saved: fallback keeps darker 3", v == 3 && !strcmp(src, "panel-capped"), 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-capped"), 1);
v = bm_bri_pick_saved(0, 3, 1, 101, &src); /* 非正值按住「未知」处理 */
check("saved: user<=0 treated unknown", v == 3 && !strcmp(src, "panel-capped"), 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);
/* 与 M20 watchdog 同一安全下限(同一系统不该有两个安全值) */
check("saved: SAFE_LOW aligned with WD_BRI_SAFE_LOW (10)", BM_BRI_SAFE_LOW, 10);
}
/* ---- 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;
/* 触发条件回到 pre-M23:面板暗就补(不依赖 /proc 的 D 态 = 评审 r1 P2-2 硬要求) */
v = bm_bri_floor_decide(1, 0, 0, 5000, 0, &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, 0, 5000, 0, &why); /* saved=80 只可能来自 settings */
check("floor: saved 80 (from settings) floors at 80", v == 80 && !strcmp(why, "scan-floor"), 1);
v = bm_bri_floor_decide(0, 0, 0, 5000, 0, &why); /* saved 未知 -> 安全低亮 */
check("floor: unknown saved -> safe-low", v == BM_BRI_SAFE_LOW && !strcmp(why, "scan-floor"), 1);
/* Bug 4 防退化回归用例(评审 r1 P2-2):面板暗就必须能补,不能有任何「播放器看起来不忙」
* 的前提 —— 旧 busy 门会让这条变成 no-write(黑屏几分钟)。 */
v = bm_bri_floor_decide(7, 0, 0, 89999, 0, &why);
check("floor: Bug4 regression — dark+unlatched always floors", v == 7 &&
!strcmp(why, "scan-floor"), 1);
v = bm_bri_floor_decide(1, 5, 0, 5000, 0, &why); /* 面板已亮:不动 */
check("floor: visible -> no write", v == 0 && !strcmp(why, "visible"), 1);
v = bm_bri_floor_decide(1, 0, 1, 5000, 0, &why); /* bl_power>0(纯保险):不动 */
check("floor: panel powered off -> no write", v == 0 && !strcmp(why, "panel-off"), 1);
/* 评审 r2 P2-2 核心:电源键**锁存**。窗内按过电源键 → 本次窗口内此后一律不补,
* 不再「3s 到期就把用户刚熄的屏点亮」。 */
v = bm_bri_floor_decide(1, 0, 0, 5000, 1, &why);
check("floor: power key latched -> no write", v == 0 && !strcmp(why, "power-key-latched"), 1);
/* 锁存不会过期:窗口剩 1ms 也不补(旧实现 3s 后就会补) */
v = bm_bri_floor_decide(1, 0, 0, 1, 1, &why);
check("floor: latch never expires inside window", v == 0 &&
!strcmp(why, "power-key-latched"), 1);
/* 下一轮窗口(锁存已清零)恢复正常补亮 */
v = bm_bri_floor_decide(1, 0, 0, 90000, 0, &why);
check("floor: new window (latch cleared) floors again", v == 1 &&
!strcmp(why, "scan-floor"), 1);
v = bm_bri_floor_decide(1, 0, 0, 0, 0, &why); /* 90s 窗口结束:放手 */
check("floor: window over -> no write", v == 0 && !strcmp(why, "window-over"), 1);
v = bm_bri_floor_decide(1, 0, 0, -1, 0, &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(降级已封顶 10) */
check("hotkey: blank -> saved 60 (settings value)", 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");
/* 评审 r2 非阻塞 1(采纳):「文件里没有这个键」与「文件读不到」同属不可信 →
* 返回 -1,而不是 launcher 设置表的默认 101→100(那会让扫描窗待机补到 100)。 */
int mn2 = 0;
check("settings: missing key -> -1 (untrusted)", bootmenu_settings_brightness(&mn2), -1);
check("settings: missing key still reports min", mn2, 1);
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(), 0, 5000, 0, &why);
if (floor > 0) write_brightness(floor);
check("e2e: scan floor writes 1 (not 60)", get_file_int(bl), 1);
/* bl_power>0(纯保险分支)→ 保底不写 */
put_file(bl, "0\n");
floor = bm_bri_floor_decide(saved, read_brightness(), 1, 5000, 0, &why);
if (floor > 0) write_brightness(floor);
check("e2e: user screen-off (panel-off) stays dark", get_file_int(bl), 0);
/* 评审 r2 核心场景:用户按电源熄屏(bl_power 仍 0)→ 锁存 → 本次窗口内**永久**不补,
* 不会再出现「3s 后自己亮回来」 */
put_file(bl, "0\n");
floor = bm_bri_floor_decide(saved, read_brightness(), 0, 5000, 1, &why);
if (floor > 0) write_brightness(floor);
check("e2e: user screen-off (power key latched) stays dark", get_file_int(bl), 0);
check("e2e: latched why", !strcmp(why, "power-key-latched"), 1);
/* 热键确认框:面板黑着 → 按 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);
/* 降级路径回归(评审 P2-1):settings 不可读 + 面板瞬时 60(播放器启动画面)
* → saved 只能是 SAFE_LOW=10,绝不能原样 60,否则扫描窗/确认框会把 60 写回去。 */
unlink(st);
put_file(bl, "60\n");
int mn_d = 1;
src = "?";
int dsaved = bm_bri_pick_saved(bootmenu_settings_brightness(&mn_d), read_brightness(),
mn_d, 101, &src);
check("e2e: settings unreadable -> saved SAFE_LOW (not 60)", dsaved, BM_BRI_SAFE_LOW);
check("e2e: degraded src=panel-capped", !strcmp(src, "panel-capped"), 1);
/* 评审 r2 非阻塞 1(采纳):文件存在但缺 display.brightness 键 → 视同不可信 →
* saved 同样封顶 SAFE_LOW,不会补到 launcher 缺省的 100(比老 bug 的 60 更亮)。 */
put_file(st, "display.brightness_min = 1\n");
put_file(bl, "60\n");
src = "?";
int msaved = bm_bri_pick_saved(bootmenu_settings_brightness(&mn_d), read_brightness(),
mn_d, 101, &src);
check("e2e: missing brightness key -> SAFE_LOW (not 100)", msaved, BM_BRI_SAFE_LOW);
check("e2e: missing key src=panel-capped", !strcmp(src, "panel-capped"), 1);
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;
}