Files
MeowOS-For-R3P2/patch_v2/bootmenu/test_bm_fw.c
T
AskaEth 8cd6e21cad 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。
2026-11-11 18:42:41 +08:00

312 lines
16 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 态 = 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, 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, 0, 5000, -1, &why); /* saved 未知 -> 安全低亮 */
check("floor: unknown saved -> safe-low", v == BM_BRI_SAFE_LOW && !strcmp(why, "scan-floor"), 1);
/* 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, 1, 5000, -1, &why); /* 面板已下电(bl_power>0):不动 */
check("floor: panel powered off -> no write", v == 0 && !strcmp(why, "panel-off"), 1);
/* 电源键刚按下(用户主动熄屏的第二道保险):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, 0, -1, -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(), 0, 5000, -1, &why);
if (floor > 0) write_brightness(floor);
check("e2e: scan floor writes 1 (not 60)", get_file_int(bl), 1);
/* 用户主动熄屏(播放器已 FBIOBLANK,bl_power>0)→ 保底不写,面板保持 0 */
put_file(bl, "0\n");
floor = bm_bri_floor_decide(saved, read_brightness(), 1, 5000, -1, &why);
if (floor > 0) write_brightness(floor);
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);
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);
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;
}