/* * test_notify_integration.c — 宿主集成测试:meow_notify_send/cancel 全链路(任务 #5)。 * * 直接把 meow_ui_notify.c + meow_notify_ipc.c 编进来,仅 stub 两个 SDK 能力 * (meow_log 空实现 / meow_ui_time_ms 单调毫秒),用本地 AF_UNIX stub socket 回环: * * S1 send(general,timeout=0) → 返回本地 id(1),stub 回 nid=101,active_count=1; * S2 send(media) → media=1、category=media(经 stub 捕获帧校验); * S3 同类型再发(未满配额)→ 返回本地 id(3); * S4 cancel(本地id=1) → 返回 1,stub 收到 MSG_NOTIFY_DISMISS(负载 nid=101); * S5 配额满(每类型4)后发第 5 条 → 丢最旧:该 5 条 send 的 replaces_id == 被丢最旧的 nid; * S6 无 launcher(connect 失败)→ send 仍返回本地 id(>0)、不崩;cancel 也静默不崩。 * * 编译(宿主 gcc): * gcc -o test_notify_integration test_notify_integration.c \ * meow_ui_notify.c meow_notify_ipc.c -I. -I../meow_launcherd -lpthread */ #include "meow_ui.h" #include "meow_notify_ipc.h" #include "meow_ipc.h" #include #include #include #include #include #include #include #include #include #include /* ---- stub SDK 依赖(避免链整库) ---- */ void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl; (void)fmt; } uint64_t meow_ui_time_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; } /* ---- stub server 捕获(每次连接一帧) ---- */ #define MAXCAP 16 typedef struct { int type; m_notify_t n; m_notify_progress_t prog; uint32_t dismiss_id; uint32_t nid; } srec_t; static srec_t g_rec[MAXCAP]; static int g_nrec = 0; static int g_stop = 0; static int g_listen_fd = -1; static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; static void *server(void *arg) { (void)arg; while (!g_stop) { int c = accept(g_listen_fd, NULL, NULL); if (c < 0) { if (errno == EAGAIN && g_stop) break; /* 非阻塞:listener 被关闭 */ if (errno == EBADF) break; usleep(1000); continue; } uint8_t hdr[MEOW_IPC_HDR_LEN]; size_t off = 0; while (off < sizeof hdr) { ssize_t r = read(c, hdr + off, sizeof hdr - off); if (r <= 0) break; off += (size_t)r; } if (off < sizeof hdr) { close(c); continue; } uint32_t type, len, req_id; memcpy(&req_id, hdr + 8, 4); memcpy(&len, hdr + 12, 4); memcpy(&type, hdr + 6, 2); if (len > MEOW_IPC_MAX) { close(c); continue; } uint8_t pay[MEOW_IPC_MAX]; off = 0; while (off < len) { ssize_t r = read(c, pay + off, len - off); if (r <= 0) break; off += (size_t)r; } pthread_mutex_lock(&g_mu); int idx = g_nrec < MAXCAP ? g_nrec++ : (MAXCAP - 1); memset(&g_rec[idx], 0, sizeof g_rec[idx]); g_rec[idx].type = (int)type; if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_rec[idx].n, pay, sizeof(m_notify_t)); if (type == MSG_NOTIFY_PROGRESS && len >= sizeof(m_notify_progress_t)) memcpy(&g_rec[idx].prog, pay, sizeof(m_notify_progress_t)); if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_rec[idx].dismiss_id, pay, 4); /* 分配 nid = 100 + NOTIFY 序号 */ uint32_t seq = 0; for (int i = 0; i <= idx; i++) if (g_rec[i].type == MSG_NOTIFY) seq++; g_rec[idx].nid = (type == MSG_NOTIFY) ? (100 + seq) : 0; uint32_t ack_nid = g_rec[idx].nid; pthread_mutex_unlock(&g_mu); if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS || type == MSG_NOTIFY_PROGRESS) { m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; a.status = ack_nid; uint8_t rhdr[MEOW_IPC_HDR_LEN]; uint32_t mag = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION, t = (uint16_t) (type == MSG_NOTIFY ? MSG_NOTIFY_ACK : MSG_ACK); uint32_t rl = sizeof a; memcpy(rhdr+0,&mag,4); memcpy(rhdr+4,&ver,2); memcpy(rhdr+6,&t,2); memcpy(rhdr+8,&req_id,4); memcpy(rhdr+12,&rl,4); size_t w0 = 0; while (w0 < sizeof rhdr) { ssize_t w = write(c, rhdr+w0, sizeof rhdr-w0); if (w<=0) break; w0 += (size_t)w; } size_t w1 = 0; while (w1 < rl) { ssize_t w = write(c, (uint8_t*)&a+w1, rl-w1); if (w<=0) break; w1 += (size_t)w; } } close(c); } return NULL; } static int g_listen_made = 0; static int make_listen(char *path, size_t n) { snprintf(path, n, "/tmp/meow_notify_intg_%d.sock", (int)getpid()); unlink(path); int fd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX; strncpy(a.sun_path, path, sizeof a.sun_path - 1); if (bind(fd, (struct sockaddr*)&a, sizeof a) < 0 || listen(fd, 4) < 0) { close(fd); return -1; } fcntl(fd, F_SETFL, O_NONBLOCK); /* 非阻塞 listener:accept() 可被 g_stop 截断 */ g_listen_fd = fd; g_listen_made = 1; return 0; } static void stop_server(void) { if (g_listen_made) { g_stop = 1; close(g_listen_fd); g_listen_made = 0; } } static srec_t rec_at(int i) { srec_t r; pthread_mutex_lock(&g_mu); r = g_rec[i]; pthread_mutex_unlock(&g_mu); return r; } static int failures = 0; static void check(const char *name, int cond) { printf("%s %s\n", cond ? "PASS" : "FAIL", name); if (!cond) failures++; } int main(void) { setenv("MEOW_LOG_APP", "intgapp", 1); char sock[108]; if (make_listen(sock, sizeof sock) != 0) { fprintf(stderr, "cannot listen\n"); return 2; } setenv("MEOW_IPC_SOCK", sock, 1); pthread_t th; pthread_create(&th, NULL, server, NULL); /* S1 通用通知 */ meow_notify nt; memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.urgency = MEOW_NOTIFY_URG_NORMAL; nt.title = "t1"; nt.timeout_ms = 0; nt.icon = "os.meow.hello/icon.png"; uint32_t id1 = meow_notify_send(&nt); check("S1 send 返回本地 id=1", id1 == 1); check("S1 active_count=1", meow_notify_active_count() == 1); check("S1 stub 回 nid=101", rec_at(0).nid == 101); check("S1 映射 urgency=NORMAL(1)", rec_at(0).n.urgency == MEOW_NOTIFY_URG_NORMAL); check("S1 映射 icon→app_icon", !strcmp(rec_at(0).n.app_icon, "os.meow.hello/icon.png")); /* S2 媒体通知:单例 + media=1 */ memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.title = "music"; nt.timeout_ms = -1; uint32_t id2 = meow_notify_send(&nt); check("S2 媒体返本地 id=2", id2 == 2); check("S2 category=media", !strcmp(rec_at(1).n.category, "media")); check("S2 media=1", rec_at(1).n.media == 1); check("S2 expire=0xFFFFFFFF", rec_at(1).n.expire_timeout == 0xFFFFFFFFu); /* S3 同类型再发(general 未满 4)→ 新本地 id */ memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.title = "t3"; nt.timeout_ms = 0; uint32_t id3 = meow_notify_send(&nt); check("S3 再发返本地 id=3", id3 == 3); check("S3 第2条general nid=103", rec_at(2).nid == 103); /* S4 cancel(id1) → DISMISS nid=101 */ int rc = meow_notify_cancel(id1); check("S4 cancel 返回 1", rc == 1); check("S4 stub 收到 MSG_NOTIFY_DISMISS", rec_at(3).type == MSG_NOTIFY_DISMISS); check("S4 dismiss 负载 nid=101", rec_at(3).dismiss_id == 101); check("S4 本地表已撤(count=2)", meow_notify_active_count() == 2); /* S5 配额满 → 丢最旧 + replaces_id 复用被丢 nid。 当前 general 有 id3(1 条);再发 4 条到 4 条(第 4 条触顶后丢最旧)。 */ for (int i = 0; i < 4; i++) { memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.title = "quota"; nt.timeout_ms = 0; (void)meow_notify_send(&nt); } /* 现在 general 应仍有 4 条(第5条丢最旧 id3=本地,即被降级替换) */ check("S5 配额满后每类型仍=4", meow_notify_count_in_type(MEOW_NOTIFY_CAT_GENERAL) == 4); /* 被丢最旧 = id3(第2条 general,本地 id=3),其 launcher nid=103(rec[2])。 触发丢最旧的是 S5 循环第 4 次 send = 第 7 条 NOTIFY(rec[7]),其 replaces_id==103。 */ check("S5 丢最旧后 send replaces_id=被丢nid(103)", rec_at(7).n.replaces_id == 103); /* S5b update_progress:新发一条 progress → 本地 id → update → stub 捕获 MSG_NOTIFY_PROGRESS */ memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_PROGRESS; nt.title = "prog"; nt.timeout_ms = 0; nt.progress = 10; uint32_t idp = meow_notify_send(&nt); int ni = -1; for (int i = 0; i < MAXCAP; i++) { srec_t r = rec_at(i); if (r.type == MSG_NOTIFY && !strcmp(r.n.summary, "prog")) { ni = i; break; } } check("S5b 找到 prog 通知帧", ni >= 0); int rc_up = meow_notify_update_progress(idp, 70); check("S5b update_progress 返回 1", rc_up == 1); srec_t pr = rec_at(ni + 1); check("S5b stub 捕获 MSG_NOTIFY_PROGRESS 帧", pr.type == MSG_NOTIFY_PROGRESS); check("S5b progress nid=该通知 launcher nid", ni >= 0 && pr.prog.nid == rec_at(ni).nid); check("S5b progress 载=70", pr.prog.progress == 70); check("S5b update_progress(未知 id) 返回 0", meow_notify_update_progress(0xFFFFFF, 50) == 0); stop_server(); pthread_join(th, NULL); /* S6 无 launcher 降级 */ char dead[108]; snprintf(dead, sizeof dead, "/tmp/meow_notify_intg_dead_%d.sock", (int)getpid()); unlink(dead); setenv("MEOW_IPC_SOCK", dead, 1); memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_EMAIL; nt.title = "no-launcher"; nt.timeout_ms = 0; uint32_t id6 = meow_notify_send(&nt); check("S6 无 launcher 仍返回本地 id(>0)", id6 > 0); check("S6 无 launcher active_count 正常", meow_notify_active_count() >= 1); check("S6 cancel 无 launcher 静默不崩", meow_notify_cancel(id6) == 1); printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", failures, failures == 1 ? "" : "s"); return failures ? 1 : 0; }