/* test_media.c — 宿主单测:meow_media 媒体会话(阶段D附录B)。 * * 验证: * 1) META 上报 → 注册会话,current()=最近上报者; * 2) 最近活跃切换(两个源,后上报者胜); * 3) has_source()(state=0 停止 → 0); * 4) PLAYCTL 下发字节格式(socketpair 对端可读回 MSG_PLAYCTL 帧,cmd/seek 正确); * 5) meow_media_playctl 无源 → -1; * 6) clear(app) 清会话 → current()=NULL。 * 只依赖 meow_media.c + meow_ipc.h(纯 C),宿主 gcc 直编。 */ #include #include #include #include #include #include #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.send:把帧写到 fd(socketpair 一端)。 */ static size_t test_send(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; uint16_t ver = MEOW_IPC_VERSION; memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &type, 2); memcpy(hdr + 8, &req_id, 4); memcpy(hdr + 12, &len, 4); size_t off = 0; while (off < sizeof hdr) { ssize_t w = write(fd, hdr + off, sizeof hdr - off); if (w <= 0) return off; off += (size_t)w; } if (len && payload) { size_t p = 0; while (p < len) { ssize_t w = write(fd, (const uint8_t *)payload + p, len - p); if (w <= 0) break; p += (size_t)w; } off += p; } return off; } static void fill_meta(m_meta_t *m, const char *app, uint32_t state, uint32_t pos, uint32_t dur, const char *title, const char *artist) { memset(m, 0, sizeof *m); snprintf(m->app_id, sizeof m->app_id, "%s", app); m->state = state; m->pos_ms = pos; m->dur_ms = dur; snprintf(m->title, sizeof m->title, "%s", title); snprintf(m->artist, sizeof m->artist, "%s", artist); } /* 从 fd 读一帧(头+负载),校验 magic/ver,返回负载类型;不足返回 -1。 */ static int read_frame(int fd, uint16_t *type, uint32_t *req_id, uint8_t *rbuf, uint32_t rcap) { uint8_t rhdr[MEOW_IPC_HDR_LEN]; size_t off = 0; while (off < sizeof rhdr) { ssize_t r = read(fd, rhdr + off, sizeof rhdr - off); if (r <= 0) return -1; off += (size_t)r; } uint32_t magic, len; uint16_t ver; memcpy(&magic, rhdr, 4); memcpy(&ver, rhdr + 4, 2); memcpy(type, rhdr + 6, 2); memcpy(req_id, rhdr + 8, 4); memcpy(&len, rhdr + 12, 4); if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) return -1; if (len > rcap) len = rcap; off = 0; while (off < len) { ssize_t r = read(fd, rbuf + off, len - off); if (r <= 0) return -1; off += (size_t)r; } return (int)len; } int main(void) { meow_media_ops ops; ops.send = test_send; ops.log = NULL; /* 静默 */ meow_media_init(&ops); int sv[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) { perror("socketpair"); return 1; } /* sv[0]=会话 fd(launcher 下发端);sv[1]=测试读回端(模拟 App)。 */ /* 1) 单源注册 */ m_meta_t m; fill_meta(&m, "com.ncm", MEOW_MEDIA_PLAYING, 120000, 240000, "Song A", "Artist A"); CHECK(meow_media_meta(&m, sv[0]) == 0, "meta registers source"); const meow_media_session_t *cur = meow_media_current(); CHECK(cur && !strcmp(cur->app_id, "com.ncm"), "current() == com.ncm"); CHECK(cur && cur->state == MEOW_MEDIA_PLAYING, "state == PLAYING"); CHECK(meow_media_has_source() == 1, "has_source == 1 (playing)"); /* 2) 多源最近活跃切换 */ fill_meta(&m, "com.player2", MEOW_MEDIA_PLAYING, 0, 180000, "Song B", "Artist B"); CHECK(meow_media_meta(&m, sv[0]) == 0, "meta registers second source"); cur = meow_media_current(); CHECK(cur && !strcmp(cur->app_id, "com.player2"), "current() == com.player2 (newest)"); fill_meta(&m, "com.ncm", MEOW_MEDIA_PLAYING, 30000, 240000, "Song A", "Artist A"); meow_media_meta(&m, sv[0]); cur = meow_media_current(); CHECK(cur && !strcmp(cur->app_id, "com.ncm"), "current() == com.ncm after re-report (newest)"); /* 3) state=0 停止 → has_source=0(但会话仍在,current() 仍指向它) */ fill_meta(&m, "com.ncm", MEOW_MEDIA_STOPPED, 0, 0, "Song A", "Artist A"); meow_media_meta(&m, sv[0]); cur = meow_media_current(); CHECK(cur && !strcmp(cur->app_id, "com.ncm"), "current() still == com.ncm (stopped)"); CHECK(meow_media_has_source() == 0, "has_source == 0 (stopped)"); /* 恢复播放,准备 PLAYCTL 下发验证 */ fill_meta(&m, "com.ncm", MEOW_MEDIA_PLAYING, 1000, 240000, "Song A", "Artist A"); meow_media_meta(&m, sv[0]); /* 4) PLAYCTL 下发:cmd=1(播放),读回端应收到 MSG_PLAYCTL/第11号 req */ CHECK(meow_media_playctl(1, 0) == 0, "playctl sends (cmd=play)"); uint8_t rbuf[512]; uint16_t type; uint32_t req; int n = read_frame(sv[1], &type, &req, rbuf, sizeof rbuf); CHECK(n == (int)sizeof(m_playctl_t), "downlink frame payload == m_playctl_t size"); CHECK(type == MSG_PLAYCTL, "downlink type == MSG_PLAYCTL"); m_playctl_t c; memcpy(&c, rbuf, sizeof c); CHECK(c.cmd == 1 && c.seek_ms == 0, "downlink cmd=1 seek=0"); /* 5) seek:cmd=5 seek=12345 */ CHECK(meow_media_playctl(5, 12345) == 0, "playctl sends (cmd=seek)"); n = read_frame(sv[1], &type, &req, rbuf, sizeof rbuf); memcpy(&c, rbuf, sizeof c); CHECK(type == MSG_PLAYCTL && c.cmd == 5 && c.seek_ms == 12345, "downlink seek cmd=5 seek=12345"); /* playctl_to 目标 app(另一会话) */ CHECK(meow_media_playctl_to("com.player2", 3, 0) == 0, "playctl_to player2 sends"); n = read_frame(sv[1], &type, &req, rbuf, sizeof rbuf); memcpy(&c, rbuf, sizeof c); CHECK(type == MSG_PLAYCTL && c.cmd == 3, "downlink to player2 cmd=3"); CHECK(meow_media_playctl_to("no.such.app", 1, 0) == -1, "playctl_to unknown app == -1"); /* 6) 无源:清全部 → playctl -1 / has_source 0 */ meow_media_clear(NULL); CHECK(meow_media_current() == NULL, "current() == NULL after clear all"); CHECK(meow_media_playctl(1, 0) == -1, "playctl no source == -1"); CHECK(meow_media_has_source() == 0, "has_source == 0 after clear"); close(sv[0]); close(sv[1]); /* 7) 空 app_id 拒绝 */ fill_meta(&m, "", MEOW_MEDIA_PLAYING, 0, 0, "x", "y"); CHECK(meow_media_meta(&m, sv[0]) == -1, "meta empty app_id == -1"); printf("\n%s (%d fails)\n", g_fails ? "FAILED" : "ALL PASS", g_fails); return g_fails ? 1 : 0; }