test(ncm): M10 宿主回归三套(44 断言)+ 构建脚本 + REPORT §42

- bench/retry_storm_sim.c(25 断言):真实 retry_ctl.c/meta_gate.c,100ms tick 断网
  120s→4 次/间隔 ≥30s;状态每 tick 抖动→2 次;时钟回退→不立即重试且间隔仍 ≥30s;
  worker 45s>间隔→不并发;META 空闲 3 帧/60s(旧 600)、播放 13 帧(≤1/5s)、
  状态抖动 ≤4 帧/秒、元数据变化/seek 立即报、超长标题截断不重报、回退不连发。
- bench/home_retry_sim.c(13 断言):真实 app/ui/home.c 的 home_tick 编进宿主
  (--gc-sections + 桩),断网 120s→4 次重试、单飞、全帧重绘 0 次;
  把真机形态的垃圾 64 位 now 喂给 home_tick 仍为 4 次/≥30s;回在线再断网不清基准。
- bench/meta_ipc_sim.c(6 断言):真实 ipc_media 线程 + 假 launcher AF_UNIX socket,
  空闲 2s 只收到 1 帧 META(旧 ~10 帧),状态翻转恰好多 1 帧,帧头合法。
- build_ncm.sh / build_ncm_host.sh:源列表加 retry_ctl.c、meta_gate.c(host 补
  mem_watch.c,否则 player_manager 链接失败);build_ncm_sims.sh 加三个 sim。
- REPORT §42:真机日志证据(时间戳低/高 32 位拆解、21016 帧 META)、根因三链、
  实现、验证(session_gate 88 / http_truncate 13 / core 139 全过)、真机验证建议。
This commit is contained in:
AskaEth committed 2026-11-05 13:00:32 +08:00
1 parent 7f4e18b0ed
commit 294ed6ffc7
7 files changed
+938 -5

No files matched your search

