Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/pagination_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

160 lines
7.9 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/pagination_sim.c — 三期第三批 滑动窗口 基本语义离线回归(0 网络请求)。
* (§14 版为「分页 append 到 NCM_SONG_MAX=1000 截断」回归;§17 起分页被滑动窗口取代,
* 本文件随之改测兼容入口 appstate_load_playlist_songs_page 的新语义。)
*
* 直接驱动 App 的真实函数 appstate_load_playlist_songs_page()(app/logic/appstate.c),
* 仅把两个网络端点(ncm_api_playlist_detail / ncm_api_song_detail)替换成本地 stub
* (合成 trackIds / 合成歌曲对象),验证:
* ① offset=0 重建索引(song_ids[]←全量 trackIds,track_ids_count)并物化首段;
* ② offset>0 物化 [offset,offset+limit)(滑窗 + song_detail 批补载);
* ③ 尾部(offset≥track_ids_count)无操作;④ 大歌单不再截断(索引全量 5191,
* 窗口内有效槽位 ≤ NCM_SONG_WINDOW);⑤ 占位/出窗槽 song_copy 失败、入窗成功。
* 风控纪律:本验证 0 次真实 NCM 请求(离线 stub;cache_dir 置空 → songmeta 全部 miss/不落盘)。
*
* 编译(宿主):$CC_HOST -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -I../vendor/jsmn -c bench/pagination_sim.c -o /tmp/ps.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/ps_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/ps_meta.o
* $CC_HOST -O2 -Wl,--gc-sections -o pagination_sim /tmp/ps.o /tmp/ps_app.o /tmp/ps_meta.o \
* out/core-host/ncm_apicache.o out/core-host/ncm_session.o -lpthread
* 运行:./pagination_sim (0 网络;PASS/FAIL 退出码)
*/
#include "../app/ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* appstate.c 依赖的全局(app.c 提供;本 bench 只测窗口路径,仅 g_st 被用到) */
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;
/* ncm_strlcpy:appstate_apply_playlist_cover 用到(从 core ncm_common 借;此处最小内联实现) */
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(离线,合成数据) ---- */
static int g_pid_total; /* detail 应返回的 trackIds 总数(供测试注入) */
static int g_detail_calls;
static int g_song_calls;
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 = (g_pid_total < id_cap) ? g_pid_total : id_cap;
for (int i = 0; i < n; i++) ids[i] = 1000000LL + i; /* 合成连续 id */
*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;
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].al_pic, sizeof out[i].al_pic, "http://p.music.126.net/cov%lld.jpg", (long long)ids[i]);
}
*out_n = m;
return NCM_OK;
}
/* ---- 驱动:重头 + 逐段 ensure 到尾部,返回 track_ids_count ---- */
static int load_all(int pid_total, int *detail_calls, int *song_calls)
{
memset(&g_st, 0, sizeof g_st);
g_st.playlist_count = 1;
snprintf(g_st.playlists[0].id, sizeof g_st.playlists[0].id, "pl0");
g_st.playlists[0].track_count = pid_total;
g_pid_total = pid_total;
g_detail_calls = 0; g_song_calls = 0;
int rc = appstate_load_playlist_songs_page(0, 0, NCM_SONG_BATCH);
if (rc != NCM_OK) { printf(" page0 rc=%d\n", rc); return -1; }
for (int off = NCM_SONG_BATCH; off < g_st.track_ids_count; off += NCM_SONG_BATCH) {
rc = appstate_load_playlist_songs_page(0, off, NCM_SONG_BATCH);
if (rc != NCM_OK) { printf(" page(%d) rc=%d\n", off, rc); return -1; }
}
if (detail_calls) *detail_calls = g_detail_calls;
if (song_calls) *song_calls = g_song_calls;
return g_st.track_ids_count;
}
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)
int main(void)
{
printf("ncm_song_t=%zuB window=%zuKB(%d首) trackIds idx=%zuKB(%d首)\n",
sizeof(ncm_song_t),
sizeof g_st.songs / 1024, NCM_SONG_WINDOW,
sizeof g_st.song_ids / 1024, NCM_TRACKID_MAX);
/* (a) 130 首(< 窗口):读全程后 130 槽全有效(窗口装得下,无需滑窗)。 */
printf("== case A: 130 songs (whole list fits window) ==\n");
int dc = 0, sc = 0;
int n = load_all(130, &dc, &sc);
CHECK(n == 130, "track_ids_count=%d (expect 130)", n);
CHECK(g_st.song_count == 130, "valid slots=%d (expect 130, all in window)", g_st.song_count);
CHECK(g_st.window_base == 0, "window_base=%d (expect 0)", g_st.window_base);
CHECK(dc == 1, "detail calls=%d (expect 1, index fetched once)", dc);
CHECK(sc == 2, "song_detail calls=%d (expect 2 = batches 0-99 + 100-129)", sc);
ncm_song_t sq;
CHECK(appstate_song_copy(129, &sq) == 0 && strcmp(sq.name, "Song1000129") == 0,
"last song copy name='%s'", sq.name);
CHECK(sq.al_pic[0] != 0, "last song has al_pic='%s'", sq.al_pic);
/* 越过尾部再 load:应无操作(不越界) */
int rc = appstate_load_playlist_songs_page(0, 130, NCM_SONG_BATCH);
CHECK(rc == NCM_OK && g_st.song_count == 130, "load past tail: rc=%d valid=%d", rc, g_st.song_count);
/* (b) 5191 首(>> 窗口):索引全量不截断(§17 取消 NCM_SONG_MAX=1000 上限),
* 窗口有效槽恒 ≤ NCM_SONG_WINDOW,末段滑窗后末首可达。 */
printf("== case B: 5191 songs (no cap; window slides) ==\n");
dc = sc = 0;
n = load_all(5191, &dc, &sc);
CHECK(n == 5191, "track_ids_count=%d (expect 5191, 不再 1000 截断)", n);
CHECK(g_st.song_count <= NCM_SONG_WINDOW, "valid slots=%d ≤ window %d", g_st.song_count, NCM_SONG_WINDOW);
CHECK(dc == 1, "detail calls=%d", dc);
CHECK(sc == (5191 + NCM_SONG_BATCH - 1) / NCM_SONG_BATCH,
"song_detail calls=%d (expect %d grid batches)", sc, (5191 + NCM_SONG_BATCH - 1) / NCM_SONG_BATCH);
CHECK(appstate_song_copy(5190, &sq) == 0 && strcmp(sq.name, "Song1005190") == 0,
"tail song copy name='%s'", sq.name);
CHECK(appstate_song_copy(0, &sq) != 0, "head song evicted (out of window) -> copy fails");
/* 越界再 load:应 NCM_OK 但不动窗口 */
int valid_before = g_st.song_count;
rc = appstate_load_playlist_songs_page(0, 5191, NCM_SONG_BATCH);
CHECK(rc == NCM_OK && g_st.song_count == valid_before, "load past tail: rc=%d valid=%d", rc, g_st.song_count);
/* (c) 空歌单(0 首) */
printf("== case C: empty playlist ==\n");
n = load_all(0, &dc, &sc);
CHECK(n == 0 && g_st.song_count == 0, "empty: track_ids_count=%d valid=%d", n, g_st.song_count);
printf("%s\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS");
return g_fail ? 1 : 0;
}