Files

492 lines
22 KiB
C
Raw Permalink 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_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;
}