+235
View File
@@ -0,0 +1,235 @@
/* bench/meta_ipc_sim.c — M10:真实 app/play/ipc_media.c 的 META 上报频率回归(假 launcher socket)。
*
* 真机证据:launcher meow_20261105_122541.log 单次启动 21016 条
* `ipc: META app=dev.player.ncm state=0 pos=0/0 rc=0 -> ACK`,间隔 ~100ms。
* 旧 ipc_media 用 `now(meow_ui_time_ms) - last_meta >= 1000` 做 1s 心跳;真机该值高 32 位是垃圾
* (REPORT §42)→ 条件恒真 → 每个 IPC 轮次(200ms poll)都发一帧。
*
* 本测试把真实 ipc_media.c 编进宿主(--gc-sections 只保留 ipc_media_start 调用链),
* 在临时目录起一个 AF_UNIX “假 launcher”,统计 App 真正发出的 MSG_META 帧数:
* A. 空闲(无播放、无元数据)2s → 只应有 1 帧(注册媒体源),旧实现为 ~10 帧;
* B. 播放态翻转一次 → 只多 1 帧(状态变化立即上报),之后持续播放 2s 不再刷屏;
* C. 帧格式合法(magic/version/app_id),且没有 MSG_META 以外的上行帧。
*
* 编译(ncm_app 目录下):
* gcc -O2 -Wall -Wextra -ffunction-sections -fdata-sections -DMEOW_UI_HOST_TEST -DJSMN_HEADER \
* -I. -I../sdk -I../sdk/include -I../meow_launcherd -Icore -Ivendor/jsmn \
* -c app/play/ipc_media.c -o out/chk/ipc_m10.o
* gcc -O2 -Wall -Wextra -ffunction-sections -fdata-sections -DMEOW_UI_HOST_TEST -DJSMN_HEADER \
* -I. -I../sdk -I../sdk/include -I../meow_launcherd -Icore -Ivendor/jsmn \
* -c app/play/meta_gate.c -o out/chk/meta_gate_m10.o
* gcc -O2 -Wall -Wextra -ffunction-sections -fdata-sections -DMEOW_UI_HOST_TEST -DJSMN_HEADER \
* -I. -I../sdk -I../sdk/include -I../meow_launcherd -Icore -Ivendor/jsmn \
* -c bench/meta_ipc_sim.c -o out/chk/meta_ipc_sim.o
* gcc -Wl,--gc-sections -o meta_ipc_sim out/chk/ipc_m10.o out/chk/meta_gate_m10.o \
* out/chk/meta_ipc_sim.o -lpthread
* ./meta_ipc_sim
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <time.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include "../app/ncm_app_internal.h"
#include "meow_ipc.h"
/* ---- app.c 全局 ---- */
ncm_app_state_t g_st;
ncm_play_run_t g_play = { .mu = PTHREAD_MUTEX_INITIALIZER,
.state = NCM_PC_STOPPED, .cur_song = -1 };
volatile int g_app_quit = 0;
volatile int g_net_ready = 0;
int g_host_mock = 0;
static int g_pass, g_fail;
#define CHECK(cond, ...) do { \
int _ok = (cond) ? 1 : 0; \
printf(" %s ", _ok ? "PASS" : "FAIL"); printf(__VA_ARGS__); printf("\n"); \
if (_ok) g_pass++; else g_fail++; \
} while (0)
/* ---- 播放器桩(本测试只关心「有没有播放/元数据变化」) ---- */
static volatile int g_pm_state = NCM_PC_STOPPED;
int player_manager_state(void) { return g_pm_state; }
int player_manager_playing_song(void) { return -1; }
int player_manager_current_song(ncm_song_t *out) { (void)out; return -1; }
void player_manager_pause(int on) { g_pm_state = on ? NCM_PC_PAUSED : NCM_PC_PLAYING; }
void player_manager_next(int dir) { (void)dir; }
void player_manager_seek(int64_t ms) { (void)ms; }
const char *cover_current_path(void) { return ""; }
int lyric_count(void) { return 0; }
/* ---- 日志/时间桩 ---- */
static volatile int g_meta_logs;
void meow_log(meow_log_level lvl, const char *fmt, ...)
{
(void)fmt;
if (lvl == MEOW_LOG_DEBUG) g_meta_logs++;
}
int64_t ncm_monotonic_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
size_t ncm_strlcpy(char *dst, const char *src, size_t cap)
{
size_t n = 0;
if (!dst || cap == 0) return src ? strlen(src) : 0;
if (src) { for (; src[n] && n + 1 < cap; n++) dst[n] = src[n]; }
dst[n] = 0;
return n;
}
/* ============================================================
* 假 launcher:AF_UNIX 服务端 + 帧计数
* ============================================================ */
static int g_srv = -1, g_cli = -1;
static char g_sock_path[256];
static int g_bad_frames;
static char g_last_app_id[64];
static void set_play_state(int st)
{
pthread_mutex_lock(&g_play.mu);
g_play.state = st; /* META 的 state 取自 g_play(与真实播放线程同源) */
g_play.err = 0;
pthread_mutex_unlock(&g_play.mu);
}
static void put_hdr(uint8_t *h, uint16_t type, uint32_t req, uint32_t len)
{
uint32_t magic = MEOW_IPC_MAGIC;
uint16_t ver = MEOW_IPC_VERSION;
memcpy(h + 0, &magic, 4); memcpy(h + 4, &ver, 2);
memcpy(h + 6, &type, 2); memcpy(h + 8, &req, 4);
memcpy(h + 12, &len, 4);
}
static int srv_open(void)
{
snprintf(g_sock_path, sizeof g_sock_path, "%s/m10_ipc.sock",
getenv("TMPDIR") && getenv("TMPDIR")[0] ? getenv("TMPDIR") : "/tmp");
unlink(g_sock_path);
g_srv = socket(AF_UNIX, SOCK_STREAM, 0);
if (g_srv < 0) return -1;
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
snprintf(a.sun_path, sizeof a.sun_path, "%s", g_sock_path);
if (bind(g_srv, (struct sockaddr *)&a, sizeof a) != 0) return -1;
if (listen(g_srv, 1) != 0) return -1;
setenv("MEOW_IPC_SOCK", g_sock_path, 1);
return 0;
}
/* 收一帧(带超时)。返回帧长(负载),*type 出帧类型;-1=超时/断开。 */
static int srv_read(int timeout_ms, uint16_t *type, uint8_t *buf, int cap)
{
struct pollfd p = { .fd = g_cli, .events = POLLIN };
if (poll(&p, 1, timeout_ms) <= 0) return -1;
uint8_t hdr[MEOW_IPC_HDR_LEN];
size_t off = 0;
while (off < sizeof hdr) {
ssize_t r = read(g_cli, hdr + off, sizeof hdr - off);
if (r <= 0) return -1;
off += (size_t)r;
}
uint32_t magic, len; uint16_t ver;
memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2);
memcpy(type, hdr + 6, 2); memcpy(&len, hdr + 12, 4);
if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION || len > (uint32_t)cap) {
g_bad_frames++;
return -1;
}
off = 0;
while (off < len) {
ssize_t r = read(g_cli, buf + off, len - off);
if (r <= 0) return -1;
off += (size_t)r;
}
return (int)len;
}
/* 收 HELLO 并回一帧 ACK(ipc_thread 等这一帧才进入主循环)。 */
static int srv_handshake(void)
{
struct pollfd p = { .fd = g_srv, .events = POLLIN };
if (poll(&p, 1, 2000) <= 0) return -1;
g_cli = accept(g_srv, NULL, NULL);
if (g_cli < 0) return -1;
uint8_t buf[MEOW_IPC_MAX]; uint16_t type = 0;
if (srv_read(2000, &type, buf, sizeof buf) < 0) return -1;
if (type != MSG_HELLO) return -1;
m_hello_t hh;
memset(&hh, 0, sizeof hh);
memcpy(&hh, buf, sizeof hh < sizeof buf ? sizeof hh : sizeof buf);
snprintf(g_last_app_id, sizeof g_last_app_id, "%s", hh.app_id);
uint8_t out[MEOW_IPC_HDR_LEN + sizeof(m_ack_t)];
m_ack_t ack; memset(&ack, 0, sizeof ack); ack.code = MEOW_OK;
put_hdr(out, MSG_ACK, 1, (uint32_t)sizeof ack);
memcpy(out + MEOW_IPC_HDR_LEN, &ack, sizeof ack);
return (write(g_cli, out, sizeof out) == (ssize_t)sizeof out) ? 0 : -1;
}
/* 收 run_ms 内的全部上行帧,按类型计数。 */
static void srv_collect(int run_ms, int *meta, int *other)
{
int64_t end = ncm_monotonic_ms() + run_ms;
uint8_t buf[MEOW_IPC_MAX];
*meta = *other = 0;
while (ncm_monotonic_ms() < end) {
uint16_t type = 0;
int n = srv_read(50, &type, buf, sizeof buf);
if (n < 0) continue;
if (type == MSG_META) (*meta)++;
else (*other)++;
}
}
int main(void)
{
printf("meta_ipc_sim — M10 真实 ipc_media 线程 META 帧频回归(REPORT §42)\n\n");
if (srv_open() != 0) { printf(" FAIL 假 launcher socket 建立失败 (%s)\n", g_sock_path); return 1; }
if (ipc_media_start() != 0) { printf(" FAIL ipc_media_start\n"); return 1; }
if (srv_handshake() != 0) { printf(" FAIL HELLO 握手失败\n"); return 1; }
printf("A. 空闲(state=0,无播放/无元数据)2s\n");
int meta = 0, other = 0;
srv_collect(2000, &meta, &other);
CHECK(meta == 1, "上行 META 帧 = %d(期望 1:注册媒体源;旧实现 ~10 帧/2s)", meta);
CHECK(other == 0, "无其它上行帧(%d)", other);
CHECK(strcmp(g_last_app_id, "dev.player.ncm") == 0, "HELLO app_id = %s", g_last_app_id);
printf("B. 播放态变化(0 → PLAYING)后继续空闲 2s\n");
set_play_state(NCM_PC_PLAYING);
int meta2 = 0, other2 = 0;
srv_collect(2000, &meta2, &other2);
CHECK(meta2 == 1, "状态变化只上报 1 帧(%d)", meta2);
/* 停止播放:又是一次状态变化 → 再 1 帧 */
set_play_state(NCM_PC_STOPPED);
int meta3 = 0, other3 = 0;
srv_collect(2000, &meta3, &other3);
CHECK(meta3 == 1, "停止播放只上报 1 帧(%d)", meta3);
CHECK(g_bad_frames == 0, "帧头 magic/version/长度全部合法(坏帧 %d)", g_bad_frames);
ipc_media_stop();
g_app_quit = 1;
usleep(400 * 1000); /* 让 ipc 线程在 poll 超时内退出 */
if (g_cli >= 0) close(g_cli);
if (g_srv >= 0) close(g_srv);
unlink(g_sock_path);
printf("\nmeta_ipc_sim: %d-%d\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}