492 lines
22 KiB
C
492 lines
22 KiB
C
/* test_bgplay.c — 宿主单测:meow_bgplay 后台播放保活 + 无头后台服务状态机(M4)。
|
||
*
|
||
* 验证:
|
||
* 1) meow_bg_should_keep 判定矩阵(background_audio × media_playing);
|
||
* 2) 后台播放保活全链路(真 fork 子进程):META PLAYING → should_keep=1 → bg_add →
|
||
* 进程存活 + PLAYCTL 经会话 fd 往返(后台化不断连)→ take 恢复 → META STOPPED →
|
||
* should_keep=0(停播后再退出=真退);
|
||
* 3) 后台 App 崩溃照收:子进程被杀 → bg_tick WNOHANG 回收 → on_bg_dead + 清槽;
|
||
* 4) 卡拔例外:on_card=1 + card_present 假 0 → tick SIGTERM 回收(防 SIGBUS);
|
||
* 4b) 批①+宽限:未声明(keepalive=0)媒体不活跃超 MEOW_BG_GRACE_MS 回收(含恰等边界不回收);
|
||
* 声明常驻(keepalive=1)或媒体活跃(META PLAYING)超 grace 不清;
|
||
* 5) bg 表满(MEOW_BG_MAX)→ LRU 回收最久未激活的未声明槽新增成功;全为声明常驻 → MEOW_ERR_NOMEM
|
||
* (不回收声明常驻);pid<=0 → MEOW_ERR_INVAL;重复 add → MEOW_ERR_EXISTS;
|
||
* 6) 服务注册:exec 不可执行 → NOENT;同名在跑 → EXISTS;
|
||
* 7) 退避重启 1s/2s + max_restart=2 熔断(假时钟注入):清通知回调 + 注册连接收到
|
||
* MSG_SYS{SVC_DEAD=2} + circuit 后不再拉起;重注册复位熔断重新拉起;
|
||
* 8) MSG_SERVICE_STOP:SIGTERM + stopping,不触发重启,槽释放;
|
||
* 9) kill_all:bg+svc 全回收 + 注册连接收到 MSG_SYS{SHUTDOWN=1}。
|
||
* 只依赖 meow_bgplay.c + meow_media.c + meow_ipc.h(纯 C),宿主 gcc 直编:
|
||
* gcc -Wall -Wextra -std=gnu99 test_bgplay.c meow_bgplay.c meow_media.c -o test_bgplay
|
||
*/
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdint.h>
|
||
#include <signal.h>
|
||
#include <unistd.h>
|
||
#include <fcntl.h>
|
||
#include <poll.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/wait.h>
|
||
#include "meow_bgplay.h"
|
||
#include "meow_media.h"
|
||
#include "meow_ipc.h"
|
||
|
||
static int g_fails = 0;
|
||
#define CHECK(cond, msg) do { \
|
||
if (cond) printf("PASS: %s\n", msg); \
|
||
else { printf("FAIL: %s\n", msg); g_fails++; } } while (0)
|
||
|
||
/* ---- 假件(ops 注入)---- */
|
||
static uint64_t g_ms = 100000; /* 假时钟(退避断言用,手动推进) */
|
||
static int g_card = 1; /* 卡在位 */
|
||
static int g_bg_dead = 0; /* on_bg_dead 计数 */
|
||
static int g_bg_dead_status = 0;
|
||
static char g_bg_dead_app[MEOW_APP_ID_MAX];
|
||
static int g_notif_cleared = 0; /* notif_clear_app 计数 */
|
||
static char g_notif_cleared_app[MEOW_APP_ID_MAX];
|
||
|
||
static uint64_t fake_now_ms(void) { return g_ms; }
|
||
static int fake_card(void) { return g_card; }
|
||
static void fake_bg_dead(const char *app_id, pid_t pid, int status)
|
||
{
|
||
(void)pid;
|
||
g_bg_dead++;
|
||
g_bg_dead_status = status;
|
||
snprintf(g_bg_dead_app, sizeof g_bg_dead_app, "%s", app_id);
|
||
}
|
||
static int fake_notif_clear(const char *app_id)
|
||
{
|
||
g_notif_cleared++;
|
||
snprintf(g_notif_cleared_app, sizeof g_notif_cleared_app, "%s", app_id);
|
||
return 1;
|
||
}
|
||
/* send_frame:整帧写到 fd(socketpair 一端),对端读回校验。 */
|
||
static size_t fake_send_frame(int fd, uint16_t type, uint32_t req_id,
|
||
const void *payload, uint32_t len)
|
||
{
|
||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||
uint32_t magic = MEOW_IPC_MAGIC, rid = req_id, l = len;
|
||
uint16_t ver = MEOW_IPC_VERSION, t = type;
|
||
size_t off = 0;
|
||
|
||
memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2);
|
||
memcpy(hdr + 8, &rid, 4); memcpy(hdr + 12, &l, 4);
|
||
if (write(fd, hdr, sizeof hdr) != (ssize_t)sizeof hdr) return 0;
|
||
off = sizeof hdr;
|
||
if (len && payload && write(fd, payload, len) > 0) off += len;
|
||
return off;
|
||
}
|
||
/* media ops.send 同 fake_send_frame(PLAYCTL 下发校验用) */
|
||
|
||
/* 从 socketpair 读一帧(poll 500ms 防挂)。返回负载字节数,<0=超时/失败。 */
|
||
static int read_frame(int fd, uint16_t *type, void *pay, int paycap)
|
||
{
|
||
struct pollfd pf = { .fd = fd, .events = POLLIN, .revents = 0 };
|
||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||
uint32_t len;
|
||
|
||
if (poll(&pf, 1, 500) <= 0) return -1;
|
||
if (read(fd, hdr, sizeof hdr) != (ssize_t)sizeof hdr) return -1;
|
||
memcpy(type, hdr + 6, 2);
|
||
memcpy(&len, hdr + 12, 4);
|
||
if (len > (uint32_t)paycap) return -1;
|
||
if (len && read(fd, pay, len) != (ssize_t)len) return -1;
|
||
return (int)len;
|
||
}
|
||
|
||
/* fork 一个长眠子进程(300s),测试末统一 SIGKILL 清理。 */
|
||
static pid_t spawn_sleeper(void)
|
||
{
|
||
pid_t pid = fork();
|
||
if (pid == 0) { sleep(300); _exit(0); }
|
||
return pid;
|
||
}
|
||
|
||
/* 泵 tick 直到 cond 或超时(~2s):子进程死亡到 WNOHANG 可见有调度延迟。 */
|
||
static int pump_bg_until(int (*cond)(void *), void *ctx)
|
||
{
|
||
for (int i = 0; i < 100; i++) {
|
||
meow_bg_tick();
|
||
if (cond(ctx)) return 1;
|
||
usleep(20000);
|
||
}
|
||
return 0;
|
||
}
|
||
static int pump_svc_until(int (*cond)(void *), void *ctx)
|
||
{
|
||
for (int i = 0; i < 100; i++) {
|
||
meow_svc_tick();
|
||
if (cond(ctx)) return 1;
|
||
usleep(20000);
|
||
}
|
||
return 0;
|
||
}
|
||
static int cond_bg_dead(void *ctx) { (void)ctx; return g_bg_dead > 0; }
|
||
static int cond_svc_reaped(void *ctx)
|
||
{
|
||
const meow_svc_rt_t *s;
|
||
(void)ctx;
|
||
s = meow_svc_find("dev.test.svc", "daemon");
|
||
return s && s->pid == 0;
|
||
}
|
||
static int cond_svc_gone(void *ctx)
|
||
{
|
||
(void)ctx;
|
||
return meow_svc_find("dev.test.svc", "daemon") == NULL;
|
||
}
|
||
|
||
/* 服务测试用长命可执行:shell 脚本 exec sleep 300(svc_spawn 内部 execve 走 shebang)。 */
|
||
#define TEST_SVC_PATH "/tmp/meow_bgplay_test_svc.sh"
|
||
static void write_test_svc(void)
|
||
{
|
||
FILE *f = fopen(TEST_SVC_PATH, "w");
|
||
fputs("#!/bin/sh\nexec sleep 300\n", f);
|
||
fclose(f);
|
||
chmod(TEST_SVC_PATH, 0755);
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
meow_bgplay_ops ops;
|
||
meow_media_ops mops;
|
||
|
||
memset(&ops, 0, sizeof ops);
|
||
ops.now_ms = fake_now_ms;
|
||
ops.card_present = fake_card;
|
||
ops.on_bg_dead = fake_bg_dead;
|
||
ops.media_resident = meow_media_app_resident; /* 批①+宽限:媒体活跃判定(宽限回收/槽满LRU) */
|
||
ops.notif_clear_app = fake_notif_clear;
|
||
ops.send_frame = fake_send_frame;
|
||
ops.log_dir = NULL;
|
||
meow_bgplay_init(&ops);
|
||
|
||
memset(&mops, 0, sizeof mops);
|
||
mops.send = fake_send_frame;
|
||
meow_media_init(&mops);
|
||
|
||
/* ---- 1) should_keep 判定矩阵(批①三参:bg_audio, media_resident, keepalive) ---- */
|
||
CHECK(meow_bg_should_keep(0, 0, 0) == 0, "should_keep(未声明,未活跃,非常驻)=0");
|
||
CHECK(meow_bg_should_keep(1, 0, 0) == 0, "should_keep(声明bg_audio,无会话)=0");
|
||
CHECK(meow_bg_should_keep(0, 1, 0) == 0, "should_keep(未声明bg_audio,活跃)=0");
|
||
CHECK(meow_bg_should_keep(1, 1, 0) == 1, "should_keep(bg_audio+活跃)=1");
|
||
CHECK(meow_bg_should_keep(0, 0, 1) == 1, "should_keep(keepalive恒保)=1");
|
||
CHECK(meow_bg_should_keep(0, 1, 1) == 1, "should_keep(keepalive恒保,忽略media)=1");
|
||
CHECK(meow_bg_should_keep(1, 0, 1) == 1, "should_keep(keepalive恒保,无会话)=1");
|
||
CHECK(meow_bg_should_keep(1, 0, 0) == 0, "should_keep(bg_audio+resident=0 真退)=0");
|
||
|
||
/* ---- 2) 后台播放保活全链路 ---- */
|
||
int sp[2];
|
||
CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == 0, "socketpair 建立");
|
||
{
|
||
m_meta_t meta;
|
||
memset(&meta, 0, sizeof meta);
|
||
snprintf(meta.app_id, sizeof meta.app_id, "dev.test.bg1");
|
||
meta.state = MEOW_MEDIA_PLAYING;
|
||
meta.dur_ms = 200000;
|
||
CHECK(meow_media_meta(&meta, sp[0]) == 0, "META 上报 PLAYING 注册媒体会话");
|
||
}
|
||
CHECK(meow_media_app_playing("dev.test.bg1") == 1, "app_playing(bg1)=1");
|
||
CHECK(meow_media_app_resident("dev.test.bg1") == 1, "app_resident(bg1)=1");
|
||
CHECK(meow_bg_should_keep(1, meow_media_app_resident("dev.test.bg1"), 0) == 1,
|
||
"播放中退出 → 应保活");
|
||
|
||
pid_t kid = spawn_sleeper();
|
||
CHECK(kid > 0, "fork 假 App 子进程");
|
||
CHECK(meow_bg_add("dev.test.bg1", kid, sp[0], 0, 0) == 0, "bg_add 转后台保活");
|
||
CHECK(meow_bg_is_backgrounded("dev.test.bg1") == 1, "is_backgrounded=1");
|
||
CHECK(meow_bg_count() == 1, "bg count=1");
|
||
{
|
||
const meow_bg_entry_t *be = meow_bg_at(0);
|
||
CHECK(be && be->bg_since_ms > 0, "bg 槽记 bg_since_ms(转后台时刻)");
|
||
CHECK(be && be->keepalive == 0, "bg_audio 类 keepalive=0");
|
||
}
|
||
CHECK(kill(kid, 0) == 0, "后台化后进程仍存活(未 SIGTERM/SIGSTOP)");
|
||
/* PLAYCTL 经会话 fd 往返(后台化不断连:进程/fd 均保留) */
|
||
CHECK(meow_media_playctl_to("dev.test.bg1", 6, 0) == 0, "PLAYCTL 下发到后台 App 会话");
|
||
{
|
||
uint16_t t = 0;
|
||
m_playctl_t pc;
|
||
int n = read_frame(sp[1], &t, &pc, sizeof pc);
|
||
CHECK(n == (int)sizeof pc && t == MSG_PLAYCTL && pc.cmd == 6,
|
||
"对端收到 MSG_PLAYCTL cmd=6(控制通道后台不断连)");
|
||
}
|
||
{
|
||
int rfd = -1;
|
||
pid_t rpid = 0;
|
||
CHECK(meow_bg_take("dev.test.bg1", &rfd, &rpid) == 0 && rpid == kid && rfd == sp[0],
|
||
"bg_take 回前台(pid/fd 原样归还)");
|
||
}
|
||
CHECK(meow_bg_is_backgrounded("dev.test.bg1") == 0 && meow_bg_count() == 0,
|
||
"take 后清槽");
|
||
meow_media_clear("dev.test.bg1");
|
||
{
|
||
m_meta_t meta;
|
||
memset(&meta, 0, sizeof meta);
|
||
snprintf(meta.app_id, sizeof meta.app_id, "dev.test.bg1");
|
||
meta.state = MEOW_MEDIA_STOPPED;
|
||
meow_media_meta(&meta, sp[0]);
|
||
}
|
||
CHECK(meow_bg_should_keep(1, meow_media_app_resident("dev.test.bg1"), 0) == 0,
|
||
"停播后再退出 → should_keep=0(真退 SIGTERM)");
|
||
kill(kid, SIGKILL); waitpid(kid, NULL, 0);
|
||
close(sp[0]); close(sp[1]);
|
||
meow_media_clear("dev.test.bg1");
|
||
|
||
/* ---- 2b) keepalive 类(background_keepalive)入槽标记 ---- */
|
||
{
|
||
pid_t kk = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.keep", kk, -1, 0, 1) == 0, "bg_add(keepalive=1)");
|
||
const meow_bg_entry_t *bk = meow_bg_at(0);
|
||
CHECK(bk && bk->keepalive == 1, "keepalive 类槽 keepalive=1");
|
||
CHECK(bk && bk->bg_since_ms > 0, "keepalive 类槽 bg_since_ms>0");
|
||
meow_bg_take("dev.test.keep", NULL, NULL);
|
||
kill(kk, SIGKILL); waitpid(kk, NULL, 0);
|
||
CHECK(meow_bg_count() == 0, "keepalive 槽 take 后清槽");
|
||
}
|
||
|
||
/* ---- 3) 后台 App 崩溃照收 ---- */
|
||
g_bg_dead = 0;
|
||
kid = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.crash", kid, -1, 0, 0) == 0, "bg_add(crash)");
|
||
kill(kid, SIGKILL);
|
||
{
|
||
int ok = pump_bg_until(cond_bg_dead, NULL);
|
||
(void)ok;
|
||
}
|
||
CHECK(g_bg_dead == 1 && strcmp(g_bg_dead_app, "dev.test.crash") == 0 &&
|
||
WIFSIGNALED(g_bg_dead_status) && WTERMSIG(g_bg_dead_status) == SIGKILL,
|
||
"崩溃子进程被 tick 回收 + on_bg_dead(SIGKILL) 回调");
|
||
CHECK(meow_bg_count() == 0, "崩溃回收后清槽");
|
||
|
||
/* ---- 4) 卡拔例外:on_card=1 + 卡不在 → SIGTERM 回收 ---- */
|
||
g_bg_dead = 0;
|
||
kid = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.card", kid, -1, 1, 0) == 0, "bg_add(on_card=1)");
|
||
g_card = 0;
|
||
pump_bg_until(cond_bg_dead, NULL);
|
||
CHECK(g_bg_dead == 1 && strcmp(g_bg_dead_app, "dev.test.card") == 0,
|
||
"卡拔 → 卡上后台 App SIGTERM 回收(防 SIGBUS)");
|
||
g_card = 1;
|
||
|
||
/* ---- 5) 表满:LRU 回收最久未激活的未声明槽 / 全常驻满→NOMEM / 重复 / 参数 ---- */
|
||
{
|
||
pid_t k[MEOW_BG_MAX];
|
||
char id[MEOW_APP_ID_MAX];
|
||
int i;
|
||
pid_t gextra = 0;
|
||
for (i = 0; i < MEOW_BG_MAX; i++) {
|
||
k[i] = spawn_sleeper();
|
||
snprintf(id, sizeof id, "dev.test.lru%d", i);
|
||
CHECK(meow_bg_add(id, k[i], -1, 0, 0) == 0, "lru 填表(未声明 keepalive=0)");
|
||
}
|
||
/* 5.1 表满 + 全未声明 → 新增 LRU 回收最旧(lru0,bg_since_ms 最小),新 App 入表成功 */
|
||
{
|
||
pid_t e2 = spawn_sleeper();
|
||
gextra = e2;
|
||
CHECK(meow_bg_add("dev.test.lrunew", e2, -1, 0, 0) == 0,
|
||
"表满全未声明 → LRU 回收最旧槽并成功新增");
|
||
CHECK(meow_bg_count() == MEOW_BG_MAX, "回收后仍满 4 槽");
|
||
CHECK(meow_bg_is_backgrounded("dev.test.lrunew") == 1, "新增 App 在后台表");
|
||
CHECK(meow_bg_is_backgrounded("dev.test.lru0") == 0, "最久未激活未声明 lru0 被回收摘表");
|
||
CHECK(kill(k[0], 0) < 0, "lru0 进程已终止(SIGTERM 真退回收)");
|
||
}
|
||
/* 5.2 重复 add → EXISTS;pid<=0 → INVAL(lru0 已回收,表内为 lrunew/lru1/lru2/lru3) */
|
||
CHECK(meow_bg_add("dev.test.lrunew", 999999, -1, 0, 0) == MEOW_ERR_EXISTS, "重复 app_id → EXISTS");
|
||
CHECK(meow_bg_add("dev.test.lrunew", 0, -1, 0, 0) == MEOW_ERR_INVAL, "pid<=0 → INVAL");
|
||
/* 5.3 清空剩余(take + 杀进程防泄漏) */
|
||
for (i = 1; i < MEOW_BG_MAX; i++) {
|
||
snprintf(id, sizeof id, "dev.test.lru%d", i);
|
||
meow_bg_take(id, NULL, NULL);
|
||
kill(k[i], SIGKILL); waitpid(k[i], NULL, 0);
|
||
}
|
||
meow_bg_take("dev.test.lrunew", NULL, NULL);
|
||
kill(gextra, SIGKILL); waitpid(gextra, NULL, 0);
|
||
CHECK(meow_bg_count() == 0, "清空后 bg 表空(未声明)");
|
||
}
|
||
|
||
/* ---- 5b) 表满 + 全为声明常驻(keepalive=1)→ 拒绝新入 NOMEM(不回收声明常驻,保安全) ---- */
|
||
{
|
||
pid_t d[MEOW_BG_MAX];
|
||
char id[MEOW_APP_ID_MAX];
|
||
int i;
|
||
for (i = 0; i < MEOW_BG_MAX; i++) {
|
||
d[i] = spawn_sleeper();
|
||
snprintf(id, sizeof id, "dev.test.dec%d", i);
|
||
CHECK(meow_bg_add(id, d[i], -1, 0, 1) == 0, "声明常驻填表(keepalive=1)");
|
||
}
|
||
{
|
||
pid_t e2 = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.decnew", e2, -1, 0, 0) == MEOW_ERR_NOMEM,
|
||
"全声明常驻满 → 新增 NOMEM(不回退真退,保安全)");
|
||
kill(e2, SIGKILL); waitpid(e2, NULL, 0); /* 未入表,就地清理 */
|
||
}
|
||
for (i = 0; i < MEOW_BG_MAX; i++) {
|
||
snprintf(id, sizeof id, "dev.test.dec%d", i);
|
||
meow_bg_take(id, NULL, NULL);
|
||
kill(d[i], SIGKILL); waitpid(d[i], NULL, 0);
|
||
}
|
||
CHECK(meow_bg_count() == 0, "声明常驻清空后 bg 表空");
|
||
}
|
||
|
||
/* ================= 批①+宽限:宽限超时回收 / 声明常驻与媒体活跃不清 ================= */
|
||
g_ms = 100000; /* 本组从干净时间基准起(后续 svc 段用相对 g_ms,不受影响) */
|
||
|
||
/* 5c) 未声明 + 媒体不活跃:grace 内保活;恰等 MEOW_BG_GRACE_MS 不回收(严格 >);超界回收 */
|
||
{
|
||
pid_t s1 = spawn_sleeper();
|
||
g_bg_dead = 0;
|
||
CHECK(meow_bg_add("dev.test.grace1", s1, -1, 0, 0) == 0, "grace1 入表(未声明 keepalive=0)");
|
||
g_ms = 100000u + MEOW_BG_GRACE_MS; /* 恰好等于宽限(非严格大于) */
|
||
meow_bg_tick();
|
||
CHECK(meow_bg_is_backgrounded("dev.test.grace1") == 1 && g_bg_dead == 0,
|
||
"grace 边界:恰等 MEOW_BG_GRACE_MS 不回收(严格 > 才回收)");
|
||
g_ms = 100000u + MEOW_BG_GRACE_MS + 1; /* 超界 1ms */
|
||
pump_bg_until(cond_bg_dead, NULL);
|
||
CHECK(meow_bg_is_backgrounded("dev.test.grace1") == 0 && meow_bg_count() == 0,
|
||
"超 grace 未再启动 → 回收摘表");
|
||
CHECK(g_bg_dead == 1, "宽限回收触发 on_bg_dead(通知 amsd)+ 进程经 SIGTERM 退出");
|
||
}
|
||
|
||
/* 5d) 声明常驻(keepalive=1)超 grace 不清(无限期) */
|
||
{
|
||
pid_t k1 = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.gracekeep", k1, -1, 0, 1) == 0, "gracekeep 入表(keepalive=1)");
|
||
g_ms = 100000u + MEOW_BG_GRACE_MS + 100000;
|
||
meow_bg_tick();
|
||
CHECK(meow_bg_is_backgrounded("dev.test.gracekeep") == 1,
|
||
"声明常驻 keepalive=1 超 grace 不清(无限期)");
|
||
meow_bg_take("dev.test.gracekeep", NULL, NULL);
|
||
kill(k1, SIGKILL); waitpid(k1, NULL, 0);
|
||
}
|
||
|
||
/* 5e) 媒体活跃(META PLAYING,keepalive=0)超 grace 不清(resident 保护) */
|
||
{
|
||
int sp2[2];
|
||
pid_t m1;
|
||
CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sp2) == 0, "grace media socketpair");
|
||
{
|
||
m_meta_t meta;
|
||
memset(&meta, 0, sizeof meta);
|
||
snprintf(meta.app_id, sizeof meta.app_id, "dev.test.gracemedia");
|
||
meta.state = MEOW_MEDIA_PLAYING;
|
||
meow_media_meta(&meta, sp2[0]);
|
||
}
|
||
m1 = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.gracemedia", m1, -1, 0, 0) == 0,
|
||
"gracemedia 入表(keepalive=0 但媒体活跃)");
|
||
g_ms = 100000u + MEOW_BG_GRACE_MS + 100000;
|
||
meow_bg_tick();
|
||
CHECK(meow_media_app_resident("dev.test.gracemedia") == 1, "gracemedia media_resident=1");
|
||
CHECK(meow_bg_is_backgrounded("dev.test.gracemedia") == 1,
|
||
"媒体活跃超 grace 不清(resident 保护)");
|
||
meow_bg_take("dev.test.gracemedia", NULL, NULL);
|
||
kill(m1, SIGKILL); waitpid(m1, NULL, 0);
|
||
meow_media_clear("dev.test.gracemedia");
|
||
close(sp2[0]); close(sp2[1]);
|
||
}
|
||
|
||
/* ================= 无头后台服务 ================= */
|
||
write_test_svc();
|
||
|
||
/* ---- 6) 注册校验 ---- */
|
||
CHECK(meow_svc_register("dev.test.svc", "daemon", "/nonexistent/x", 1, 2, 60000, 0, -1)
|
||
== MEOW_ERR_NOENT, "exec 不可执行 → NOENT(不留尸槽)");
|
||
CHECK(meow_svc_find("dev.test.svc", "daemon") == NULL, "NOENT 后无槽残留");
|
||
|
||
int sp_svc[2];
|
||
CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sp_svc) == 0, "svc socketpair 建立");
|
||
int pid1 = meow_svc_register("dev.test.svc", "daemon", TEST_SVC_PATH,
|
||
1, 2, 60000, 0, sp_svc[0]);
|
||
CHECK(pid1 > 0, "register 拉起服务");
|
||
CHECK(meow_svc_register("dev.test.svc", "daemon", TEST_SVC_PATH, 1, 2, 60000, 0, sp_svc[0])
|
||
== MEOW_ERR_EXISTS, "同名服务在跑 → EXISTS");
|
||
|
||
/* ---- 7) 退避 1s/2s + max=2 熔断 ---- */
|
||
const meow_svc_rt_t *sl;
|
||
kill(pid1, SIGKILL);
|
||
pump_svc_until(cond_svc_reaped, NULL);
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid == 0 && sl->restart_count == 1 &&
|
||
sl->next_retry_ms == g_ms + MEOW_SVC_BACKOFF_1,
|
||
"第 1 次死 → 1s 退避排程(restart_count=1)");
|
||
g_ms += MEOW_SVC_BACKOFF_1;
|
||
meow_svc_tick();
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid > 0 && sl->pid != pid1, "到点重启:新 pid");
|
||
|
||
pid_t pid2 = sl->pid;
|
||
kill(pid2, SIGKILL);
|
||
pump_svc_until(cond_svc_reaped, NULL);
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid == 0 && sl->restart_count == 2 &&
|
||
sl->next_retry_ms == g_ms + MEOW_SVC_BACKOFF_2,
|
||
"第 2 次死 → 2s 退避排程(restart_count=2)");
|
||
g_ms += MEOW_SVC_BACKOFF_2;
|
||
meow_svc_tick();
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid > 0, "第 2 次重启拉起");
|
||
|
||
g_notif_cleared = 0;
|
||
kill(sl->pid, SIGKILL);
|
||
pump_svc_until(cond_svc_reaped, NULL);
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid == 0 && sl->circuit == 1 && sl->restart_count == 3,
|
||
"第 3 次死超 max_restart=2 → 熔断 circuit=1");
|
||
CHECK(g_notif_cleared == 1 && strcmp(g_notif_cleared_app, "dev.test.svc") == 0,
|
||
"熔断清该 App 通知(notif_clear_app 回调)");
|
||
{
|
||
uint16_t t = 0;
|
||
m_sys_t m;
|
||
int n = read_frame(sp_svc[1], &t, &m, sizeof m);
|
||
CHECK(n == (int)sizeof m && t == MSG_SYS && m.event == MEOW_SYS_SVC_DEAD &&
|
||
strcmp(m.svc, "daemon") == 0,
|
||
"注册连接收到 MSG_SYS{SVC_DEAD=2} 熔断广播");
|
||
}
|
||
g_ms += 60000;
|
||
meow_svc_tick();
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid == 0, "熔断后不再拉起");
|
||
|
||
/* 重注册复位熔断 */
|
||
int pid4 = meow_svc_register("dev.test.svc", "daemon", TEST_SVC_PATH,
|
||
1, 2, 60000, 0, sp_svc[0]);
|
||
CHECK(pid4 > 0, "熔断槽重注册 → 复位重新拉起");
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->circuit == 0 && sl->restart_count == 0, "熔断复位(circuit/count 清零)");
|
||
|
||
/* ---- 8) SERVICE_STOP:不重启 ---- */
|
||
CHECK(meow_svc_stop("dev.test.svc", "daemon") == 0, "svc_stop 受理");
|
||
pump_svc_until(cond_svc_gone, NULL);
|
||
CHECK(meow_svc_find("dev.test.svc", "daemon") == NULL, "stop 后槽释放(不重启)");
|
||
CHECK(meow_svc_stop("dev.test.svc", "daemon") == MEOW_ERR_NOENT, "重复 stop → NOENT");
|
||
close(sp_svc[0]); close(sp_svc[1]);
|
||
|
||
/* ---- 9) kill_all:全回收 + SHUTDOWN 广播 ---- */
|
||
{
|
||
int sp2[2];
|
||
socketpair(AF_UNIX, SOCK_STREAM, 0, sp2);
|
||
int spid = meow_svc_register("dev.test.svc", "daemon", TEST_SVC_PATH,
|
||
1, 3, 60000, 0, sp2[0]);
|
||
pid_t bgk = spawn_sleeper();
|
||
meow_bg_add("dev.test.bg9", bgk, -1, 0, 0);
|
||
CHECK(spid > 0 && meow_bg_count() == 1, "kill_all 前置:1 svc + 1 bg");
|
||
meow_bgplay_kill_all();
|
||
CHECK(meow_bg_count() == 0 && meow_svc_find("dev.test.svc", "daemon") == NULL,
|
||
"kill_all 后 bg/svc 全回收");
|
||
CHECK(kill(spid, 0) < 0 && kill(bgk, 0) < 0, "子进程确已终止");
|
||
uint16_t t = 0;
|
||
m_sys_t m;
|
||
int n = read_frame(sp2[1], &t, &m, sizeof m);
|
||
CHECK(n == (int)sizeof m && t == MSG_SYS && m.event == MEOW_SYS_SHUTDOWN,
|
||
"注册连接收到 MSG_SYS{SHUTDOWN=1}(关机前回收广播)");
|
||
close(sp2[0]); close(sp2[1]);
|
||
}
|
||
|
||
unlink(TEST_SVC_PATH);
|
||
printf(g_fails ? "== FAILURES: %d ==\n" : "== ALL PASS ==\n", g_fails);
|
||
return g_fails ? 1 : 0;
|
||
}
|