Files
MeowOS-For-R3P2/patch_v2/meow_watchdog/test_wd_fw.c
T
AskaEth 63b278e13e fix(watchdog+launcher+sdk): M40 电源键 time64 ABI + 锁屏/叠加残留链
- sdk/meow_time32: 抽出可独立链接的 o32 time32 取时/睡眠设施(不依赖 meow_ui.a,
  watchdog 等独立静态组件可直接链;可选 -DMEOW_TIME32_LIBC_OVERRIDES 接管 libc 入口)
- watchdog: now_ms/usleep 改走 time32(修复 held 变天文数字→每次单击都弹菜单);
  阈值读 power.longpress_menu_ms/forceoff_ms(缺省 3000/8000),删除
  PWR_TOGGLE_MS==PWR_MENU_MS;新增 wd_power.h 纯判定 + 宿主行为矩阵测试
- launcher: App 前台电源菜单补齐 §10.5(notif 全抑制 + 帧恢复 + SIGCONT 排空 +
  标记翻回前台);show_power_menu 恢复进入前背景帧(关闭后不再停留菜单画面);
  重绘挪到首轮按键采样之后;activity 标记排空窗 + 会话入口清理 + manual_off 去闩锁
- lock_screen: 电源改按下边沿 + 进场 300ms 才熄屏;电源熄屏退出也恢复底层帧;
  launcher 在 SIGCONT 前写 /tmp/meow_app_repaint,SDK present 消费并 dirty_full
- power_menu: 菜单内强关接 power.forceoff_ms(按 longpress 抵扣),日志不再谎称 8s
- 重编 meow_ui.a 并重链 meow_launcherd/ncm/files/hello/reader(picbrowser 无源码)
- fw_content.conf: 同步 launcherd/watchdog 新 md5;顺带修正 M35 遗留的 nyai 失效钉值

Host: test_wd_fw 66/66、test_overlay_gate 40/40、test_meow_time64/time/repaint PASS。
真机未验证(设备关机);未刷机、未写设备。
2026-11-10 22:51:00 +08:00

