Files
MeowOS-For-R3P2/patch_v2/meow_watchdog/test_wd_fw.c
T
AskaEth 166b32d0e5 feat(watchdog): M52 资源压力可观测 + multi_font 日志去刷屏 + audiod bind 失败即退
- watchdog(新 wd_resguard.h + meow_watchdog.c):每 10s 采 /proc/meminfo
  (MemFree/MemAvailable)、进程总数、单父进程子进程数前 5 名;过阈值时向
  MeowOS/logs 打 RESGUARD 告警,带等级/命中规则/进程名+PID 名单。零 fork、零堆分配、
  不发任何信号(只报警)。阈值依据 = 真机实测 56872kB / 空闲 100 进程 / maxch=11,
  推导写在 wd_resguard.h 文件头;日志只在等级跃迁或同态每 5 分钟复述。
- launcherd:multi_font 那行「每 2s 无条件打一条」改为只在配置签名跃迁时打
  (新 mf_logsig.h 纯函数 + test_mf_logsig 宿主单测,接进 build_launcher.sh test)。
  真机 6603 行日志里该行占 5297 行(80%)。
- audiod:io bind 失败不再只记一行就 return(旧行为让主线程 watchdog 空转 720s、
  客户端全 ECONNREFUSED),改为明确报错 + g_stop 干净退出;仅动 io_thread 一个 hunk。
2026-11-12 08:02:22 +08:00

520 lines
25 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);
}
/* ---- 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/<pid>/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;
}