根因(2026-11-04 真机 dev.player.ncm pid=7520): - bg_page.c bgm_app_meta 把 app.json 的 framework 字段映射进 bgmgr_row.framework,而 bgmgr_can_kill 把它当「系统/框架 App 禁止 kill」。但 framework=1 在 MeowOS 里意为「用 meow_ui.a 的 SDK 框架 App」(docs/APP_DEV_GUIDE.md §9,推荐值,真机 4 个 App 全写 1) → 后台页每行「杀」按钮被置灰,bgm_row_tap 命中后直接 return 1、不调 meow_bg_terminate; 「一键清除后台」同理 0 行可杀。整条链路无日志,故只能靠翻 app.json 才定位(全部设备日志里 "后台管理页终止后台App" 出现 0 次 = 4 次进页从未真正杀过)。 - 面板 killfg 正常是因为走 am kill → amsd → MSG_AMS_KILL,无 framework 门控;但它其实靠 amsd 1200ms×2 超时兜底 kill(pid,SIGTERM) 才杀成:util.c 原本在无前台 App 会话时只回 ACK -2((uint32_t)MEOW_ERR_NOENT),ACK 及时到达时 amsd 判「launcher refused」完全不杀 (ams_20260915_031339.log 实证),ACK 迟到才侥幸成功(2026-11-04 真机)。 修复: - meow_bgmgr: 门控位 framework → protected_app(系统级受保护标记;当前无 App 置位,launcher 自身组件本就不在 App 表/后台表内)。bg_page 不再把 a->framework 送进门控。 - bg_page: 行副文本改存 ctx 持久缓冲(原 rbuf[].sub 指向 bgm_rows 栈帧 → 悬垂,副标题乱码); 门控拒绝/未命中后台表各留一行 logmsg,「点了没反应」从此可查。 - util.c MSG_AMS_KILL: 先查后台保活表 → meow_bg_terminate 就地走真退序列(与页面「杀」按钮 同一通道,故 am kill 与页面按钮行为对称),无前台会话也能杀;否则仍走 pending / ACK -2。 - amsd cmd_kill/cmd_start: ipc 失败原因细分(connect failed / timeout, no reply / ...),日志 不再笼统写 "launcher offline"(响应串保留该字样,MeowPanel api_ams_ctl 依此映射 503)。 - meow_bgplay: meow_bg_terminate 注释/日志写明「后台管理页 / am kill」双入口同通道。 测试(宿主 gcc,产物在 $TMPDIR): - test_bgpage 40 checks:新增 framework=1 仍判可杀且按钮真 terminate(本 bug 回归)、 受保护行按钮不杀但留诊断日志、sub 持久化 + 栈抖动不变(悬垂回归)。 - test_bgmgr 30 checks:protected_app 门控矩阵 + framework=1 可杀回归。 - test_bgplay 92 PASS:新增 4b meow_bg_terminate 全链路(受理→term_at_ms→tick SIGTERM→ on_bg_dead→清槽;空/不在表分别 INVAL/NOENT);TEST_SVC_PATH 改取 $TMPDIR (Termux 无 /tmp,原 fputs(NULL) 直接 abort)。 - amsd 宿主集成(fake launcher 桩,4 例):ACK 0→terminate 且不盲杀;ACK -2→refused 不盲杀; 无 launcher→direct SIGTERM (connect failed);挂起不回→direct SIGTERM (timeout, no reply)。 0warning:util.c 与 main 同告警逐行一致(仅 SDK 头既有 typedef 重定义),其余文件干净。
187 lines
8.5 KiB
C
187 lines
8.5 KiB
C
/* test_bgmgr.c — 批③ 后台管理页 逻辑层宿主单测(meow_bgmgr)。
|
||
*
|
||
* 验证:
|
||
* 1) bgmgr_collect:enumerate 后台表 → app_meta 补 name/icon/protected_app/is_fg → proc_sample 补
|
||
* pcpu/rss_kb;行 killable 预判正确;name 回退 app_id。
|
||
* 2) bgmgr_can_kill 门控矩阵:protected_app=1 → 0(**受保护 App 不 kill**);is_fg=1 → 0(当前前台不 kill);
|
||
* pid<=0 → 0(非后台/无进程);普通后台(非受保护/非前台/pid>0)→ 1。
|
||
* 3) bgmgr_kill_all 一键清:受保护 App 不杀、当前前台不杀、非后台不杀;其余逐一 kill;返回 kill 数。
|
||
* 3b) 回归:app.json framework=1(SDK 框架 App 标志,现有 App 全写 1)**不影响**可杀性。
|
||
* 4) bgmgr_status_str / bgmgr_mem_str 文案。
|
||
* 只依赖 meow_bgmgr.c + meow_ipc.h(纯 C);宿主 gcc 直编:
|
||
* gcc -Wall -Wextra -std=gnu99 test_bgmgr.c meow_bgmgr.c -o test_bgmgr
|
||
*/
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdint.h>
|
||
#include "meow_bgmgr.h"
|
||
|
||
static int g_fails = 0;
|
||
static int g_checks = 0;
|
||
#define CHECK(cond, msg) do { \
|
||
g_checks++; \
|
||
if (cond) printf("PASS: %s\n", msg); \
|
||
else { printf("FAIL: %s\n", msg); g_fails++; } } while (0)
|
||
|
||
/* ---- 假件(enumerate/app_meta/proc_sample 注入)---- */
|
||
static const char g_meta_app[][24] = {
|
||
"os.meow.hello", "os.meow.reader", "os.meow.sysguard", "os.meow.fgapp",
|
||
};
|
||
static int g_killed_n = 0;
|
||
static char g_killed_app[8][40];
|
||
static int g_meta_use_unknown = 0; /* 第 4 行 meta 返回 -1(未注册),测 name 回退 */
|
||
|
||
static int fake_enumerate(void *ctx, bgmgr_row_t *rows, int max)
|
||
{
|
||
(void)ctx;
|
||
if (max < 4) return 0;
|
||
/* 4 个后台行:hello=普通、reader=常驻、sysguard=受保护、fgapp=当前前台 */
|
||
memset(&rows[0], 0, sizeof rows[0]);
|
||
snprintf(rows[0].app_id, sizeof rows[0].app_id, "%s", g_meta_app[0]);
|
||
rows[0].pid = 1001; rows[0].keepalive = 0; rows[0].on_card = 0; rows[0].bg_since_ms = 1000;
|
||
|
||
memset(&rows[1], 0, sizeof rows[1]);
|
||
snprintf(rows[1].app_id, sizeof rows[1].app_id, "%s", g_meta_app[1]);
|
||
rows[1].pid = 1002; rows[1].keepalive = 1; rows[1].on_card = 0; rows[1].bg_since_ms = 2000;
|
||
|
||
memset(&rows[2], 0, sizeof rows[2]);
|
||
snprintf(rows[2].app_id, sizeof rows[2].app_id, "%s", g_meta_app[2]);
|
||
rows[2].pid = 1003; rows[2].keepalive = 0; rows[2].on_card = 0; rows[2].bg_since_ms = 3000;
|
||
|
||
memset(&rows[3], 0, sizeof rows[3]);
|
||
snprintf(rows[3].app_id, sizeof rows[3].app_id, "%s", g_meta_app[3]);
|
||
rows[3].pid = 1004; rows[3].keepalive = 0; rows[3].on_card = 0; rows[3].bg_since_ms = 4000;
|
||
return 4;
|
||
}
|
||
|
||
/* meta:hello/reader/sysguard/fgapp 各给名字;sysguard 标受保护;pid==1004 标前台。 */
|
||
static int fake_app_meta(void *ctx, const char *app_id, pid_t pid,
|
||
char *name, int nl, char *icon, int il,
|
||
char *protected_app, int *is_fg)
|
||
{
|
||
(void)ctx;
|
||
if (!strcmp(app_id, "os.meow.hello")) snprintf(name, nl, "播放");
|
||
else if (!strcmp(app_id, "os.meow.reader")) snprintf(name, nl, "阅读");
|
||
else if (!strcmp(app_id, "os.meow.sysguard")) snprintf(name, nl, "系统守护");
|
||
else if (!strcmp(app_id, "os.meow.fgapp")) snprintf(name, nl, "前台应用");
|
||
else return -1; /* 未注册 → name 回退 app_id */
|
||
snprintf(icon, il, "/tmp/icon_%s.png", app_id);
|
||
*protected_app = (!strcmp(app_id, "os.meow.sysguard")) ? '1' : 0;
|
||
*is_fg = (pid == 1004) ? 1 : 0; /* fgapp 标前台 */
|
||
return 0;
|
||
}
|
||
|
||
static int fake_proc_sample(void *ctx, pid_t pid, double *pcpu, long *rss_kb)
|
||
{
|
||
(void)ctx;
|
||
if (pid <= 0) return -1;
|
||
*pcpu = (double)(pid % 100) * 0.5; /* 1001->0.5% ... 1004->2.0% */
|
||
*rss_kb = (long)(pid % 10) * 2048; /* 1001->2048 ... 1004->8192 */
|
||
return 0;
|
||
}
|
||
|
||
static void fake_log(const char *fmt, ...) { (void)fmt; }
|
||
|
||
static void fake_kill(const char *app_id)
|
||
{
|
||
if (g_killed_n < 8) snprintf(g_killed_app[g_killed_n++], 40, "%s", app_id);
|
||
}
|
||
|
||
/* enumerate 只回一行:未注册 app_id,测 name 回退。 */
|
||
static int fake_enumerate_unknown(void *ctx, bgmgr_row_t *rows, int max)
|
||
{
|
||
(void)ctx;
|
||
if (max < 1) return 0;
|
||
(void)g_meta_use_unknown;
|
||
memset(&rows[0], 0, sizeof rows[0]);
|
||
snprintf(rows[0].app_id, sizeof rows[0].app_id, "os.meow.noreg");
|
||
rows[0].pid = 2001; rows[0].keepalive = 0;
|
||
return 1;
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
bgmgr_ops_t ops;
|
||
memset(&ops, 0, sizeof ops);
|
||
ops.enumerate = fake_enumerate;
|
||
ops.app_meta = fake_app_meta;
|
||
ops.proc_sample = fake_proc_sample;
|
||
ops.log = fake_log;
|
||
|
||
bgmgr_row_t rows[MEOW_BGMR_ROW_MAX];
|
||
int n = bgmgr_collect(&ops, rows, MEOW_BGMR_ROW_MAX);
|
||
|
||
printf("=== 1) collect ===\n");
|
||
CHECK(n == 4, "collect returns 4 rows");
|
||
CHECK(!strcmp(rows[0].name, "播放") && rows[0].pid == 1001, "row0 hello name/pid");
|
||
CHECK(rows[0].protected_app == 0, "row0 hello not protected");
|
||
CHECK(!strcmp(rows[1].name, "阅读") && rows[1].keepalive == 1, "row1 reader keepalive");
|
||
CHECK(rows[2].protected_app == '1', "row2 sysguard protected");
|
||
CHECK(rows[3].is_fg == 1, "row3 fgapp is_fg");
|
||
CHECK(rows[0].pcpu > 0 && rows[0].rss_kb > 0, "row0 pcpu/rss populated");
|
||
CHECK(rows[0].killable == 1 && rows[2].killable == 0, "killable prefilled by can_kill");
|
||
CHECK(!strcmp(rows[2].icon, "/tmp/icon_os.meow.sysguard.png"), "icon populated");
|
||
|
||
/* name 回退:未注册 app_id(app_meta 返回 -1)→ name==app_id */
|
||
printf("=== 1b) name fallback ===\n");
|
||
{
|
||
bgmgr_ops_t o2 = ops; o2.enumerate = fake_enumerate_unknown;
|
||
bgmgr_row_t r1[1]; memset(r1, 0, sizeof r1);
|
||
int n2 = bgmgr_collect(&o2, r1, 1);
|
||
CHECK(n2 == 1, "unknown row collected");
|
||
CHECK(!strcmp(r1[0].name, "os.meow.noreg"), "name falls back to app_id");
|
||
CHECK(bgmgr_can_kill(&r1[0]) == 1, "unknown bg app still killable");
|
||
}
|
||
|
||
printf("=== 2) can_kill gating ===\n");
|
||
CHECK(bgmgr_can_kill(&rows[2]) == 0, "protected app NOT killable");
|
||
CHECK(bgmgr_can_kill(&rows[3]) == 0, "fg app NOT killable (is_fg)");
|
||
CHECK(bgmgr_can_kill(&rows[0]) == 1, "normal bg app killable");
|
||
CHECK(bgmgr_can_kill(&rows[1]) == 1, "keepalive bg app killable");
|
||
{
|
||
bgmgr_row_t dead; memset(&dead, 0, sizeof dead);
|
||
snprintf(dead.app_id, sizeof dead.app_id, "x");
|
||
dead.pid = 0;
|
||
CHECK(bgmgr_can_kill(&dead) == 0, "pid<=0 NOT killable");
|
||
CHECK(bgmgr_can_kill(NULL) == 0, "NULL NOT killable");
|
||
}
|
||
|
||
printf("=== 3) kill_all (one-key clear) ===\n");
|
||
g_killed_n = 0;
|
||
int k = bgmgr_kill_all(&ops, rows, n, fake_kill);
|
||
CHECK(k == 2, "kill_all kills 2 (hello+reader)");
|
||
CHECK(g_killed_n == 2, "kill_all actually invoked kill_fn 2x");
|
||
int killed_fw = 0, killed_fg = 0, killed_bg = 0;
|
||
for (int i = 0; i < g_killed_n; i++) {
|
||
if (!strcmp(g_killed_app[i], "os.meow.sysguard")) killed_fw = 1;
|
||
if (!strcmp(g_killed_app[i], "os.meow.fgapp")) killed_fg = 1;
|
||
if (!strcmp(g_killed_app[i], "os.meow.hello")) killed_bg = 1;
|
||
}
|
||
CHECK(killed_fw == 0, "kill_all did NOT kill protected app");
|
||
CHECK(killed_fg == 0, "kill_all did NOT kill fg app");
|
||
CHECK(killed_bg == 1, "kill_all killed a real bg app");
|
||
CHECK(bgmgr_kill_all(&ops, rows, n, NULL) == 0, "kill_all NULL kill_fn -> 0");
|
||
|
||
/* 回归(2026-11-04 真机):app.json framework=1 是「SDK 框架 App」标志(现有 App 全写 1),
|
||
* **不是**受保护位。旧实现把它当门控 → dev.player.ncm 等所有后台 App 都不可杀、按钮置灰。 */
|
||
printf("=== 3b) framework(SDK) ≠ 受保护 ===\n");
|
||
{
|
||
bgmgr_row_t sdk; memset(&sdk, 0, sizeof sdk);
|
||
snprintf(sdk.app_id, sizeof sdk.app_id, "dev.player.ncm");
|
||
sdk.pid = 7520; sdk.protected_app = 0;
|
||
CHECK(bgmgr_can_kill(&sdk) == 1, "SDK 框架 App(framework=1)后台仍可杀");
|
||
}
|
||
|
||
printf("=== 4) status/mem str ===\n");
|
||
CHECK(!strcmp(bgmgr_status_str(&rows[1]), "常驻"), "status_str keepalive=常驻");
|
||
CHECK(!strcmp(bgmgr_status_str(&rows[0]), "后台"), "status_str normal=后台");
|
||
CHECK(!strcmp(bgmgr_status_str(&rows[3]), "前台"), "status_str is_fg=前台");
|
||
CHECK(!strcmp(bgmgr_mem_str(512), "512KB"), "mem_str(<1MB)=KB");
|
||
CHECK(!strcmp(bgmgr_mem_str(2048), "2.0MB"), "mem_str(>=1MB)=MB");
|
||
|
||
if (g_fails == 0) printf("\nALL PASS (%d checks)\n", g_checks);
|
||
else printf("\n%d FAILED\n", g_fails);
|
||
return g_fails ? 1 : 0;
|
||
}
|