Files
MeowOS-For-R3P2/patch_v2/ncm_app/test/test_ncm_cache.c
T
AskaEth e60642217a test(ncm): M6 缓存命名宿主单测(63 PASS)+ core 全量用例 + REPORT §39
- 新增 test/test_ncm_cache.c + build_ncm_cache_host.sh(纯 libc:真实 ncm_cache.c +
  ncm_tag.c,仅桩 strlcpy/mkdir_p/档位映射,不依赖 vendor/mbedtls)
- 覆盖:实际档映射、降级后写入名、hires 命中已存 exhigh/lossless、低档不命中高档、
  旧命名命中/扫描/删除/双点名迁移、列表去重、LRU/清空/删除兼容、level_from_path 边界
- test_ncm_core.c 新增 test_cache_actual_naming(接入 core 全量测试集,同语义断言)
- REPORT §39:命名/命中/兼容方案、质量取舍与未执行项(vendor/交叉/真机)
2026-11-04 21:11:00 +08:00

511 lines
21 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.
/* test/test_ncm_cache.c — M6 缓存命名「实际档」宿主单测(纯 libc,不依赖 mbedtls/jsmn)。
*
* 覆盖(对应任务:命名生成/解析、降级后写入名、hires 命中已存 exhigh、旧命名兼容、LRU 淘汰):
* ① ncm_cache_actual_level:请求档 + 容器/码率/位深 + 响应生效档 → 实际档(只降不升);
* ② 命名生成:实际档键 → songs/song_<id>_<实际档>.<实际格式>(降级后不再写请求档名);
* ③ 命中语义:hires 请求命中已存 song_<id>_exhigh.mp3 / song_<id>_lossless.flac(不重下),
* 低档请求不向上命中高档实体;
* ④ 旧命名兼容:song_<id>_hires.mp3(真机存量)可命中/可扫描/可删除,不迁移;
* 历史双点名 song_<id>..mp3 命中即迁移;LRU 淘汰对两种命名一视同仁;
* ⑤ 已缓存列表去重:同曲同实际档的旧名+新名只展示最近访问的一条;不同实际档都展示;
* ⑥ ncm_cache_level_from_path 解析(含 sky_c51 / 无档位段 / 封面名)。
*
* 编译/运行($TMPDIR 纪律;Termux 的 /tmp 不可写):./build_ncm_cache_host.sh && ./test/test_ncm_cache_host
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>
#include "ncm_common.h" /* NCM_OK / NCM_ERR_* */
#include "ncm_cache.h"
/* ---- ncm_cache.c 依赖的核心符号(宿主单测最小桩;与 core 实现同语义) ---- */
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;
}
int ncm_store_mkdir_p(const char *path)
{
if (!path || !*path) return -1;
char tmp[512];
size_t len = strlen(path);
if (len >= sizeof tmp) return -1;
memcpy(tmp, path, len + 1);
if (tmp[len - 1] == '/') tmp[len - 1] = '\0';
for (size_t i = 1; i <= strlen(tmp); i++) {
if (tmp[i] == '/' || tmp[i] == '\0') {
char ch = tmp[i];
tmp[i] = '\0';
if (tmp[0] && mkdir(tmp, 0755) != 0 && errno != EEXIST) return -1;
tmp[i] = ch;
}
}
return 0;
}
int ncm_store_read_cfg_int(const char *dir, const char *key, int def)
{ (void)dir; (void)key; return def; }
const char *ncm_api_level_str(int level)
{
switch (level) {
case NCM_LEVEL_SURROUND:
case NCM_LEVEL_SKY: return "sky";
case NCM_LEVEL_JYMASTER: return "jymaster";
case NCM_LEVEL_JYEFFECT: return "jyeffect";
case NCM_LEVEL_DOLBY: return "dolby";
case NCM_LEVEL_HIRES: return "hires";
case NCM_LEVEL_LOSSLESS: return "lossless";
case NCM_LEVEL_EXHIGH: return "exhigh";
case NCM_LEVEL_HIGHER: return "higher";
default: return "standard";
}
}
const char *ncm_api_level_key(int level)
{
if (level == NCM_LEVEL_SURROUND) return "sky_c51";
return ncm_api_level_str(level);
}
int ncm_api_level_from_key(const char *key)
{
if (!key || !key[0]) return -1;
if (!strcmp(key, "sky_c51")) return NCM_LEVEL_SURROUND;
if (!strcmp(key, "sky")) return NCM_LEVEL_SKY;
if (!strcmp(key, "jymaster")) return NCM_LEVEL_JYMASTER;
if (!strcmp(key, "jyeffect")) return NCM_LEVEL_JYEFFECT;
if (!strcmp(key, "dolby")) return NCM_LEVEL_DOLBY;
if (!strcmp(key, "hires")) return NCM_LEVEL_HIRES;
if (!strcmp(key, "lossless")) return NCM_LEVEL_LOSSLESS;
if (!strcmp(key, "exhigh")) return NCM_LEVEL_EXHIGH;
if (!strcmp(key, "higher")) return NCM_LEVEL_HIGHER;
if (!strcmp(key, "standard")) return NCM_LEVEL_STANDARD;
return -1;
}
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)
#define TMPDIR_PREFIX "ncm_cache_test"
static void fresh_dir(const char *sub)
{
char cmd[256];
snprintf(cmd, sizeof cmd, "rm -rf %s/%s; mkdir -p %s/%s",
TMPDIR_PREFIX, sub, TMPDIR_PREFIX, sub);
if (system(cmd) != 0) { /* best-effort */ }
}
/* ---- 样本:MP3 首帧(MPEG1 Layer III)+ 假音频字节 / FLAC STREAMINFO + 假帧 ---- */
static unsigned char *make_mp3(int kbps, size_t *len)
{
static unsigned char buf[512];
int idx = (kbps == 128) ? 9 : 14; /* 9=128k, 14=320k(MPEG1 L3 码率表) */
memset(buf, 0x11, sizeof buf);
buf[0] = 0xFF; buf[1] = 0xFB;
buf[2] = (unsigned char)((idx << 4) | 0x00); /* 44.1kHz(sr_idx=0) */
buf[3] = 0x00;
*len = sizeof buf;
return buf;
}
static unsigned char *make_flac(int bps, int sr, size_t *len)
{
static unsigned char buf[700];
size_t o = 0;
memset(buf, 0, sizeof buf);
memcpy(buf + o, "fLaC", 4); o += 4;
buf[o++] = 0x80; buf[o++] = 0x00; buf[o++] = 0x00; buf[o++] = 34; /* STREAMINFO, last */
unsigned char *si = buf + o;
si[0] = 0x10; si[1] = 0x00; si[2] = 0x10; si[3] = 0x00;
si[10] = (unsigned char)((sr >> 12) & 0xFF);
si[11] = (unsigned char)((sr >> 4) & 0xFF);
si[12] = (unsigned char)(((sr & 0x0F) << 4) | 0x02 | (((bps - 1) >> 4) & 0x01));
si[13] = (unsigned char)(((bps - 1) & 0x0F) << 4);
si[16] = 0xAC; si[17] = 0x44;
o += 34;
for (int i = 0; i < 512; i++) buf[o++] = (unsigned char)(0xF8 ^ i);
*len = o;
return buf;
}
static int file_put(const char *path, const void *data, size_t len)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
if (len && fwrite(data, 1, len, f) != len) { fclose(f); return -1; }
return fclose(f);
}
static int file_exists(const char *path)
{
struct stat st;
return stat(path, &st) == 0 && S_ISREG(st.st_mode);
}
static int file_exists_cstr(const char *dir, const char *name)
{
char p[512];
snprintf(p, sizeof p, "%s/songs/%s", dir, name);
return file_exists(p);
}
static void set_mtime(const char *path, long t)
{
struct timespec ts[2];
ts[0].tv_sec = t; ts[0].tv_nsec = 0;
ts[1].tv_sec = t; ts[1].tv_nsec = 0;
utimensat(AT_FDCWD, path, ts, 0);
}
/* 走生产写路径:实际档键 → ncm_cache_open/write/commit → 正式文件名。 */
static int cache_put(ncm_cache_t *c, const char *id, const char *level_key,
const char *ext, const void *data, size_t len,
char *out, size_t out_sz)
{
char key[64];
ncm_cache_writer_t *w;
snprintf(key, sizeof key, "%s_%s", id, level_key);
if (ncm_cache_open(c, key, ext, &w) != NCM_OK) return -1;
if (len && ncm_cache_write(w, (const unsigned char *)data, len) != NCM_OK) {
ncm_cache_abort(w);
return -1;
}
if (ncm_cache_commit(w) != 0) return -1;
return ncm_cache_path(c, key, ext, out, out_sz);
}
/* 直接落盘(旧命名/手工样本):songs/<name>,不走 writer。 */
static void raw_put(const ncm_cache_t *c, const char *name, const void *data, size_t len)
{
char p[NCM_CACHE_DIR_MAX + 64];
snprintf(p, sizeof p, "%s/%s", ncm_cache_songs_dir(c), name);
file_put(p, data, len);
}
/* ============================================================ */
static void test_actual_level(void)
{
ncm_audio_info_t ai;
printf("== actual level mapping (single source of truth) ==\n");
memset(&ai, 0, sizeof ai);
snprintf(ai.container, sizeof ai.container, "mp3");
ai.bitrate_kbps = 320;
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_EXHIGH,
"hires + 320k mp3 -> exhigh");
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_EXHIGH) == NCM_LEVEL_EXHIGH,
"hires + 320k mp3 + chain eff exhigh -> exhigh");
CHECK(ncm_cache_actual_level(NCM_LEVEL_STANDARD, &ai, -1) == NCM_LEVEL_STANDARD,
"standard request never upgraded by 320k mp3");
ai.bitrate_kbps = 128;
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_STANDARD,
"hires + 128k mp3 -> standard");
ai.bitrate_kbps = 0;
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_EXHIGH,
"hires + mp3 br unknown -> exhigh (mp3 never above exhigh)");
CHECK(ncm_cache_actual_level(NCM_LEVEL_SURROUND, &ai, -1) == NCM_LEVEL_EXHIGH,
"surround + mp3 br unknown -> exhigh");
memset(&ai, 0, sizeof ai);
snprintf(ai.container, sizeof ai.container, "flac");
ai.bits_per_sample = 16; ai.sample_rate = 44100;
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_LOSSLESS,
"hires + 16bit/44.1k flac -> lossless");
ai.bits_per_sample = 24; ai.sample_rate = 48000;
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_HIRES,
"hires + 24bit/48k flac -> hires");
ai.sample_rate = 96000;
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_HIRES,
"hires + 24bit/96k flac -> hires (request caps)");
CHECK(ncm_cache_actual_level(NCM_LEVEL_JYMASTER, &ai, -1) == NCM_LEVEL_JYEFFECT,
"jymaster + 24bit/96k flac -> jyeffect");
ai.sample_rate = 192000;
CHECK(ncm_cache_actual_level(NCM_LEVEL_JYMASTER, &ai, -1) == NCM_LEVEL_JYMASTER,
"jymaster + 24bit/192k flac -> jymaster");
memset(&ai, 0, sizeof ai);
snprintf(ai.container, sizeof ai.container, "flac"); /* 仅知容器(响应时点) */
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_LOSSLESS) == NCM_LEVEL_LOSSLESS,
"hires + flac(header unknown) + chain eff lossless -> lossless");
CHECK(ncm_cache_actual_level(-1, &ai, NCM_LEVEL_HIRES) == NCM_LEVEL_HIRES,
"unknown request + chain eff hires -> hires");
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, NULL, -1) == NCM_LEVEL_HIRES,
"no probe + unknown chain -> request level");
}
static void test_naming_degraded_write(void)
{
ncm_cache_t c;
char dir[256], path[NCM_CACHE_DIR_MAX + 64];
size_t mlen;
unsigned char *mp3 = make_mp3(320, &mlen);
ncm_audio_info_t ai;
int actual;
printf("== naming: degraded write uses actual level (M6) ==\n");
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/naming");
fresh_dir("naming");
ncm_cache_init(&c, dir);
memset(&ai, 0, sizeof ai);
snprintf(ai.container, sizeof ai.container, "mp3");
ai.bitrate_kbps = 320;
actual = ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_EXHIGH);
CHECK(actual == NCM_LEVEL_EXHIGH, "degraded response actual level = exhigh");
CHECK(cache_put(&c, "171650", ncm_api_level_key(actual), "mp3",
mp3, mlen, path, sizeof path) == 0, "write with actual-level key");
CHECK(strstr(path, "song_171650_exhigh.mp3") != NULL,
"file name reflects actual level (%s)", path);
CHECK(file_exists(path), "actual-named file on disk");
CHECK(!file_exists_cstr(dir, "song_171650_hires.mp3"),
"no request-level name written for degraded download");
CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_EXHIGH,
"level parsed from new file name = exhigh");
}
static void test_hit_semantics(void)
{
ncm_cache_t c;
char dir[256], path[NCM_CACHE_DIR_MAX + 64];
size_t mlen, flen;
unsigned char *mp3 = make_mp3(320, &mlen);
unsigned char *flac = make_flac(16, 44100, &flen);
printf("== hit semantics: hires request finds stored actual-level entries ==\n");
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/hit");
fresh_dir("hit");
ncm_cache_init(&c, dir);
/* 已存:exhigh mp3(新命名)+ lossless flac(新命名) */
CHECK(cache_put(&c, "2001", "exhigh", "mp3", mp3, mlen, path, sizeof path) == 0,
"store exhigh mp3");
CHECK(cache_put(&c, "2002", "lossless", "flac", flac, flen, path, sizeof path) == 0,
"store lossless flac");
CHECK(ncm_cache_lookup(&c, "2001_hires", "mp3", path, sizeof path) == NCM_OK &&
strstr(path, "song_2001_exhigh.mp3") != NULL,
"hires request hits stored exhigh mp3 (no redownload)");
CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_EXHIGH,
"hit level = exhigh");
CHECK(ncm_cache_lookup(&c, "2002_hires", "flac", path, sizeof path) == NCM_OK &&
strstr(path, "song_2002_lossless.flac") != NULL,
"hires request hits stored lossless flac");
CHECK(ncm_cache_lookup(&c, "2002_exhigh", "flac", path, sizeof path) == NCM_ERR_NOENT,
"lower request never hits higher-tier entity (no upward lookup)");
CHECK(ncm_cache_lookup(&c, "2001_exhigh", "mp3", path, sizeof path) == NCM_OK,
"exact level still hits directly");
CHECK(ncm_cache_lookup(&c, "2001_standard", "mp3", path, sizeof path) == NCM_ERR_NOENT,
"standard request does not use 320k exhigh file");
CHECK(ncm_cache_lookup(&c, "9999_hires", "mp3", path, sizeof path) == NCM_ERR_NOENT,
"unknown song misses");
}
static void test_legacy_compat(void)
{
ncm_cache_t c;
char dir[256], path[NCM_CACHE_DIR_MAX + 64];
size_t mlen;
unsigned char *mp3 = make_mp3(320, &mlen);
printf("== legacy naming compatibility (read/delete/scan, no migration) ==\n");
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/legacy");
fresh_dir("legacy");
ncm_cache_init(&c, dir);
/* 真机存量形态:请求档 hires + 实际 320k mp3 */
raw_put(&c, "song_171650_hires.mp3", mp3, mlen);
CHECK(ncm_cache_lookup(&c, "171650_hires", "mp3", path, sizeof path) == NCM_OK &&
strstr(path, "song_171650_hires.mp3") != NULL,
"legacy hires.mp3 still playable at its request key");
CHECK(file_exists(path), "legacy file untouched (not renamed)");
CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_HIRES,
"path level is the legacy request level (hires)");
/* 扫描:旧文件的档位段保留(删除/点播定位用),实际档由文件头纠正 */
{
ncm_cache_item_t items[8];
int n = ncm_cache_scan(&c, items, 8);
CHECK(n == 1, "scan lists legacy entry (n=%d)", n);
CHECK(n == 1 && strcmp(items[0].id, "171650") == 0 &&
strcmp(items[0].level, "hires") == 0 && strcmp(items[0].ext, "mp3") == 0,
"legacy item id/level/ext parsed");
CHECK(n == 1 && items[0].actual_level == NCM_LEVEL_EXHIGH,
"legacy hires.mp3 displays actual level exhigh (got %d)",
n == 1 ? items[0].actual_level : -999);
if (n == 1)
CHECK(ncm_cache_remove(&c, items[0].id, items[0].level, items[0].ext) == NCM_OK,
"delete by scanned legacy key");
CHECK(!file_exists_cstr(dir, "song_171650_hires.mp3"),
"legacy file removed by list delete path");
}
/* 历史双点名 song_<id>..mp3:命中即迁移到单点名 */
raw_put(&c, "song_171651..mp3", mp3, mlen);
CHECK(ncm_cache_lookup(&c, "171651", "mp3", path, sizeof path) == NCM_OK &&
strstr(path, "song_171651.mp3") != NULL && strstr(path, "..") == NULL,
"legacy double-dot migrated on lookup (%s)", path);
CHECK(!file_exists_cstr(dir, "song_171651..mp3"), "double-dot source gone");
/* 旧无档位键 song_<id>.mp3:任何请求档都回退命中(调用方第 3 步 lookup(id)) */
CHECK(ncm_cache_lookup(&c, "171651", "mp3", path, sizeof path) == NCM_OK,
"no-level legacy key still hit");
CHECK(ncm_cache_remove(&c, "171651", "", "mp3") == NCM_OK, "remove no-level legacy key");
}
static void test_scan_dedup(void)
{
ncm_cache_t c;
char dir[256], path[NCM_CACHE_DIR_MAX + 64];
size_t mlen, flen;
unsigned char *mp3 = make_mp3(320, &mlen);
unsigned char *flac = make_flac(16, 44100, &flen);
printf("== cache list dedup: same song same actual level ==\n");
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/dedup");
fresh_dir("dedup");
ncm_cache_init(&c, dir);
/* 同曲同实际档、两种命名并存(旧 song_3001_hires.mp3 新 song_3001_exhigh.mp3) */
raw_put(&c, "song_3001_hires.mp3", mp3, mlen);
raw_put(&c, "song_3001_exhigh.mp3", mp3, mlen);
{
char p[512];
snprintf(p, sizeof p, "%s/songs/song_3001_hires.mp3", dir);
set_mtime(p, 1000);
snprintf(p, sizeof p, "%s/songs/song_3001_exhigh.mp3", dir);
set_mtime(p, 2000);
}
CHECK(cache_put(&c, "3002", "lossless", "flac", flac, flen, path, sizeof path) == 0,
"store lossless entry for tier-variety case");
CHECK(cache_put(&c, "3002", "exhigh", "mp3", mp3, mlen, path, sizeof path) == 0,
"store exhigh entry for same song");
{
ncm_cache_item_t items[8];
int n = ncm_cache_scan(&c, items, 8);
int d3001 = 0, d3002 = 0;
int lv3001 = -999;
for (int i = 0; i < n; i++) {
if (strcmp(items[i].id, "3001") == 0) { d3001++; lv3001 = items[i].actual_level; }
if (strcmp(items[i].id, "3002") == 0) d3002++;
}
CHECK(d3001 == 1, "same song same actual level deduped to 1 row (got %d)", d3001);
CHECK(lv3001 == NCM_LEVEL_EXHIGH, "deduped row keeps actual level exhigh (got %d)", lv3001);
CHECK(d3002 == 2, "different actual levels both listed (got %d)", d3002);
}
}
static void test_lru_compat(void)
{
ncm_cache_t c;
char dir[256];
size_t mlen;
unsigned char *mp3 = make_mp3(320, &mlen);
char oldp[512], newp[512];
int fd;
printf("== LRU eviction compatibility (legacy + actual naming) ==\n");
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/lru");
fresh_dir("lru");
ncm_cache_init(&c, dir);
ncm_cache_set_limit_gb(&c, 1); /* 1GB 最小上限 */
/* 稀疏文件:旧命名 700MB(最旧)+ 新命名 400MB(最新)→ 超限删最旧 */
snprintf(oldp, sizeof oldp, "%s/songs/song_4001_hires.mp3", dir);
snprintf(newp, sizeof newp, "%s/songs/song_4002_exhigh.mp3", dir);
fd = open(oldp, O_WRONLY | O_CREAT, 0644); if (fd >= 0) { if (ftruncate(fd, 700L << 20) != 0) {} close(fd); }
fd = open(newp, O_WRONLY | O_CREAT, 0644); if (fd >= 0) { if (ftruncate(fd, 400L << 20) != 0) {} close(fd); }
set_mtime(oldp, 1000);
set_mtime(newp, 2000);
CHECK(ncm_cache_total_bytes(&c) == (700ULL << 20) + (400ULL << 20),
"total counts both naming schemes (%llu)",
(unsigned long long)ncm_cache_total_bytes(&c));
CHECK(ncm_cache_evict(&c) == NCM_OK, "evict runs");
CHECK(!file_exists(oldp), "oldest legacy-named entry evicted");
CHECK(file_exists(newp), "newest actual-named entry kept");
CHECK(ncm_cache_total_bytes(&c) == (400ULL << 20), "total after evict = 400MB (%llu)",
(unsigned long long)ncm_cache_total_bytes(&c));
ncm_cache_set_limit_gb(&c, 10);
/* 清空兼容两种命名 */
raw_put(&c, "song_4003_hires.mp3", mp3, mlen);
raw_put(&c, "song_4004_exhigh.mp3", mp3, mlen);
CHECK(ncm_cache_clear(&c) == NCM_OK, "clear runs");
CHECK(!file_exists(newp) && !file_exists_cstr(dir, "song_4003_hires.mp3") &&
!file_exists_cstr(dir, "song_4004_exhigh.mp3"),
"clear removes both naming schemes");
}
static void test_remove_compat(void)
{
ncm_cache_t c;
char dir[256];
size_t mlen;
unsigned char *mp3 = make_mp3(320, &mlen);
printf("== remove compatibility ==\n");
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/remove");
fresh_dir("remove");
ncm_cache_init(&c, dir);
raw_put(&c, "song_5001_hires.mp3", mp3, mlen); /* 旧请求档名 */
raw_put(&c, "song_5002_exhigh.mp3", mp3, mlen); /* 新实际档名 */
raw_put(&c, "song_5003.mp3", mp3, mlen); /* 旧无档位键 */
CHECK(ncm_cache_remove(&c, "5001", "hires", "mp3") == NCM_OK, "remove legacy request-level name");
CHECK(ncm_cache_remove(&c, "5002", "exhigh", "mp3") == NCM_OK, "remove actual-level name");
CHECK(ncm_cache_remove(&c, "5003", "", "mp3") == NCM_OK, "remove no-level name");
CHECK(ncm_cache_remove(&c, "5002", "exhigh", "mp3") == NCM_ERR_NOENT, "second remove = NOENT");
}
static void test_level_from_path(void)
{
printf("== level_from_path parsing ==\n");
CHECK(ncm_cache_level_from_path("/x/songs/song_171650_exhigh.mp3") == NCM_LEVEL_EXHIGH,
"song_<id>_exhigh.mp3 -> exhigh");
CHECK(ncm_cache_level_from_path("song_171650_sky_c51.flac") == NCM_LEVEL_SURROUND,
"song_<id>_sky_c51.flac -> surround");
CHECK(ncm_cache_level_from_path("song_171650.mp3") == -1, "no level segment -> -1");
CHECK(ncm_cache_level_from_path("cover_current.jpg") == -1, "cover name -> -1");
CHECK(ncm_cache_level_from_path("/tmp/pf_hires.mp3") == NCM_LEVEL_HIRES,
"bare <name>_<level>.ext parsed");
CHECK(ncm_cache_level_from_path(NULL) == -1 && ncm_cache_level_from_path("") == -1,
"NULL/empty -> -1");
}
int main(void)
{
const char *base = getenv("TMPDIR");
printf("ncm_cache naming self-test (M6)\n");
if (base && base[0] && chdir(base) != 0) printf(" warn: chdir(%s) failed\n", base);
{
char cmd[128];
snprintf(cmd, sizeof cmd, "rm -rf %s; mkdir -p %s", TMPDIR_PREFIX, TMPDIR_PREFIX);
if (system(cmd) != 0) { /* best-effort */ }
}
test_actual_level();
test_naming_degraded_write();
test_hit_semantics();
test_legacy_compat();
test_scan_dedup();
test_lru_compat();
test_remove_compat();
test_level_from_path();
printf("\n%s\n", g_fail ? "== RESULT: FAIL ==" : "== RESULT: PASS ==");
return g_fail ? 1 : 0;
}