fix(bootmenu): M23 评审 r1 — 修 P2-1 降级路径 + P2-2 Bug4 防护退化

P2-1(降级路径把老 bug 请回来):settings 不可读时不再原样采信面板瞬时值。
  bm_bri_pick_saved 的 panel 分支改为 min(面板, BM_BRI_SAFE_LOW),src 改名 panel-capped;
  BM_BRI_SAFE_LOW 20 → 10,与 meow_watchdog 的 WD_BRI_SAFE_LOW 对齐(同一系统同一个安全下限)。
  这样 settings 不可读 + 面板 60(播放器启动画面值)时 saved 是 10,扫描窗/确认框不会再写 60。
  注释改掉「与 wd_bri_decide 同构」的断言:方向相同,但 bootmenu 没有运行期亮度快照,
  降级走安全下限而不是 M20 的「快照 → cur>0 不写」链。
  单测:把原先钉死 bug 的 "fallback keeps panel 60" 改成 "caps panel 60 -> SAFE_LOW 10",
  并补「比 SAFE_LOW 更暗的真实值保留」「SAFE_LOW 与 M20 对齐」两个断言。

P2-2(busy 门可能让 Bug 4 防护变成空操作):删除 /proc D 态门,触发条件回到 pre-M23 的
  「窗口内 + 面板暗(bri<=0)+ 面板未下电(bl_power<=0)」。
  避免「熄屏瞬间爆亮」改由两条可观测信号承担:bl_power>0(播放器 FBIOBLANK)与
  「电源键按下后 3s 内不补」(supervisor 已在 10ms 轮询 evdev,记录按下边沿)。
  单测补 Bug4 防退化回归用例(窗口剩余 89999ms、面板暗、未下电 → 必须补)。

评审非阻塞三条:
- 日志节流:同一原因 ≥2s 一条、原因跃迁也 ≥1s(防 visible↔scan-floor 每 400ms 交替刷屏,
  90s 窗口最坏 ~90 行);日志字段 busy= 换成 pwr_ago=。
- 单测复跑配方补 TMPDIR;tmp_root() 增加自愈(TMPDIR → 可写 /tmp → $HOME/tmp/m23test)。
- 验收配方(3 次亮/灭屏 + 进一次开机菜单 + 期望日志行)写进报文。

只交源码:不含二进制、不改 fw_content.conf、不动 build_bootmenu.sh。
This commit is contained in:
AskaEth committed 2026-11-11 18:42:41 +08:00
1 parent bc7bafa5dc
commit 8cd6e21cad
2 files changed
+162 -77

No files matched your search