371 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_wd_fw.c — 宿主单测:meow_watchdog 的纯逻辑(M2.86 框架 App 判定 + M20 亮屏亮度 +
* M40 time32 取时/睡眠 + 电源阈值行为矩阵)。
*
* 编译:#include meow_watchdog.c(去 main 拿 wd_app_is_framework/json_str/from_pid、
* wd_bri_decide、wd_settings_brightness、wd_panel_off/on;wd_power.h 的纯判定)。
* gcc -Wall -Wextra -Os -DWd_NO_MAIN -Wno-unused-function -Wno-unused-parameter \
* test_wd_fw.c ../sdk/meow_time32.c -o test_wd_fw
* (meow_watchdog.c 含仅 main 使用的静态函数,宿主去 main 后须 -Wno-unused-function 静音;
* mips 目标构建不定义 WD_NO_MAIN,不受影响。)
* 运行:./test_wd_fw;全 PASS -> exit 0。
* M20 用例用 env MEOW_BACKLIGHT / MEOW_SETTINGS_FILE 把背光与 settings 指到临时文件,
* 真的走 wd_panel_off/wd_panel_on 的读写路径(真机无这两个 env,走 /sys 与 /usr/data 默认)。
*/
#define WD_NO_MAIN
#include "meow_watchdog.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 属于 shell 用户、本进程不可写),
* 否则 /tmp(Linux 构建容器)。让 `build_watchdog.sh host` 在两种环境都能直接跑。 */
static const char *tmp_root(void)
{
const char *t = getenv("TMPDIR");
return (t && t[0]) ? t : "/tmp";
}
/* ---- M2.87:SIGUSR1 手势让位(handle_touch 左缘滑动分支)----
* 判定(meow_watchdog.c):`!g_fw_app && immersive` 才发 SIGUSR1(只给裸沉浸 App);
* SDK 框架 App(g_fw_app=1)不发(返回由 SDK on_swipe 处理),防 SIGUSR1 杀死 App。 */
static volatile int g_sigusr1_hits = 0;
static void on_sigusr1(int s) { (void)s; g_sigusr1_hits++; }
/* 经 handle_touch 模拟一次「左缘起点+向右滑」完整手势(press→release),返回 SIGUSR1 命中数。 */
static int sim_swipe_sigusr1(int fw, int immersive)
{
g_fw_app = fw;
g_sigusr1_hits = 0;
last_x = 5; last_y = 300; /* 左缘原点(x<EDGE_W=24) */
unsigned long long now = now_ms();
handle_touch(immersive, getpid(), now - 3000, 1); /* press(过冷却期 + 坐标就绪) */
last_x = 100; /* 向右滑 95px(>SWIPE_DX=50) */
handle_touch(immersive, getpid(), now - 3000, 0); /* release */
return g_sigusr1_hits;
}
/* 在 <base>/<name> 建目录并写 app.json;写失败返回 -1。 */
static int fixture(const char *base, const char *name, const char *appjson)
{
char dir[512], path[560];
snprintf(dir, sizeof dir, "%s/%s", base, name);
if (mkdir(dir, 0755) != 0 && access(dir, F_OK) != 0) return -1;
snprintf(path, sizeof path, "%s/app.json", dir);
FILE *f = fopen(path, "w");
if (!f) return -1;
fputs(appjson, f);
fclose(f);
return 0;
}
static int expect_fw(const char *base, const char *name, const char *appjson, int want)
{
char dir[512];
snprintf(dir, sizeof dir, "%s/%s", base, name);
if (fixture(base, name, appjson) != 0) { g_fail++; printf("FAIL %s: cannot write fixture\n", name); return 0; }
char label[256];
snprintf(label, sizeof label, "is_fw(%s)", name);
check(label, wd_app_is_framework(dir), want);
return 1;
}
/* ---- M20:亮屏亮度决策(wd_bri_decide 纯函数)----
* 真机 bug(0.8.46,2026-11-05):settings display.brightness=1,亮屏时 watchdog 写入固定 60
* 覆盖它。新语义:settings 优先 → 真实快照 → 面板已亮则不写 → 面板黑才写安全低亮。 */
static void test_bri_decide(void)
{
const char *src = NULL;
int v;
v = wd_bri_decide(1, 60, 60, &src); /* 用户值 1 对上被污染的 60/当前 60:必须写 1 */
check("bri: settings wins over saved/cur", v == 1 && !strcmp(src, "settings"), 1);
v = wd_bri_decide(7, 0, 7, &src);
check("bri: settings 7", v == 7 && !strcmp(src, "settings"), 1);
v = wd_bri_decide(-1, 42, 3, &src); /* settings 不可读 → 用真实快照 */
check("bri: snapshot fallback", v == 42 && !strcmp(src, "snapshot"), 1);
v = wd_bri_decide(-1, 0, 3, &src); /* 都未知但面板已亮 → 不写(核心修复) */
check("bri: untouched keeps cur", v == -1 && !strcmp(src, "untouched"), 1);
v = wd_bri_decide(-1, 0, 0, &src); /* 都未知且面板黑 → 安全低亮,绝不 60 */
check("bri: safe-low not 60", v == WD_BRI_SAFE_LOW && v != 60 && v > 0 &&
!strcmp(src, "safe-low"), 1);
v = wd_bri_decide(-1, 0, -1, &src); /* 背光读失败按「黑」处理,仍要点亮 */
check("bri: read-fail -> safe-low", v == WD_BRI_SAFE_LOW && !strcmp(src, "safe-low"), 1);
v = wd_bri_decide(0, -1, 5, &src); /* 非正值一律按「未知」,不当作亮度 */
check("bri: non-positive treated unknown", v == -1 && !strcmp(src, "untouched"), 1);
}
/* ---- M20:settings 解析 + 重试 ----
* 用真机 /usr/data/meow/settings.conf 的关键行断言「路径/键名/解析/clamp 都是对的」,
* 证明真机写 60 不是本函数返回 -1(原因见报文:出货二进制根本没编进这段代码)。 */
static void test_settings_brightness(void)
{
char path[600];
snprintf(path, sizeof path, "%s/wdsetXXXXXX", tmp_root());
int fd = mkstemp(path);
if (fd < 0) { g_fail++; printf("FAIL settings: mkstemp\n"); return; }
FILE *f = fdopen(fd, "w");
fputs("# meow-settings 1\n"
"display.brightness = 1\n"
"display.brightness_min = 1\n"
"audio.volume = 10\n", f);
fclose(f);
setenv("MEOW_SETTINGS_FILE", path, 1);
check("settings: device file -> 1", wd_settings_brightness(), 1);
f = fopen(path, "w"); /* clamp 抬到 brightness_min */
fputs("display.brightness = 0\ndisplay.brightness_min = 3\n", f);
fclose(f);
check("settings: clamp up to min", wd_settings_brightness(), 3);
f = fopen(path, "w"); /* clamp 压到 100 */
fputs("display.brightness = 200\ndisplay.brightness_min = 1\n", f);
fclose(f);
check("settings: clamp down to 100", wd_settings_brightness(), 100);
f = fopen(path, "w"); /* 键缺失:与 launcher 设置表同默认 101 -> clamp 100 */
fputs("audio.volume = 10\n", f);
fclose(f);
check("settings: missing key -> 100", wd_settings_brightness(), 100);
/* 不可读(原子替换/未挂载窗口):open 重试 3 次仍失败才 -1,总耗时 ≈ 20ms */
unlink(path);
unsigned long long t0 = now_ms();
int r = wd_settings_brightness();
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");
}
static void put_file(const char *path, const char *body)
{
FILE *f = fopen(path, "w");
if (!f) return;
fputs(body, f);
fclose(f);
}
static int get_file_int(const char *path)
{
FILE *f = fopen(path, "r");
if (!f) return -1;
int v = -1;
if (fscanf(f, "%d", &v) != 1) v = -1;
fclose(f);
return v;
}
/* ---- M20:panel OFF/ON 端到端(真的读写一个「背光文件」,不 mock 函数)----
* 复现真机场景:settings display.brightness=1 → 熄屏 → 亮屏。旧代码亮屏写 60,
* 新代码必须写 1;且 settings 不可读、快照未知时保持当前值而不是回退 60。 */
static void test_panel_brightness_e2e(void)
{
char bl[600], st[600];
snprintf(bl, sizeof bl, "%s/wdblXXXXXX", tmp_root());
snprintf(st, sizeof st, "%s/wdstXXXXXX", 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);
/* 1) 真机 bug 场景:settings=1、屏亮 1 → OFF 快照 1;ON 必须写 1,绝不 60 */
put_file(bl, "1\n");
put_file(st, "display.brightness = 1\ndisplay.brightness_min = 1\n");
wd_saved_bri = WD_BRI_UNKNOWN;
wd_panel_off();
check("e2e: panel OFF writes 0", get_file_int(bl), 0);
check("e2e: panel OFF snapshot = 1", wd_saved_bri, 1);
wd_panel_on();
check("e2e: panel ON restores settings 1 (not 60)", get_file_int(bl), 1);
/* 2) 用户改了亮度:settings 仍是唯一权威(旧快照 1 不得覆盖 5) */
put_file(st, "display.brightness = 5\ndisplay.brightness_min = 1\n");
put_file(bl, "0\n");
wd_panel_on();
check("e2e: panel ON follows new settings 5", get_file_int(bl), 5);
/* 3) settings 不可读 + 快照未知 + 屏亮 3 → 不写(保持 3,不再回退 60) */
unlink(st);
put_file(bl, "3\n");
wd_saved_bri = WD_BRI_UNKNOWN;
wd_panel_on();
check("e2e: panel ON leaves 3 untouched", get_file_int(bl), 3);
/* 4) settings 不可读 + 快照未知 + 屏黑 0 → 写安全低亮(>0 且 != 60) */
put_file(bl, "0\n");
wd_panel_on();
check("e2e: panel ON dark -> safe-low", get_file_int(bl), WD_BRI_SAFE_LOW);
/* 5) settings 不可读但本次运行期真观测到 7 → 快照优先于「不动」 */
put_file(bl, "7\n");
wd_saved_bri = WD_BRI_UNKNOWN;
wd_panel_off(); /* 观测到 7,写 0 */
check("e2e: panel OFF snapshot = 7", wd_saved_bri, 7);
wd_panel_on();
check("e2e: panel ON uses real snapshot 7", get_file_int(bl), 7);
unlink(bl);
unsetenv("MEOW_BACKLIGHT");
unsetenv("MEOW_SETTINGS_FILE");
}
/* ---- M40:time32 取时/睡眠(与 libc 正常取时一致;o32 syscall 路径由交叉二进制反汇编验证)---- */
static void test_time32(void)
{
struct meow_time32 t;
check("time32 gettime ok", meow_time32_clock_gettime(CLOCK_MONOTONIC, &t) == 0, 1);
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
check("time32 sec == libc sec", t.tv_sec == (int32_t)ts.tv_sec, 1);
long dn = (long)t.tv_nsec - (long)ts.tv_nsec;
if (dn < 0) dn = -dn;
check("time32 nsec == libc nsec (<1ms)", dn < 1000000, 1);
uint64_t a = meow_time32_monotonic_ms();
uint64_t b = meow_time32_monotonic_ms();
check("monotonic nonzero", a > 0, 1);
check("monotonic nondecreasing", b >= a, 1);
check("ms from parts 1135.008s", meow_time_ms_from_parts(1135, 8000000) == 1135008ULL, 1);
unsigned long long s0 = now_ms();
meow_time32_sleep_us(20000);
unsigned long long el = now_ms() - s0;
check("sleep 20ms actually sleeps (>=15ms)", el >= 15, 1);
}
/* ---- M40:电源阈值/边沿行为矩阵(settings 取不同值)---- */
static void test_power_matrix(void)
{
unsigned long long lp = 0, fo = 0;
wd_pwr_resolve_thresholds(0, 0, &lp, &fo);
check("pwr default longpress=3000", lp == 3000, 1);
check("pwr default forceoff=8000", fo == 8000, 1);
wd_pwr_resolve_thresholds(1500, 5000, &lp, &fo);
check("pwr settings longpress=1500", lp == 1500, 1);
check("pwr settings forceoff=5000", fo == 5000, 1);
wd_pwr_resolve_thresholds(-1, 0, &lp, &fo);
check("pwr invalid -> default longpress", lp == 3000, 1);
check("pwr invalid -> default forceoff", fo == 8000, 1);
wd_pwr_resolve_thresholds(3000, 2000, &lp, &fo);
check("pwr forceoff<=longpress lifted to lp+1000", fo == 4000, 1);
/* 松开边沿:单击 / 长按但未到阈值 / 到阈值 / 唤醒 / 菜单已请求 */
check("release 100ms screen-on -> toggle off",
wd_pwr_release_action(0, 0, 100, 3000, 50) == WD_PWR_REL_TOGGLE_OFF, 1);
check("release 100ms screen-off -> toggle on",
wd_pwr_release_action(0, 0, 100, 3000, 0) == WD_PWR_REL_TOGGLE_ON, 1);
check("release 2500ms (<3000) still click -> toggle off",
wd_pwr_release_action(0, 0, 2500, 3000, 50) == WD_PWR_REL_TOGGLE_OFF, 1);
check("release 3000ms (>=longpress) -> no toggle",
wd_pwr_release_action(0, 0, 3000, 3000, 50) == WD_PWR_REL_NONE, 1);
check("release after wake -> no toggle",
wd_pwr_release_action(1, 0, 100, 3000, 0) == WD_PWR_REL_NONE, 1);
check("release after menu req -> no toggle",
wd_pwr_release_action(0, 1, 3500, 3000, 50) == WD_PWR_REL_NONE, 1);
/* 按住 tick:单击窗 / 长按出菜单 / 强关兜底 / 菜单已接管 / 已强关 */
check("tick 2999ms -> none",
wd_pwr_tick_action(0, 1, 0, 0, 2999, 3000, 8000) == WD_PWR_TICK_NONE, 1);
check("tick 3000ms -> menu",
wd_pwr_tick_action(0, 1, 0, 0, 3000, 3000, 8000) == WD_PWR_TICK_MENU, 1);
check("tick 8000ms menu requested, no takeover -> forceoff",
wd_pwr_tick_action(0, 1, 1, 0, 8000, 3000, 8000) == WD_PWR_TICK_FORCEOFF, 1);
check("tick 8000ms launcher menu active -> none",
wd_pwr_tick_action(1, 1, 1, 0, 8000, 3000, 8000) == WD_PWR_TICK_NONE, 1);
check("tick forceoff already done -> none",
wd_pwr_tick_action(0, 1, 1, 1, 9000, 3000, 8000) == WD_PWR_TICK_NONE, 1);
check("tick not down -> none",
wd_pwr_tick_action(0, 0, 0, 0, 9000, 3000, 8000) == WD_PWR_TICK_NONE, 1);
/* settings 取短值:1000/2000 */
check("tick custom 1000ms -> menu",
wd_pwr_tick_action(0, 1, 0, 0, 1000, 1000, 2000) == WD_PWR_TICK_MENU, 1);
check("tick custom 2000ms no takeover -> forceoff",
wd_pwr_tick_action(0, 1, 1, 0, 2000, 1000, 2000) == WD_PWR_TICK_FORCEOFF, 1);
}
int main(void)
{
signal(SIGUSR1, on_sigusr1); /* 捕获 handle_touch 可能发出的 SIGUSR1(测命中) */
char dir[600];
snprintf(dir, sizeof dir, "%s/wdfwXXXXXX", tmp_root());
if (!mkdtemp(dir)) { perror("mkdtemp"); return 2; }
/* 框架 App:各值形态 */
expect_fw(dir, "v_num", "{\"name\":\"x\",\"framework\":1,\"exec\":\"/a/b\"}", 1); /* JSON 数值 1 */
expect_fw(dir, "v_true", "{\"name\":\"x\",\"framework\":true}", 1); /* true */
expect_fw(dir, "v_yes", "framework=yes", 1); /* 扁平 yes */
expect_fw(dir, "v_str", "{\"framework\":\"true\"}", 1); /* 字符串 "true" */
expect_fw(dir, "v_space","{\"name\":\"x\", \"framework\" : 1 }", 1); /* 空格容错 */
/* 非框架 App */
expect_fw(dir, "v_zero", "{\"name\":\"x\",\"framework\":0}", 0); /* 数值 0 */
expect_fw(dir, "v_false","{\"framework\":false}", 0); /* false */
expect_fw(dir, "v_none", "{\"name\":\"x\",\"exec\":\"/a/b\"}", 0); /* 无 framework 键 */
expect_fw(dir, "v_sub", "{\"x_framework\":1}", 0); /* 键前缀歧义:不误判 */
/* 多行 JSON(framework 单独一行,值独立) */
expect_fw(dir, "multiline",
"{\n \"name\": \"x\",\n \"framework\": 1,\n \"exec\": \"/a/b\"\n}\n", 1);
/* wd_json_str 直接断言(防 "myframework" / 前缀干扰) */
{
char out[16] = "";
check("json_str exact", wd_json_str("{\"framework\":1}", "framework", out, sizeof out) > 0 &&
!strcmp(out, "1"), 1);
check("json_str prefix-reject",
wd_json_str("{\"x_framework\":1}", "framework", out, sizeof out), 0);
check("json_str no-hit", wd_json_str("{\"name\":\"x\"}", "framework", out, sizeof out), 0);
}
/* wd_framework_from_pid:自进程 /proc/self/exe 目录无 app.json -> 0,不崩 */
check("from_pid(no app.json)", wd_framework_from_pid(getpid()), 0);
/* ---- M2.87 SIGUSR1 让位判定(仅裸沉浸 App 发 SIGUSR1)---- */
check("sigusr1 fw-app (framework=1) -> 0", sim_swipe_sigusr1(1, 1), 0); /* SDK 框架 App:不发 */
check("sigusr1 bare-immersive -> 1", sim_swipe_sigusr1(0, 1), 1); /* 裸沉浸:发(命中) */
check("sigusr1 bare-nonimmersive -> 0", sim_swipe_sigusr1(0, 0), 0); /* 裸非沉浸:不发 */
/* ---- M20:亮屏亮度(决策 + settings 解析/重试 + panel OFF/ON 端到端)---- */
test_bri_decide();
test_settings_brightness();
test_panel_brightness_e2e();
/* ---- M40:time32 取时/睡眠 + 电源阈值行为矩阵 ---- */
test_time32();
test_power_matrix();
/* 清理 */
{
char path[560];
const char *names[] = { "v_num","v_true","v_yes","v_str","v_space","v_zero",
"v_false","v_none","v_sub","multiline" };
for (unsigned i = 0; i < sizeof names / sizeof names[0]; i++) {
snprintf(path, sizeof path, "%s/%s/app.json", dir, names[i]); unlink(path);
snprintf(path, sizeof path, "%s/%s", dir, names[i]); rmdir(path);
}
rmdir(dir);
}
printf("\n%d pass, %d fail\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}