Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/cover_chain_sim.c
T
MeowOS 6200a41b71 fix(ncm): 三连修 — 封面切歌不更新/缓冲无反馈失败无声/暂停切歌边界 (REPORT §18)
- cover worker 忙等死循环(成功后不清目标,每圈解码+换入+gen++,CPU 100%+每帧整盘重绘)
  → 成功清目标转怠速; 失败指数退避 0.5-4s 连 8 次放弃+WARN 日志; 去重加在拉目标;
  cover_get 持锁+cover_put 修绘制期 UAF; g_host_mock 防真网守卫
- 封面兜底污染修复: 不再把单曲 al_pic 写进 g_st.cover_url(否则 al_pic 缺失曲回落成
  上一首封面且撞去重 → 封面永停); plr_page_enter 强制重估自愈; pl_page_enter 回填歌单封面
- 缓冲 UI: BUFFER 态「缓冲中… N%」(g_play.dl_done/total, 下载回调累加); 播放错误中文
  错误条+snack(旧版 err 无 UI 消费者=无声死等); STOPPED 态播放键=重试; play/next 清 err
- 写失败 set_err 修连环跳歌(旧版静默 break 被外层当自然播完,流死时跳完歌单+狂发 song_url);
  still_current 护栏修下载途中切歌的状态/快照过期覆盖
- 验证(0 真实请求): cover_chain_sim 7 组+player_flow_sim P1-P5 全 PASS, 旧代码对照
  T2/P2/P4 必败坐实根因; audiod_chain_sim 14/14; pagination/window/core+qemu 回归 PASS;
  宿主+mipsel 自有代码 0 warning; 9 页离屏渲染 rc=0 (含新 mock 页 playerbuf/playererr)
- ncm md5 50d41bbf4a379e925699ba013b997ffd (未部署真机, 留主会话)
2026-09-01 05:29:09 +08:00

