Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/window_sim.c
T
AskaEth 2ca386973b test(ncm): M9 分类/登录门宿主单测(83 断言)+ 传输层归一 4 例 + REPORT §41
- bench/session_gate_sim.c(新):真实 core + ncm_http_request 桩,覆盖
  A 错误分类矩阵(-4/-5/-6/-17/裸 mbedtls vs -13/其它)、B HTTP 状态矩阵
  (401/403 vs 5xx/429 vs 3xx/4xx)、C 登录门 + 启动决策(无网→离线、无会话→扫码)、
  D 真实 ncm_api 状态/业务码映射(含 -460→SVC、301/302→AUTH)、
  E 真实 ncm_login_status_check/refresh 映射、F 会话密钥稳定性(换 MAC 仍可解、
  旧 MAC 密文可解且自动迁移)。83 PASS / 0 FAIL,0 真实请求。
- bench/http_truncate_sim.c:追加 4 例传输层归一(握手 fatal alert→NET、握手超时→TIMEOUT、
  读 fatal alert→NET、CA 失败→TLS),原 8 例截断守卫零回归(12 PASS)。
- build_ncm_sims.sh:window/search/plpage/apicache/cookie_refresh 链入 ncm_session.o
  (appstate/api/login 新依赖),新增 session_gate_sim 构建与运行。
- test_ncm_core.c:采用 M8 分支的 $TMPDIR 适配(Termux /tmp 不可写;合并时以 M8 为准)。
- 相关 sim 头注释同步链接命令(ncm_apicache.o/ncm_session.o)。
- REPORT §41:判定点调用链与行号、根因证据、实现、验证矩阵与诚实出入
  (mipsel/真机未跑;app.c 宿主不可编译的既有原因;403/-460 取舍)。

验证:test_ncm_core_host 139 PASS/0 FAIL;session_gate_sim 83 PASS;
http_truncate_sim 12 PASS;window/search/apicache/cookie_refresh sim 通过;
核心 12 TU + 应用 13 TU(-Wall -Wextra)0 warning。
2026-11-05 11:45:44 +08:00

