Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/home_retry_sim.c
T
AskaEth 294ed6ffc7 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 全过)、真机验证建议。
2026-11-05 13:00:32 +08:00

231 lines
9.8 KiB
C
Raw 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.
/* bench/home_retry_sim.c — M10:驱动**真实 app/ui/home.c 的 home_tick** 的离线重试回归。
*
* 与 bench/retry_storm_sim.c 的分工:那边直接驱动 logic/retry_ctl.c(纯节奏闸);
* 这边把真实 home.c 编进宿主(-ffunction-sections + --gc-sections 只保留 home_tick 调用链),
* 用桩替换网络/状态/UI,验证三件事在**页面代码**层面成立:
* 1) 断网 120s(100ms tick):home_tick 只发起 4 次重试(0/30/60/90s),间隔 ≥30000ms;
* 2) 传给 home_tick 的 now 是**真机形态的垃圾 64 位值**(低 32 位=整秒×1000、高 32 位随机,
* 见 REPORT §42)时,节奏完全不受影响(旧实现此时会每 tick 重试);
* 3) 静默重试若无可见变化(歌单数量/离线态都不变)→ 一次全帧重绘都不发(旧实现每轮
* home_apply() 复位滚动 + 整帧重绘,这是「歌单列表滑动特别卡」的直接来源)。
*
* 编译(ncm_app 目录下;桩全部在本文件内,不链 SDK/核心库):
* 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/ui/home.c -o out/chk/home_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/home_retry_sim.c -o out/chk/home_retry_sim.o
* gcc -Wl,--gc-sections -o home_retry_sim out/chk/home_m10.o out/chk/home_retry_sim.o -lpthread
* ./home_retry_sim
*/
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "../app/ncm_app_internal.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; /* 关键:走真实离线重试路径(非 host mock 短路) */
/* ---- 可控世界状态 ---- */
static volatile int g_offline;
static volatile int g_load_calls;
static volatile int g_retry_probes; /* ncm_api_cache_refresh_path 调用 = 重试次数 */
static int64_t g_probe_at[64];
static volatile int g_paint_full; /* 全帧重绘次数 */
static int64_t g_mono; /* 假单调钟(UI 线程推进) */
static int64_t g_vt; /* 虚拟时间轴(跨 run_ticks 连续) */
#define TICK_MS 100
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)
/* ============================================================
* 桩:核心/逻辑层
* ============================================================ */
int64_t ncm_monotonic_ms(void) { return g_mono; }
int appstate_is_offline(void) { return g_offline; }
int appstate_session_gate(int rc)
{
/* 与真实实现同语义:网络类错误 → 离线;成功 → 在线 */
if (rc == NCM_OK) g_offline = 0;
else if (rc == -4 || rc == -6 || rc == -17) g_offline = 1;
return 0;
}
int appstate_load_playlists_ex(int *from_cache)
{
g_load_calls++;
if (from_cache) *from_cache = 0;
return -4; /* 断网:真机 rc=-4/-6 */
}
int appstate_playlists_cache_load(int64_t *ts_out)
{
if (ts_out) *ts_out = 0;
return NCM_ERR_NOENT; /* 本场景已有内存数据,不需要盘缓存 */
}
int account_cookie_maybe_refresh(void) { return NCM_OK; }
void account_vip_maybe_refresh(int force) { (void)force; }
void ncm_api_cache_refresh_path(const char *path)
{
(void)path;
int i = g_retry_probes++;
if (i < 64) g_probe_at[i] = g_mono;
}
/* ============================================================
* 桩:SDK(home_tick 可达的绘制/列表调用)
* ============================================================ */
static meow_theme s_theme;
const meow_theme *meow_theme_get(void) { return &s_theme; }
void meow_ui_set_page_bg(uint32_t c) { (void)c; }
void meow_ui_paint_full(void) { g_paint_full++; }
void meow_listview_set_count(meow_widget *w, int n) { (void)w; (void)n; }
void meow_listview_set_scroll(meow_widget *w, int o) { (void)w; (void)o; }
void meow_listview_set_sel(meow_widget *w, int i) { (void)w; (void)i; }
int meow_listview_scroll(meow_widget *w) { (void)w; return 0; }
void meow_tabbar_set_selected(meow_widget *w, int i) { (void)w; (void)i; }
void meow_log(meow_log_level lvl, const char *fmt, ...)
{
(void)lvl;
char body[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(body, sizeof body, fmt, ap);
va_end(ap);
printf(" [log] %s\n", body);
}
/* ============================================================
* 场景
* ============================================================ */
static void reset_world(void)
{
home_retry_reset(); /* 清真实 home.c 的节奏闸(等价于重新进页) */
g_offline = 1;
g_load_calls = 0;
g_retry_probes = 0;
g_paint_full = 0;
g_mono = 0;
g_vt = 0;
g_st.logged_in = 1;
g_st.playlist_count = 120; /* 已有缓存列表(真机 120 个歌单) */
g_st.nickname[0] = 0;
g_st.uid[0] = 0;
}
/* 真机 meow_ui_time_ms() 形态:低 32 位=整秒×1000,高 32 位随机(REPORT §42 实测)。 */
static uint64_t device_like_now(int tick, uint64_t *seed)
{
*seed = *seed * 6364136223846793005ULL + 1442695040888963407ULL;
uint32_t hi = (uint32_t)(*seed >> 33);
uint64_t lo = ((uint64_t)(tick / 10)) * 1000u; /* 整秒粒度 */
return ((uint64_t)hi << 32) | lo;
}
/* 跑 seconds 秒的 100ms tick(虚拟时间连续推进,跨调用不重置);garbage_clock=1 时传给
* home_tick 的 now 用真机垃圾形态。 */
static int64_t g_vt;
static void run_ticks(int seconds, int flap, int garbage_clock)
{
uint64_t seed = 88172645463325252ULL;
int64_t end = g_vt + (int64_t)seconds * 1000;
for (; g_vt < end; g_vt += TICK_MS) {
g_mono = g_vt; /* 可信单调钟(core clock_gettime) */
if (flap) g_offline = ((g_vt / TICK_MS) % 2 == 0);
uint64_t now = garbage_clock ? device_like_now((int)g_vt, &seed) : (uint64_t)g_vt;
home_tick(now);
usleep(1000); /* 让 worker 跑完(真机是一次网络失败 5-7s) */
}
}
static void case_steady(void)
{
printf("A. 真实 home_tick:断网 120s(100ms tick)\n");
reset_world();
run_ticks(120, 0, 0);
CHECK(g_retry_probes == 4, "重试次数 = %d(期望 4:0/30/60/90s;旧实现每 tick 一次)",
g_retry_probes);
int gaps_ok = 1;
for (int i = 1; i < g_retry_probes && i < 64; i++)
if (g_probe_at[i] - g_probe_at[i - 1] < 30000) gaps_ok = 0;
CHECK(gaps_ok, "相邻重试间隔全部 ≥30000ms");
CHECK(g_load_calls == g_retry_probes + 0, "每次重试只起一个 worker(单飞,%d/%d)",
g_load_calls, g_retry_probes);
CHECK(g_paint_full == 0,
"静默重试无可见变化 → 全帧重绘 %d 次(期望 0;旧实现每轮都重绘+复位滚动)",
g_paint_full);
}
static void case_garbage_clock(void)
{
printf("B. 真实 home_tick:入参 now 用真机垃圾 64 位值(低 32 位整秒×1000 + 高 32 位随机)\n");
reset_world();
run_ticks(120, 0, 1);
CHECK(g_retry_probes == 4, "垃圾 now 下重试次数 = %d(期望 4;旧实现此时每 tick 都重试)",
g_retry_probes);
int gaps_ok = 1;
for (int i = 1; i < g_retry_probes && i < 64; i++)
if (g_probe_at[i] - g_probe_at[i - 1] < 30000) gaps_ok = 0;
CHECK(gaps_ok, "垃圾 now 下间隔仍 ≥30000ms(节奏只认 core 单调钟)");
}
static void case_flap(void)
{
printf("C. 真实 home_tick:online/offline 每 tick 抖动 60s\n");
reset_world();
run_ticks(60, 1, 0);
CHECK(g_retry_probes == 2, "抖动下重试次数 = %d(期望 2:0/30s;旧实现随抖动加速)",
g_retry_probes);
if (g_retry_probes >= 2)
CHECK(g_probe_at[1] - g_probe_at[0] >= 30000, "抖动下间隔 = %lldms ≥30000",
(long long)(g_probe_at[1] - g_probe_at[0]));
}
static void case_recover(void)
{
printf("D. 真实 home_tick:回在线再断网(在线期间不得重置节奏)\n");
reset_world();
run_ticks(35, 0, 0); /* 0..35s 断网:0s / 30s 各一次 */
CHECK(g_retry_probes == 2, "35s 断网重试 %d 次(期望 2)", g_retry_probes);
g_offline = 0; /* 等价于 session_gate 把它切回在线 */
run_ticks(5, 0, 0); /* 35..40s 在线:不该有重试 */
CHECK(g_retry_probes == 2, "回在线期间不重试(%d)", g_retry_probes);
g_offline = 1; /* 40s 再次断网 */
run_ticks(30, 0, 0); /* 40..70s:上次尝试在 30s,故下一次落在 60s */
CHECK(g_retry_probes == 3, "再次断网重试 %d 次(期望 3)", g_retry_probes);
if (g_retry_probes >= 3)
CHECK(g_probe_at[2] - g_probe_at[1] >= 30000,
"跨越在线/离线切换后间隔 = %lldms ≥30000(在线不得清零基准)",
(long long)(g_probe_at[2] - g_probe_at[1]));
CHECK(g_paint_full >= 0, "离线态翻转路径可重绘(%d 次)", g_paint_full);
}
int main(void)
{
printf("home_retry_sim — M10 真实 home_tick 离线重试回归(REPORT §42)\n\n");
case_steady();
case_garbage_clock();
case_flap();
case_recover();
printf("\nhome_retry_sim: %d-%d\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}