238 lines
8.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/cover_chain_sim.c — 封面链离线回归(0 真实请求,REPORT §18)。
*
* 驱动真实 app/logic/cover.c(worker 线程/ncm_cache LRU/cover_current.jpg 落盘),
* HTTP 端点为本地合成 stub(按 URL 生成可区分内容,可注入失败次数模拟慢/坏网络),
* meow_img 为假句柄(嵌内容哈希,不真解码 JPEG——测的是链路逻辑不是解码器)。
*
* 用例:
* T1 首次请求 → 封面出现(内容哈希对得上 URL);
* T2 定居后 worker **不忙等**:cover_changed() 在 1.2s 内不得再置位
* (§18 前:成功后不清目标 → 每圈解码+换入+g_gen++ 死循环,CPU 占满且播放页每帧
* 整盘重绘——本用例在旧代码上必 FAIL);
* T3 切歌换 URL → 封面换入新内容 + cover_current.jpg 同步更新(launcher 媒体卡片读它);
* T4 重复请求同一 URL → 去重(0 新增 HTTP,0 换入);
* T5 网络失败 ×2 后退避重试成功(不无声死等);
* T6 连续失败 ≥8 次放弃(目标清除,不永久占 worker);
* T7 空/NULL URL 忽略;cover_cache_reset 清空。
*
* 编译(宿主,ncm_app 目录下;weak cover_put 兼容 §18 前的 cover.c 做对照):
* gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -Ivendor/jsmn -c bench/cover_chain_sim.c -o /tmp/cs.o
* gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -Ivendor/jsmn -c app/logic/cover.c -o /tmp/cs_cover.o
* gcc -O2 -ffunction-sections -DJSMN_HEADER -Icore -c core/ncm_cache.c -o /tmp/cs_cache.o
* gcc -O2 -ffunction-sections -DJSMN_HEADER -Icore -c core/ncm_store.c -o /tmp/cs_store.o
* gcc -O2 -Wl,--gc-sections -o cover_chain_sim /tmp/cs.o /tmp/cs_cover.o /tmp/cs_cache.o \
* /tmp/cs_store.o -lpthread
* 运行:./cover_chain_sim (0 网络;PASS/FAIL 退出码)
*/
#include "../app/ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/stat.h>
ncm_app_state_t g_st;
volatile int g_app_quit = 0;
int g_host_mock = 0;
/* ---- 公共 stub ---- */
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;
}
static int g_log_warns;
void meow_log(meow_log_level lvl, const char *fmt, ...)
{
if (lvl >= MEOW_LOG_WARN) g_log_warns++;
(void)fmt;
}
/* ---- 假 meow_img:嵌内容 FNV 哈希(链路验证用,不真解码) ---- */
struct meow_img { unsigned long hash; int len; int freed; };
static unsigned long fnv1a(const unsigned char *p, size_t n)
{
unsigned long h = 1469598103934665603UL;
for (size_t i = 0; i < n; i++) { h ^= p[i]; h *= 1099511628211UL; }
return h;
}
meow_img *meow_img_load_mem(const void *buf, int len)
{
if (!buf || len <= 0) return NULL;
meow_img *im = (meow_img *)malloc(sizeof *im);
if (!im) return NULL;
im->hash = fnv1a((const unsigned char *)buf, (size_t)len);
im->len = len;
im->freed = 0;
return im;
}
void meow_img_free(meow_img *im)
{
if (!im) return;
im->freed = 1;
free(im);
}
/* §18 前的 cover.c 无 cover_put:weak 空实现让同一 sim 可对照旧代码(新代码强符号覆盖)。 */
__attribute__((weak)) void cover_put(void) { }
/* ---- HTTP stub:内容 = djb2(url) 填充 4KB(每 URL 唯一);可注入失败/延迟 ---- */
static int g_http_calls;
static int g_fail_next; /* 剩余注入失败次数 */
static int g_delay_ms; /* 模拟慢网络 */
static unsigned long djb2(const char *s)
{
unsigned long h = 5381;
while (*s) h = ((h << 5) + h) + (unsigned char)*s++;
return h;
}
int ncm_http_request(const ncm_http_req_t *req, char *buf, size_t buf_sz, ncm_http_resp_t *resp)
{
g_http_calls++;
if (g_fail_next > 0) { g_fail_next--; return NCM_ERR_IO; }
if (g_delay_ms > 0) usleep((useconds_t)g_delay_ms * 1000);
size_t n = 4096 < buf_sz ? 4096 : buf_sz;
unsigned long h = djb2(req->url);
for (size_t i = 0; i < n; i++) buf[i] = (char)((h >> ((i % 8) * 8)) ^ (unsigned char)i);
memset(resp, 0, sizeof *resp);
resp->status = 200;
resp->body = buf;
resp->body_len = n;
resp->content_length = n;
return NCM_OK;
}
/* URL 期望内容哈希(与 stub 生成式一致) */
static unsigned long url_hash(const char *url)
{
unsigned char tmp[4096];
unsigned long h = djb2(url);
for (size_t i = 0; i < sizeof tmp; i++) tmp[i] = (unsigned char)((h >> ((i % 8) * 8)) ^ (unsigned char)i);
return fnv1a(tmp, sizeof tmp);
}
static int g_fail;
#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)
/* 轮询等待封面内容变成 want_hash(≤ ms)。返回 1=达成。 */
static int wait_cover_hash(unsigned long want, int ms)
{
for (int t = 0; t < ms; t += 20) {
meow_img *im = cover_get();
unsigned long h = im ? im->hash : 0;
cover_put();
if (h == want) return 1;
usleep(20 * 1000);
}
return 0;
}
static unsigned long cur_cover_hash(void)
{
meow_img *im = cover_get();
unsigned long h = im ? im->hash : 0;
cover_put();
return h;
}
/* 读 cover_current.jpg 并哈希(验证 launcher 媒体卡片读的文件同步更新)。 */
static unsigned long curjpg_hash(void)
{
char path[600];
snprintf(path, sizeof path, "%s/cover_current.jpg", g_st.cache_dir);
FILE *f = fopen(path, "rb");
if (!f) return 0;
unsigned char b[4096];
size_t n = fread(b, 1, sizeof b, f);
fclose(f);
return n ? fnv1a(b, n) : 0;
}
#define URL_A "http://p1.music.126.net/albumA/xxx.jpg"
#define URL_B "http://p1.music.126.net/albumB/yyy.jpg"
#define URL_C "http://p1.music.126.net/albumC/zzz.jpg"
#define URL_D "http://p1.music.126.net/albumD/www.jpg"
int main(void)
{
if (system("rm -rf /tmp/ncm_cover_sim") != 0) { printf("FAIL rm /tmp cache\n"); return 1; }
memset(&g_st, 0, sizeof g_st);
snprintf(g_st.cache_dir, sizeof g_st.cache_dir, "/tmp/ncm_cover_sim");
ncm_cache_init(&g_st.ctx.cache, g_st.cache_dir);
printf("== T1: first request -> cover appears ==\n");
cover_request(URL_A);
CHECK(wait_cover_hash(url_hash(URL_A), 3000), "cover A shown (hash match)");
CHECK(g_http_calls == 1, "http calls=%d (expect 1)", g_http_calls);
CHECK(curjpg_hash() == url_hash(URL_A), "cover_current.jpg == A");
printf("== T2: settle -> worker idle (no busy loop) ==\n");
cover_ack();
int churn = 0;
for (int t = 0; t < 1200; t += 10) { if (cover_changed()) churn++; usleep(10 * 1000); }
CHECK(churn == 0, "cover_changed churn in 1.2s = %d (expect 0; §18 前=忙等死循环)", churn);
CHECK(g_http_calls == 1, "http calls=%d (still 1)", g_http_calls);
printf("== T3: switch song -> cover switches + cover_current.jpg updated ==\n");
cover_request(URL_B);
CHECK(wait_cover_hash(url_hash(URL_B), 3000), "cover switched to B");
CHECK(curjpg_hash() == url_hash(URL_B), "cover_current.jpg == B");
CHECK(g_http_calls == 2, "http calls=%d (expect 2)", g_http_calls);
cover_ack();
usleep(400 * 1000);
CHECK(!cover_changed(), "no churn after switch");
printf("== T4: re-request same url -> dedup ==\n");
cover_request(URL_B);
usleep(500 * 1000);
CHECK(g_http_calls == 2, "http calls=%d (still 2, dedup)", g_http_calls);
printf("== T5: flaky network x2 -> backoff retry succeeds ==\n");
g_fail_next = 2;
cover_request(URL_C);
CHECK(wait_cover_hash(url_hash(URL_C), 8000), "cover C after retries");
CHECK(g_http_calls >= 5, "http calls=%d (>=5: 2 fails + 1 ok for C)", g_http_calls);
CHECK(g_log_warns >= 2, "warn logs=%d (failures logged, 不无声)", g_log_warns);
printf("== T6: permanent failure -> give up after %d tries ==\n", 8);
g_fail_next = 100;
int before = g_http_calls;
cover_request(URL_D);
usleep(25 * 1000 * 1000 / 10); /* ~2.5s 不够 8 次退避(0.5+1+2+4*5=23.5s),分段等 */
for (int t = 0; t < 30 && g_http_calls - before < 8; t++) usleep(1000 * 1000);
int tries = g_http_calls - before;
CHECK(tries == 8, "give up after tries=%d (expect 8)", tries);
CHECK(cur_cover_hash() == url_hash(URL_C), "old cover C retained after give-up");
usleep(600 * 1000);
CHECK(g_http_calls - before == 8, "no further retries after give-up (calls=%d)",
g_http_calls - before);
printf("== T7: null/empty + reset ==\n");
cover_request(NULL);
cover_request("");
CHECK(g_http_calls == before + 8, "null/empty ignored (calls=%d)", g_http_calls);
cover_cache_reset();
CHECK(cur_cover_hash() == 0, "reset -> cover cleared");
g_fail_next = 0;
printf(g_fail ? "== COVER CHAIN SIM: FAIL ==\n" : "== COVER CHAIN SIM: ALL PASS ==\n");
return g_fail;
}