233 lines
12 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/window_sim.c — 三期第三批 滑动窗口 6000 首滚动全程离线验证(0 网络请求)。
*
* 驱动真实 appstate.c(滑动窗口)+ 真实 songmeta.c(盘缓存,/tmp 临时目录),
* 网络端点(ncm_api_playlist_detail / ncm_api_song_detail)为本地合成 stub。
* 场景(对齐用户「我喜欢」6000+ 首):
* ① 重头:detail 取全量 6000 trackIds,物化首段(1 detail + 1 song_detail 批);
* ② 全程下滚:视口 12 行、步进 8 行(模拟跟手拖动),每步 pl_maybe_ensure 等价物
* ensure(pos-24, pos+36);每步断言视口 12 行全部物化(列表无占位破窗);
* 断言 song_detail 调用数 ≈ 6000/批网格(批对齐预取,无小请求放大);
* ③ 全程回滚到 0:盘缓存(songmeta)全命中,song_detail 调用数 0 增长;
* ④ 播放兼容:窗口感知的取歌——播放线程路径 appstate_song_fetch 在窗口远离时
* 仍能物化当前播放曲(不滑窗、不崩、盘缓存命中免请求);
* ⑤ 尾部/越界:ensure 尾部区间 + 越过尾部无操作;
* ⑥ 内存账:6000 首场景改前(1000 截断 × 1324B)vs 改后(窗口 256×608B + 索引)。
* 风控纪律:0 次真实 NCM 请求(离线 stub + /tmp 盘缓存)。
*
* 编译(宿主):$CC_HOST -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -I../vendor/jsmn -c bench/window_sim.c -o /tmp/ws.o
* $CC_HOST -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -I../vendor/jsmn -c app/logic/appstate.c -o /tmp/ws_app.o
* $CC_HOST -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -I../vendor/jsmn -c app/logic/songmeta.c -o /tmp/ws_meta.o
* $CC_HOST -O2 -Wl,--gc-sections -o window_sim /tmp/ws.o /tmp/ws_app.o /tmp/ws_meta.o \
* out/core-host/ncm_apicache.o out/core-host/ncm_session.o -lpthread
* 运行:./window_sim (0 网络;PASS/FAIL 退出码)
*/
#include "../app/ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
size_t ncm_strlcpy(char *dst, const char *src, size_t sz)
{
size_t n = 0;
if (sz) {
while (src[n] && n < sz - 1) { dst[n] = src[n]; n++; }
dst[n] = 0;
}
while (src[n]) n++;
return n;
}
/* ---- 网络端点 stub(离线,合成数据) ---- */
#define SIM_TOTAL 6000
static int g_detail_calls;
static int g_song_calls;
static int g_song_served; /* song_detail 累计返回首数 */
int ncm_api_playlist_detail(const char *device_id, const char *cookie, const char *playlist_id,
char *name, size_t name_sz, char *cover, size_t cover_sz,
int64_t *ids, int id_cap, int *out_id_n)
{
(void)device_id; (void)cookie; (void)cover_sz; (void)playlist_id;
g_detail_calls++;
if (name) snprintf(name, name_sz, "Sim Playlist");
if (cover) cover[0] = 0;
int n = (SIM_TOTAL < id_cap) ? SIM_TOTAL : id_cap;
for (int i = 0; i < n; i++) ids[i] = 2000000LL + i;
*out_id_n = n;
return NCM_OK;
}
int ncm_api_song_detail(const char *device_id, const char *cookie, const int64_t *ids, int n,
ncm_song_t *out, int max, int *out_n)
{
(void)device_id; (void)cookie;
g_song_calls++;
int m = (n < max) ? n : max;
g_song_served += m;
for (int i = 0; i < m; i++) {
snprintf(out[i].id, sizeof out[i].id, "%lld", (long long)ids[i]);
snprintf(out[i].name, sizeof out[i].name, "Song%lld", (long long)ids[i]);
snprintf(out[i].art_name, sizeof out[i].art_name, "Artist%lld", (long long)ids[i]);
snprintf(out[i].album_name, sizeof out[i].album_name, "Album%lld", (long long)ids[i]);
snprintf(out[i].al_pic, sizeof out[i].al_pic, "http://p.music.126.net/cov%lld.jpg", (long long)ids[i]);
out[i].duration_ms = 200000;
}
*out_n = m;
return NCM_OK;
}
static int g_fail = 0;
#define CHECK(cond, fmt, ...) do { \
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
else { printf(" FAIL " fmt "\n", ##__VA_ARGS__); g_fail = 1; } \
} while (0)
/* 模拟 playlist.c pl_maybe_ensure:视口 12 行 ±24 行余量。 */
static int ui_ensure(int pos)
{
int lo = pos - 24, hi = pos + 12 + 24;
if (lo < 0) lo = 0;
if (hi > SIM_TOTAL) hi = SIM_TOTAL;
if (lo >= hi) return NCM_OK;
if (appstate_range_loaded(lo, hi)) return NCM_OK;
return appstate_song_ensure_range(lo, hi);
}
int main(void)
{
if (system("rm -rf /tmp/ncm_window_sim_cache") != 0) { /* 清上次运行残留(盘缓存) */
printf("FAIL cannot clean /tmp cache dir\n");
return 1;
}
memset(&g_st, 0, sizeof g_st);
snprintf(g_st.cache_dir, sizeof g_st.cache_dir, "/tmp/ncm_window_sim_cache");
g_st.playlist_count = 1;
snprintf(g_st.playlists[0].id, sizeof g_st.playlists[0].id, "pl6000");
g_st.playlists[0].track_count = SIM_TOTAL;
size_t win_bytes = sizeof g_st.songs + sizeof g_st.song_valid;
size_t idx_bytes = sizeof g_st.song_ids;
size_t old_bytes = (size_t)1000 * 1324; /* §14:NCM_SONG_MAX=1000 × ~1324B/首 */
printf("== memory model ==\n");
printf(" ncm_song_t=%zuB (old 1324B) window=%zuB(%d首) trackIds idx=%zuB(%d首)\n",
sizeof(ncm_song_t), win_bytes, NCM_SONG_WINDOW, idx_bytes, NCM_TRACKID_MAX);
printf(" 6000首场景:旧模型 %zuKB(且 1000 截断)→ 新模型 %zuKB(任意长度恒定)\n",
old_bytes / 1024, (win_bytes + idx_bytes) / 1024);
/* ① 重头 */
printf("== phase 1: open 6000-song playlist ==\n");
int rc = appstate_load_playlist_songs_page(0, 0, NCM_SONG_BATCH);
CHECK(rc == NCM_OK, "page0 rc=%d", rc);
CHECK(g_st.track_ids_count == SIM_TOTAL, "track_ids_count=%d (expect %d, 无截断)",
g_st.track_ids_count, SIM_TOTAL);
CHECK(g_st.song_count == NCM_SONG_BATCH, "valid slots=%d (expect first batch %d)",
g_st.song_count, NCM_SONG_BATCH);
CHECK(g_st.window_base == 0, "window_base=%d", g_st.window_base);
CHECK(g_detail_calls == 1 && g_song_calls == 1, "detail=%d song_detail=%d (expect 1+1)",
g_detail_calls, g_song_calls);
/* ② 全程下滚(0→6000,步进 8 行):视口每步全部物化 */
printf("== phase 2: scroll down 0 -> %d (step 8 rows) ==\n", SIM_TOTAL);
int step_checks = 0;
for (int pos = 0; pos < SIM_TOTAL; pos += 8) {
rc = ui_ensure(pos);
if (rc != NCM_OK) { printf(" FAIL ensure(pos=%d) rc=%d\n", pos, rc); g_fail = 1; break; }
int vhi = pos + 12; if (vhi > SIM_TOTAL) vhi = SIM_TOTAL;
if (!appstate_range_loaded(pos, vhi)) {
printf(" FAIL viewport [%d,%d) not fully materialized\n", pos, vhi);
g_fail = 1; break;
}
step_checks++;
}
CHECK(step_checks == (SIM_TOTAL + 7) / 8, "scroll steps verified=%d (expect %d)",
step_checks, (SIM_TOTAL + 7) / 8);
CHECK(g_st.song_count <= NCM_SONG_WINDOW, "valid slots=%d ≤ window %d (全程不超窗)",
g_st.song_count, NCM_SONG_WINDOW);
printf(" INFO song_detail calls=%d served=%d (grid prefetch; 下限 %d)\n",
g_song_calls, g_song_served, SIM_TOTAL / NCM_SONG_BATCH);
CHECK(g_song_calls <= SIM_TOTAL / NCM_SONG_BATCH + 6,
"song_detail calls=%d ≤ %d (批网格对齐,无小请求放大)",
g_song_calls, SIM_TOTAL / NCM_SONG_BATCH + 6);
ncm_song_t sq;
CHECK(appstate_song_copy(SIM_TOTAL - 1, &sq) == 0 &&
strcmp(sq.name, "Song2005999") == 0, "tail song name='%s'", sq.name);
CHECK(appstate_song_copy(0, &sq) != 0, "head evicted after scrolling to tail");
/* ③ 全程回滚到 0:盘缓存全命中,0 新增 song_detail */
printf("== phase 3: scroll back up %d -> 0 (disk cache) ==\n", SIM_TOTAL);
int calls_before = g_song_calls;
step_checks = 0;
for (int pos = SIM_TOTAL - 8; pos >= 0; pos -= 8) {
rc = ui_ensure(pos);
if (rc != NCM_OK) { printf(" FAIL ensure(pos=%d) rc=%d\n", pos, rc); g_fail = 1; break; }
int vhi = pos + 12; if (vhi > SIM_TOTAL) vhi = SIM_TOTAL;
if (!appstate_range_loaded(pos, vhi)) {
printf(" FAIL viewport [%d,%d) not materialized on way back\n", pos, vhi);
g_fail = 1; break;
}
step_checks++;
}
CHECK(g_song_calls == calls_before,
"scroll-back song_detail calls +%d (expect 0,盘缓存全命中)", g_song_calls - calls_before);
CHECK(appstate_song_copy(0, &sq) == 0 && strcmp(sq.name, "Song2000000") == 0,
"head re-materialized from disk: name='%s'", sq.name);
/* ④ 播放兼容:窗口滑出当前播放曲 → fetch 不滑窗、不崩、盘命中免请求 */
printf("== phase 4: playback survives window slide ==\n");
rc = appstate_song_ensure_range(3000 - 128, 3000 + 128); /* 窗口移到 3000 附近 */
int base_at_play = g_st.window_base;
CHECK(rc == NCM_OK, "ensure around 3000 rc=%d", rc);
/* 模拟 run_song:cur=3050 在窗内 → song_copy;然后窗口滑回 0,再 fetch(3050) */
CHECK(appstate_song_copy(3050, &sq) == 0, "song_copy(3050) in window");
rc = appstate_song_ensure_range(0, 100); /* 用户滚回顶部(窗口滑走) */
CHECK(rc == NCM_OK && g_st.window_base != base_at_play,
"window slid away base %d -> %d", base_at_play, g_st.window_base);
CHECK(appstate_song_copy(3050, &sq) != 0, "playing song evicted from window");
calls_before = g_song_calls;
rc = appstate_song_fetch(3050, &sq); /* 播放线程路径:不滑窗 */
CHECK(rc == NCM_OK && strcmp(sq.id, "2003050") == 0 && sq.duration_ms == 200000,
"fetch(3050) ok id=%s dur=%d", sq.id, sq.duration_ms);
CHECK(g_st.window_base != base_at_play && g_st.song_count <= NCM_SONG_WINDOW,
"fetch did not move window (base=%d)", g_st.window_base);
CHECK(g_song_calls == calls_before, "fetch hit disk cache (0 new requests)");
/* next/prev 边界语义:cur 可到 SIM_TOTAL-1,不越界不崩 */
CHECK(appstate_song_fetch(SIM_TOTAL - 1, &sq) == NCM_OK, "fetch(tail) ok");
CHECK(appstate_song_fetch(SIM_TOTAL, &sq) != NCM_OK, "fetch(out of range) rejected");
/* ⑤ 尾部/越界 ensure */
printf("== phase 5: tail & out-of-range ==\n");
rc = appstate_song_ensure_range(SIM_TOTAL - 12, SIM_TOTAL);
CHECK(rc == NCM_OK && appstate_range_loaded(SIM_TOTAL - 12, SIM_TOTAL),
"tail ensure rc=%d", rc);
CHECK(g_st.window_base <= SIM_TOTAL - NCM_SONG_WINDOW ||
SIM_TOTAL < NCM_SONG_WINDOW, "base=%d clamped at tail", g_st.window_base);
rc = appstate_song_ensure_range(SIM_TOTAL, SIM_TOTAL + 100);
CHECK(rc == NCM_OK, "ensure past tail no-op rc=%d", rc);
rc = appstate_song_ensure_range(-50, 0);
CHECK(rc == NCM_OK, "ensure empty range no-op rc=%d", rc);
/* ⑥ 盘缓存规模核对 */
printf("== phase 6: disk cache ==\n");
{
char cmd[256];
snprintf(cmd, sizeof cmd,
"ls /tmp/ncm_window_sim_cache/meta | wc -l");
FILE *p = popen(cmd, "r");
int files = -1;
if (p) { if (fscanf(p, "%d", &files) != 1) files = -1; pclose(p); }
CHECK(files == SIM_TOTAL, "songmeta files=%d (expect %d, 全程拉过即落盘)", files, SIM_TOTAL);
}
printf("%s\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS");
return g_fail ? 1 : 0;
}