/* bench/degrade_sim.c — song_url_auto 全档降级链离线矩阵(§20,0 真实请求)。 * * 驱动真实 core/ncm_api.c 的 ncm_api_song_url_auto_f(注入假 try_fn,按「可用下限」 * 模型应答:level <= avail_from 才给 url)。覆盖: * ① desired × avail_from 全矩阵(9 档 × 9 档 + 全不可用):命中档 = min(desired, avail_from), * 请求数 = desired - 命中 + 1(逐级试,不多不少); * ② desired 越界钳制(DOLBY=16 → SURROUND;负数 → STANDARD); * ③ url 在但 type 未知(如 aac)→ 跳过继续降; * ④ type 缺失时按 url 扩展名收 flac; * ⑤ out_level 回填值与命中档一致(「下首恢复用户档」由调用方重传 desired 保证, * 链内不记忆——每次调用独立从 desired 起步)。 * * 编译(宿主,ncm_app 目录下,先跑过 build_core.sh 产出 out/core-host/ 全部对象): * gcc -O2 -Wall -Wextra -DJSMN_HEADER -Icore -Ivendor/jsmn \ * bench/degrade_sim.c out/core-host/全部.o \ * -Lout/mbedtls/host -lmbedtls -lmbedx509 -lmbedcrypto -o degrade_sim -lpthread -lm * 运行:./degrade_sim (PASS/FAIL 退出码) */ #include "ncm.h" #include #include 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) /* ---- 假逐档取址:level <= g_avail_from 给 url(type 可配),记录每档请求次数 ---- */ static int g_avail_from; /* 可用下限;-1=全档不可用 */ static const char *g_type = "mp3"; /* 应答 type(""=缺省看扩展名) */ static const char *g_resp_level = NULL; /* 非 NULL:响应 data[0].level 固定串(服务端静默降档模拟) */ static int g_calls[NCM_LEVEL_SURROUND + 1]; static int fake_try(const char *dev, const char *ck, const char *id, int level, ncm_play_url_t *out) { (void)dev; (void)ck; (void)id; if (level >= 0 && level <= NCM_LEVEL_SURROUND) g_calls[level]++; memset(out, 0, sizeof *out); if (level > g_avail_from) return NCM_OK; /* 该档不可用:url 空 */ snprintf(out->url, sizeof out->url, "http://sim/%lld.%s", (long long)12345, (level >= NCM_LEVEL_LOSSLESS) ? "flac" : "mp3"); snprintf(out->type, sizeof out->type, "%s", g_type); if (g_resp_level) snprintf(out->level, sizeof out->level, "%s", g_resp_level); out->br = 320000; out->size = 9000000; return NCM_OK; } static int g_bad_type_from = -1; /* >=0: 该档以上给 aac(未知格式应跳过) */ static int fake_try_badtype(const char *dev, const char *ck, const char *id, int level, ncm_play_url_t *out) { int r = fake_try(dev, ck, id, level, out); if (r == NCM_OK && out->url[0] && level >= g_bad_type_from) snprintf(out->type, sizeof out->type, "aac"); return r; } static void reset_calls(void) { memset(g_calls, 0, sizeof g_calls); } static int total_calls(void) { int n = 0; for (int i = 0; i <= NCM_LEVEL_SURROUND; i++) n += g_calls[i]; return n; } int main(void) { ncm_play_url_t u; int eff; printf("== 1) desired × avail_from 全矩阵 ==\n"); int cases = 0, bad = 0; for (int desired = NCM_LEVEL_STANDARD; desired <= NCM_LEVEL_SURROUND; desired++) { for (int avail = NCM_LEVEL_STANDARD; avail <= NCM_LEVEL_SURROUND; avail++) { reset_calls(); g_avail_from = avail; g_type = "mp3"; int rc = ncm_api_song_url_auto_f("dev", "ck", "1", desired, &u, &eff, fake_try); int want = (avail < desired) ? avail : desired; /* 期望命中 = min */ int ok = (rc == NCM_OK) && (eff == want) && u.url[0] && (total_calls() == desired - want + 1); /* 高档(>命中)各恰好 1 次,低档不请求 */ for (int lv = want + 1; lv <= desired; lv++) ok = ok && (g_calls[lv] == 1); for (int lv = 0; lv < want; lv++) ok = ok && (g_calls[lv] == 0); cases++; if (!ok) { bad++; printf(" FAIL desired=%d(%s) avail=%d -> rc=%d eff=%d calls=%d (want %d/%d)\n", desired, ncm_api_level_str(desired), avail, rc, eff, total_calls(), want, desired - want + 1); g_fail = 1; } } /* 全不可用 → NOTFOUND,且恰好 desired+1 次请求 */ reset_calls(); g_avail_from = -1; g_type = "mp3"; int rc = ncm_api_song_url_auto_f("dev", "ck", "1", desired, &u, &eff, fake_try); int ok = (rc == NCM_ERR_NOTFOUND) && (eff == -1) && (total_calls() == desired - NCM_LEVEL_STANDARD + 1); cases++; if (!ok) { bad++; printf(" FAIL desired=%d all-unavail -> rc=%d eff=%d calls=%d\n", desired, rc, eff, total_calls()); g_fail = 1; } } CHECK(bad == 0, "matrix %d cases all pass (desired 9 x avail 9 + 全不可用 9)", cases); printf("== 2) desired 越界钳制 ==\n"); reset_calls(); g_avail_from = NCM_LEVEL_SURROUND; g_type = "mp3"; CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_DOLBY, &u, &eff, fake_try) == NCM_OK && eff == NCM_LEVEL_SURROUND && total_calls() == 1 && g_calls[NCM_LEVEL_SURROUND] == 1, "desired=DOLBY(16) clamped to SURROUND (eff=%d calls=%d)", eff, total_calls()); reset_calls(); CHECK(ncm_api_song_url_auto_f("d", "c", "1", -7, &u, &eff, fake_try) == NCM_OK && eff == NCM_LEVEL_STANDARD && total_calls() == 1, "desired=-7 clamped to STANDARD (eff=%d)", eff); printf("== 3) 未知 type 跳过继续降 ==\n"); reset_calls(); g_avail_from = NCM_LEVEL_SURROUND; g_bad_type_from = NCM_LEVEL_HIRES; /* hires 及以上 url 在但 type=aac → 跳过,落在 lossless */ CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_SKY, &u, &eff, fake_try_badtype) == NCM_OK && eff == NCM_LEVEL_LOSSLESS, "aac above hires -> land lossless (eff=%d)", eff); printf("== 4) type 缺失按 url 扩展名收 flac ==\n"); reset_calls(); g_avail_from = NCM_LEVEL_LOSSLESS; g_type = ""; CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_LOSSLESS, &u, &eff, fake_try) == NCM_OK && strcmp(u.type, "flac") == 0, "type empty + .flac url -> type backfilled flac"); printf("== 5) out_level 每次独立(下首恢复用户档语义) ==\n"); reset_calls(); g_avail_from = NCM_LEVEL_STANDARD; g_type = "mp3"; ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_SURROUND, &u, &eff, fake_try); int first_eff = eff; reset_calls(); g_avail_from = NCM_LEVEL_SURROUND; ncm_api_song_url_auto_f("d", "c", "2", NCM_LEVEL_SURROUND, &u, &eff, fake_try); CHECK(first_eff == NCM_LEVEL_STANDARD && eff == NCM_LEVEL_SURROUND, "prev degraded to standard, next song back at surround (eff %d -> %d)", first_eff, eff); printf("== 6) 响应 level 静默降档采信(data[0].level < 请求档) ==\n"); reset_calls(); g_avail_from = NCM_LEVEL_SURROUND; g_type = "flac"; g_resp_level = "exhigh"; CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_HIRES, &u, &eff, fake_try) == NCM_OK && eff == NCM_LEVEL_EXHIGH, "req hires, resp level=exhigh -> eff=exhigh (got %d)", eff); reset_calls(); g_resp_level = "sky"; /* sky 族有歧义:不采信,保守取请求档 */ CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_SURROUND, &u, &eff, fake_try) == NCM_OK && eff == NCM_LEVEL_SURROUND, "req surround, resp level=sky (歧义) -> eff=surround (got %d)", eff); g_resp_level = NULL; printf(g_fail ? "== DEGRADE SIM: FAIL ==\n" : "== DEGRADE SIM: ALL PASS ==\n"); return g_fail; }