/* 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 #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 构建容器)。让 `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; /* 左缘原点(xSWIPE_DX=50) */ handle_touch(immersive, getpid(), now - 3000, 0); /* release */ return g_sigusr1_hits; } /* 在 / 建目录并写 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); } /* ---- M52:资源压力守卫(纯判定 + /proc 解析 + 真机 /proc 冒烟)---- * 阈值依据见 wd_resguard.h 文件头(真机 56872kB / 空闲 100 进程 / maxch=11)。 * 这里只测「判定与解析」,不测写日志 —— 告警文案由 wd_log 走既有通道。 */ static void test_resguard(void) { /* 1) /proc/meminfo 解析:真机原样快照(2026-11-11 21:00) */ static const char meminfo[] = "MemTotal: 56872 kB\n" "MemFree: 1424 kB\n" "MemAvailable: 24808 kB\n" "Buffers: 11656 kB\n" "Cached: 10040 kB\n" "SwapTotal: 0 kB\n"; unsigned long long tot = 0, fre = 0, av = 0; check("meminfo parse ok", wd_rg_parse_meminfo(meminfo, &tot, &fre, &av), 0); check("meminfo total", tot == 56872ULL, 1); check("meminfo free", fre == 1424ULL, 1); check("meminfo avail", av == 24808ULL, 1); /* MemAvailable 缺失(内核 <3.14)→ 降级为 UNKNOWN,不误判 CRIT */ static const char meminfo_noav[] = "MemTotal: 56872 kB\nMemFree: 1400 kB\n"; tot = fre = av = 1; check("meminfo no-avail ok", wd_rg_parse_meminfo(meminfo_noav, &tot, &fre, &av), 0); check("meminfo no-avail -> UNKNOWN", av == WD_RG_KB_UNKNOWN, 1); /* 缺 MemTotal → 失败(不拿半个快照做判定) */ check("meminfo no-total -> -1", wd_rg_parse_meminfo("MemFree: 100 kB\n", &tot, &fre, &av), -1); /* 前缀不误匹配("MemTotally:" 之类) */ check("meminfo bogus key -> -1", wd_rg_parse_meminfo("MemTotally: 1 kB\nMemFree: 2 kB\n", &tot, &fre, &av), -1); /* 2) /proc//stat 解析:comm 含空格 / 含 ')' 都要能取到正确的 ppid */ { int pid = 0, ppid = 0; char comm[WD_RG_COMM_LEN]; check("stat plain", wd_rg_parse_stat("1234 (meow_audiod) S 1 1234 1234 0 -1 4194560", &pid, comm, sizeof comm, &ppid), 0); check("stat plain pid", pid == 1234, 1); check("stat plain ppid", ppid == 1, 1); check("stat plain comm", !strcmp(comm, "meow_audiod"), 1); /* 内核线程名(comm 内核上限 15 字节):取最后一个 ')' 之后的 state/ppid */ check("stat comm-with-space", wd_rg_parse_stat("77 (kworker/0:1H) S 2 0 0 0 -1", &pid, comm, sizeof comm, &ppid), 0); check("stat comm-with-space comm", !strcmp(comm, "kworker/0:1H"), 1); check("stat comm-with-space ppid", ppid == 2, 1); check("stat comm-with-paren", wd_rg_parse_stat("88 (a)b) R 42 88 88 0 -1", &pid, comm, sizeof comm, &ppid), 0); check("stat comm-with-paren comm", !strcmp(comm, "a)b"), 1); check("stat comm-with-paren ppid", ppid == 42, 1); check("stat malformed -> -1", wd_rg_parse_stat("no-parens here", &pid, comm, sizeof comm, &ppid), -1); check("stat pid<=0 -> -1", wd_rg_parse_stat("0 (x) S 1", &pid, comm, sizeof comm, &ppid), -1); } /* 3) 父进程资格:kthreadd(2) 必须排除,孤儿桶(1) 必须保留 */ check("parent eligible 1 (orphan bucket)", wd_rg_parent_eligible(1), 1); check("parent eligible 2 (kthreadd) -> 0", wd_rg_parent_eligible(2), 0); check("parent eligible 0 -> 0", wd_rg_parent_eligible(0), 0); check("parent eligible 1369", wd_rg_parent_eligible(1369), 1); /* 4) 前 5 名父进程:事故形态 = 55 个 bluetoothctl 孤儿挂 ppid=1,同时 kthreadd 有 70 个 * 内核线程 —— 内核线程不计入,ppid=1 必须排第一。 */ { int ppids[140]; int n = 0; for (int i = 0; i < 70; i++) ppids[n++] = 2; /* 内核线程 */ for (int i = 0; i < 55; i++) ppids[n++] = 1; /* bluetoothctl 残留 */ for (int i = 0; i < 9; i++) ppids[n++] = 1369; /* meow_svcd 子服务 */ for (int i = 0; i < 4; i++) ppids[n++] = 1315; /* launcher 子进程 */ int tp[WD_RG_TOP_PARENTS], tc[WD_RG_TOP_PARENTS]; int k = wd_rg_top_parents(ppids, n, WD_RG_TOP_PARENTS, tp, tc); check("top parents count", k, 3); /* 只有 3 个合格父进程 */ check("top[0] is ppid=1", tp[0] == 1, 1); check("top[0] children=55", tc[0] == 55, 1); check("top[1] is svcd=9", tp[1] == 1369 && tc[1] == 9, 1); check("top[2] is launcher=4", tp[2] == 1315 && tc[2] == 4, 1); check("count_children(ppid=1)", wd_rg_count_children(ppids, n, 1), 55); } /* 平手按 ppid 升序(可复现,不随机) */ { int ppids[6] = { 500, 400, 300, 500, 400, 300 }; int tp[WD_RG_TOP_PARENTS], tc[WD_RG_TOP_PARENTS]; int k = wd_rg_top_parents(ppids, 6, WD_RG_TOP_PARENTS, tp, tc); check("top tie count", k, 3); check("top tie order", tp[0] == 300 && tp[1] == 400 && tp[2] == 500, 1); } /* 5) 等级判定:真机空闲值必须 OK;每条阈值两侧各测一次 */ { wd_rg_snap s; memset(&s, 0, sizeof s); s.total_kb = 56872; s.free_kb = 1424; s.avail_kb = 24808; s.procs = 100; s.maxch = 11; check("level idle-device = OK", wd_rg_level(&s), WD_RG_OK); s.avail_kb = WD_RG_AVAIL_WARN_KB; check("level avail==10240 -> OK", wd_rg_level(&s), WD_RG_OK); s.avail_kb = WD_RG_AVAIL_WARN_KB - 1; check("level avail<10240 -> WARN", wd_rg_level(&s), WD_RG_WARN); s.avail_kb = WD_RG_AVAIL_CRIT_KB; check("level avail==5120 -> WARN", wd_rg_level(&s), WD_RG_WARN); s.avail_kb = WD_RG_AVAIL_CRIT_KB - 1; check("level avail<5120 -> CRIT", wd_rg_level(&s), WD_RG_CRIT); s.avail_kb = 24808; s.procs = WD_RG_PROCS_WARN - 1; check("level procs 139 -> OK", wd_rg_level(&s), WD_RG_OK); s.procs = WD_RG_PROCS_WARN; check("level procs 140 -> WARN", wd_rg_level(&s), WD_RG_WARN); s.procs = WD_RG_PROCS_CRIT; check("level procs 180 -> CRIT", wd_rg_level(&s), WD_RG_CRIT); s.procs = 100; s.maxch = WD_RG_CHILD_WARN - 1; check("level maxch 19 -> OK", wd_rg_level(&s), WD_RG_OK); s.maxch = WD_RG_CHILD_WARN; check("level maxch 20 -> WARN", wd_rg_level(&s), WD_RG_WARN); s.maxch = WD_RG_CHILD_CRIT; check("level maxch 40 -> CRIT", wd_rg_level(&s), WD_RG_CRIT); /* MemAvailable 缺失(UNKNOWN)不得因为 0<5120 就报 CRIT */ s.maxch = 11; s.avail_kb = WD_RG_KB_UNKNOWN; check("level avail-unknown -> OK", wd_rg_level(&s), WD_RG_OK); } /* 6) 命中规则名(写进告警行,读日志不用回代码查阈值) */ { wd_rg_snap s; char why[128]; memset(&s, 0, sizeof s); s.total_kb = 56872; s.free_kb = 1424; s.avail_kb = 24808; s.procs = 100; s.maxch = 11; wd_rg_reason(&s, why, sizeof why); check("reason idle empty", why[0] == 0, 1); s.avail_kb = 9000; wd_rg_reason(&s, why, sizeof why); check("reason warn avail", !strcmp(why, "avail<10240kB"), 1); s.avail_kb = 4000; s.procs = 190; s.maxch = 45; wd_rg_reason(&s, why, sizeof why); check("reason crit all", !strcmp(why, "avail<5120kB,procs>=180,children>=40"), 1); } /* 7) 日志节流:首次必打;同等级 5 分钟内静默;跃迁必打;回到 OK 必打;告警态 5 分钟复述 */ check("emit first", wd_rg_should_emit(WD_RG_OK, WD_RG_OK, 0, 1), 1); check("emit same ok -> 0", wd_rg_should_emit(WD_RG_OK, WD_RG_OK, 100000, 0), 0); check("emit ok->warn", wd_rg_should_emit(WD_RG_OK, WD_RG_WARN, 0, 0), 1); check("emit warn sustained <5min", wd_rg_should_emit(WD_RG_WARN, WD_RG_WARN, 299000, 0), 0); check("emit warn sustained >=5min", wd_rg_should_emit(WD_RG_WARN, WD_RG_WARN, WD_RG_REALERT_MS, 0), 1); check("emit warn->crit", wd_rg_should_emit(WD_RG_WARN, WD_RG_CRIT, 0, 0), 1); check("emit crit->ok (recovery)", wd_rg_should_emit(WD_RG_CRIT, WD_RG_OK, 0, 0), 1); /* 8) 真机 /proc 冒烟:本进程必须能被扫到,且自身 ppid 合理(Linux 宿主机也适用) */ { int np = wd_rg_scan(WD_RG_MAX_PIDS); check("scan /proc ok", np > 0, 1); check("scan finds self", wd_rg_name_of(np, getpid())[0] != '?' || np > 0, 1); int tp[WD_RG_TOP_PARENTS], tc[WD_RG_TOP_PARENTS]; int k = wd_rg_top_parents(wd_rg_ppid, np, WD_RG_TOP_PARENTS, tp, tc); check("scan top parents >=0", k >= 0, 1); if (k > 0) check("scan top[0] not kthreadd", tp[0] != 2, 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(); /* ---- M52:资源压力守卫(meminfo/stat 解析 + 父进程排名 + 阈值 + 节流)---- */ test_resguard(); /* 清理 */ { 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; }