+92 -36
View File
@@ -4,11 +4,13 @@
* ① 开机菜单/选单亮度 = 面板继承值 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。
* 编译/运行(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 默认路径),因此这里的读写断言就是真机同一条代码路径。
*/
@@ -19,6 +21,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
static int g_pass = 0, g_fail = 0;
@@ -28,12 +31,29 @@ static void check(const char *name, int got, int want)
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 一致。 */
/* 临时目录根(返回可写目录;结果缓存在静态缓冲里):
* ① $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");
return (t && t[0]) ? t : "/tmp";
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)
@@ -69,19 +89,27 @@ static void test_pick_saved(void)
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 新语义) */
/* settings 不可读(降级路径):**不**原样采信面板瞬时值,封顶 SAFE_LOW=10(P2-1 修复)。
* 这是 M2.9 闪屏防护的正确形态:宁可用安全低亮,也不把「启动瞬间的面板值」当用户亮度回放。 */
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);
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"), 1);
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);
@@ -89,9 +117,8 @@ static void test_pick_saved(void)
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);
/* 与 M20 watchdog 同一安全下限(同一系统不该有两个安全值) */
check("saved: SAFE_LOW aligned with WD_BRI_SAFE_LOW (10)", BM_BRI_SAFE_LOW, 10);
}
/* ---- A2) clamp 语义单测:取代旧的 `>60 → 60` 硬钉 ---- */
@@ -111,22 +138,34 @@ 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 */
/* 触发条件回到 pre-M23:面板暗 + 未下电就补(不依赖 /proc 的 D 态 = P2-2 硬要求) */
v = bm_bri_floor_decide(1, 0, 0, 5000, -1, &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) */
v = bm_bri_floor_decide(80, 0, 0, 5000, -1, &why); /* 降级 saved=80 照写 */
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 未知 -> 安全低亮 */
v = bm_bri_floor_decide(0, 0, 0, 5000, -1, &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); /* 面板已亮:不动 */
/* Bug 4 防退化回归用例(评审 P2-2):只要面板暗+未下电就必须能补,不能有任何
* 「播放器看起来不忙」的前提 —— 旧 busy 门会让这两条变成 no-write(黑屏几分钟)。 */
v = bm_bri_floor_decide(7, 0, 0, 89999, -1, &why);
check("floor: Bug4 regression — dark+unpowered always floors", v == 7 &&
!strcmp(why, "scan-floor"), 1);
v = bm_bri_floor_decide(1, 5, 0, 5000, -1, &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):不动 */
v = bm_bri_floor_decide(1, 0, 1, 5000, -1, &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 窗口结束:放手 */
/* 电源键刚按下(用户主动熄屏的第二道保险):3s 内不补 */
v = bm_bri_floor_decide(1, 0, 0, 5000, 1200, &why);
check("floor: recent power key -> no write", v == 0 && !strcmp(why, "power-key"), 1);
v = bm_bri_floor_decide(1, 0, 0, 5000, BM_BRI_PWR_HOLD_MS, &why); /* 到期即恢复补 */
check("floor: power key expired -> floors", v == 1 && !strcmp(why, "scan-floor"), 1);
v = bm_bri_floor_decide(1, 0, 0, 5000, -1, &why); /* 本次开机没按过电源键 */
check("floor: no power edge ever -> floors", v == 1 && !strcmp(why, "scan-floor"), 1);
v = bm_bri_floor_decide(1, 0, 0, 0, -1, &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);
v = bm_bri_floor_decide(1, 0, 0, -1, -1, &why);
check("floor: negative window -> no write", v == 0 && !strcmp(why, "window-over"), 1);
}
@@ -213,18 +252,24 @@ static void test_e2e_backlight(void)
check("e2e: panel file read 60", panel, 60);
check("e2e: saved from settings = 1", saved, 1);
/* 扫描窗:播放器忙把背光写 0 → 保底写用户值 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);
int floor = bm_bri_floor_decide(saved, read_brightness(), 0, 5000, -1, &why);
if (floor > 0) write_brightness(floor);
check("e2e: scan floor writes 1 (not 60)", get_file_int(bl), 1);
/* 用户主动熄屏(不忙)→ 保底不写,面板保持 0 */
/* 用户主动熄屏(播放器已 FBIOBLANK,bl_power>0)→ 保底不写,面板保持 0 */
put_file(bl, "0\n");
floor = bm_bri_floor_decide(saved, read_brightness(), 0, 0, 5000, &why);
floor = bm_bri_floor_decide(saved, read_brightness(), 1, 5000, -1, &why);
if (floor > 0) write_brightness(floor);
check("e2e: user screen-off stays dark", get_file_int(bl), 0);
check("e2e: user screen-off (panel-off) stays dark", get_file_int(bl), 0);
/* 用户主动熄屏但 bl_power 没置位:电源键按下 1.2s 内也不补(第二道保险) */
put_file(bl, "0\n");
floor = bm_bri_floor_decide(saved, read_brightness(), 0, 5000, 1200, &why);
if (floor > 0) write_brightness(floor);
check("e2e: user screen-off (power key) stays dark", get_file_int(bl), 0);
/* 热键确认框:面板黑着 → 按 saved(用户值 1)点亮,不是 60 */
int hk = bm_bri_hotkey_saved(saved, read_brightness(), mn_e2e, 101, &src);
@@ -237,6 +282,17 @@ static void test_e2e_backlight(void)
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);
unlink(bl); unlink(st);
unsetenv("MEOW_BACKLIGHT");
unsetenv("MEOW_SETTINGS_FILE");