/* * test_meow_lyric.c — meow_lyric_* 宿主单测(2026-09-03,SDK 通用歌词组件批1)。 * * 编译(宿主,meow_lyric 为纯 libc + pthread,无需 SDK 其它对象): * gcc -Wall -Wextra -pthread -o test_meow_lyric test_meow_lyric.c meow_lyric.c -I. * ./test_meow_lyric * * 覆盖矩阵: * T1 解析基础 + 乱序自动按时间升序排序(原文行) * T2 meow_lyric_line_at 边界:首戳前 / 首戳 / 区间 / 恰逢戳 / 末戳后 / 负值 * T3 翻译合并:按相同 timestamp 匹配合并;无匹配翻译行忽略 * T4 服务器字面 \n / \r 当换行(含 \r\n 折叠成单空格) * T5 行上限截断:meow_lyric_set_max_lines 下调后 parse 超限行丢弃 * T6 时间戳小数位自适应:1 位(0.1s) / 2 位(厘秒) / 3 位(网易云毫秒) / >3 位截断到 ms * T7 越界访问:line_text/line_tl 越界返回 "";line_time 越界返回 -1 * T8 meow_lyric_clear:清空后 count=0 / text="" / line_time=-1;空输入 parse 返回 0 * T9 折行 meow_lyric_wrap:中西文混排断行(英文按词、中文按字符)+ max_rows 截断 * + 单字符超宽防死循环 + width 回调为 NULL 时按字节近似兜底 */ #include "meow_lyric.h" #include #include static int g_fail = 0; #define CHECK(cond, msg) do { \ if (cond) { printf(" PASS: %s\n", msg); } \ else { printf(" FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); g_fail = 1; } \ } while (0) /* 折行宽测量:ASCII=1 单位,多字节(CJK 等)=2 单位(用于中西文断行测试)。 */ static int uw(const char *u, int size, void *ctx) { (void)size; (void)ctx; int w = 0; for (const char *p = u; *p; ) { unsigned char c = (unsigned char)*p; int ch = (c < 0x80) ? 1 : (c < 0xE0) ? 2 : (c < 0xF0) ? 3 : 4; w += (c < 0x80) ? 1 : 2; p += ch; } return w; } static void t1_basic(void) { printf("T1 解析基础 + 乱序按时间升序排序\n"); /* 输入乱序;期望按时间戳升序存(Line B@1000 < Line C@2000 < Line A@3000) */ const char *lrc = "[00:03.00]Line A\n[00:01.00]Line B\n[00:02.00]Line C\n"; int n = meow_lyric_parse(lrc, NULL); CHECK(n == 3, "count==3"); CHECK(meow_lyric_count() == 3, "count()==3"); CHECK(strcmp(meow_lyric_line_text(0), "Line B") == 0, "idx0==Line B (1000ms 升序最前)"); CHECK(strcmp(meow_lyric_line_text(1), "Line C") == 0, "idx1==Line C"); CHECK(strcmp(meow_lyric_line_text(2), "Line A") == 0, "idx2==Line A (3000ms 最后)"); CHECK(meow_lyric_line_time(0) == 1000, "line_time(0)==1000"); CHECK(meow_lyric_line_time(2) == 3000, "line_time(2)==3000"); } static void t2_line_at(void) { printf("T2 meow_lyric_line_at 边界\n"); const char *lrc = "[00:01.00]a\n[00:02.00]b\n[00:03.00]c\n"; meow_lyric_parse(lrc, NULL); CHECK(meow_lyric_line_at(500) == 0, "首戳前(500ms)→0"); CHECK(meow_lyric_line_at(1000) == 0, "恰逢首戳→0"); CHECK(meow_lyric_line_at(1500) == 0, "首区间(1500ms)→0"); CHECK(meow_lyric_line_at(2500) == 1, "第二区间(2500ms)→1"); CHECK(meow_lyric_line_at(3000) == 2, "恰逢末戳→2"); CHECK(meow_lyric_line_at(99999) == 2, "末戳后→n-1"); CHECK(meow_lyric_line_at(-100) == 0, "负值→0"); } static void t3_translation(void) { printf("T3 翻译合并(同 timestamp;无匹配翻译行忽略)\n"); const char *lrc = "[00:02.00]Hello\n[00:05.00]World\n"; const char *tlrc = "[00:05.00]你好\n[00:02.00]你好世界\n[01:00.00]no match line\n"; int n = meow_lyric_parse(lrc, tlrc); CHECK(n == 2, "count==2(原文行数)"); CHECK(strcmp(meow_lyric_line_text(0), "Hello") == 0, "idx0原文 Hello"); CHECK(strcmp(meow_lyric_line_tl(0), "你好世界") == 0, "idx0翻译 你好世界(按 2000ms 合并)"); CHECK(strcmp(meow_lyric_line_text(1), "World") == 0, "idx1原文 World"); CHECK(strcmp(meow_lyric_line_tl(1), "你好") == 0, "idx1翻译 你好(按 5000ms 合并)"); /* 01:00.00 无对应原文行 → 丢弃,不新增行也不污染其它翻译 */ CHECK(meow_lyric_count() == 2, "无匹配翻译行被丢弃(仍 2 行)"); } static void t4_literal_newline(void) { printf("T4 服务器字面 \\n / \\r 当换行\n"); /* C 源码里 \\n 是字面反斜杠+n(服务器塞进一行的多行文本) */ const char *lrc = "[00:01.00]line1\\nline2\r\n[00:02.00]a\\r\\nb\n"; meow_lyric_parse(lrc, NULL); CHECK(strcmp(meow_lyric_line_text(0), "line1 line2") == 0, "字面 \\n → 空格"); CHECK(strcmp(meow_lyric_line_text(1), "a b") == 0, "字面 \\r\\n 成对 → 单空格"); } static void t5_limit(void) { printf("T5 行上限截断(set_max_lines 下调后超限丢弃)\n"); meow_lyric_set_max_lines(2); const char *lrc = "[00:01]a\n[00:02]b\n[00:03]c\n[00:04]d\n"; int n = meow_lyric_parse(lrc, NULL); CHECK(n == 2, "count==2(上限 2)"); CHECK(strcmp(meow_lyric_line_text(0), "a") == 0, "idx0==a"); CHECK(strcmp(meow_lyric_line_text(1), "b") == 0, "idx1==b(第三个 c 被截断)"); meow_lyric_set_max_lines(MEOW_LYRIC_LINES_MAX); /* 恢复默认 */ n = meow_lyric_parse(lrc, NULL); CHECK(n == 4, "恢复上限后 count==4"); /* set_max_lines 越界 clamp */ meow_lyric_set_max_lines(0); n = meow_lyric_parse(lrc, NULL); CHECK(n == 1, "set_max_lines(0)→clamp=1,count==1"); meow_lyric_set_max_lines(MEOW_LYRIC_LINES_MAX); } static void t6_timefrac(void) { printf("T6 时间戳小数位自适应\n"); const char *lrc = "[00:01.5]a\n" /* 1 位 → 1500ms */ "[00:02.25]b\n" /* 2 位 → 2250ms */ "[00:03.456]c\n" /* 3 位(网易云毫秒) → 3456ms */ "[00:04.1234]d\n"; /* >3 位截断到 ms → 4123ms */ meow_lyric_parse(lrc, NULL); CHECK(meow_lyric_line_time(0) == 1500, "1 位小数 → 1500ms"); CHECK(meow_lyric_line_time(1) == 2250, "2 位小数 → 2250ms"); CHECK(meow_lyric_line_time(2) == 3456, "3 位小数 → 3456ms"); CHECK(meow_lyric_line_time(3) == 4123, ">3 位小数截断 → 4123ms"); /* 元数据行 [ti:..] 应跳过,不计入行(与歌词须在独立行;ncm 语义:首个非时间标签即令该行失效) */ meow_lyric_parse("[ti:Test]\n[ar:Someone]\n[00:01.00]x\n", NULL); CHECK(meow_lyric_count() == 1, "元数据 [ti:..]/[ar:..] 行不被当作歌词"); } static void t7_oob(void) { printf("T7 越界访问\n"); meow_lyric_parse("[00:01.00]x\n[00:02.00]y\n", NULL); CHECK(strcmp(meow_lyric_line_text(-1), "") == 0, "line_text(-1)==\"\""); CHECK(strcmp(meow_lyric_line_text(99), "") == 0, "line_text(99)==\"\""); CHECK(strcmp(meow_lyric_line_tl(-1), "") == 0, "line_tl(-1)==\"\""); CHECK(strcmp(meow_lyric_line_tl(99), "") == 0, "line_tl(99)==\"\""); CHECK(meow_lyric_line_time(-1) == -1, "line_time(-1)==-1"); CHECK(meow_lyric_line_time(99) == -1, "line_time(99)==-1"); } static void t8_clear_and_empty(void) { printf("T8 clear / 空输入\n"); meow_lyric_parse("[00:01.00]x\n", NULL); meow_lyric_clear(); CHECK(meow_lyric_count() == 0, "clear 后 count==0"); CHECK(strcmp(meow_lyric_line_text(0), "") == 0, "clear 后 line_text(0)==\"\""); CHECK(meow_lyric_line_time(0) == -1, "clear 后 line_time(0)==-1"); CHECK(meow_lyric_parse(NULL, NULL) == 0, "parse(NULL,NULL)→0"); CHECK(meow_lyric_count() == 0, "空解析后 count==0"); } static void t9_wrap(void) { printf("T9 折行 meow_lyric_wrap\n"); char seg[8][MEOW_LYRIC_SEG_SZ]; /* 中西文:英文按词断行、中文按字符断行 → "Hello" / "世界" 两段 */ int r = meow_lyric_wrap("Hello 世界", 28, 6, seg, 8, uw, NULL); CHECK(r == 2, "中西文折为 2 段"); CHECK(strcmp(seg[0], "Hello") == 0, "seg0==Hello(词边界断)"); CHECK(strcmp(seg[1], "世界") == 0, "seg1==世界(中文按字符)"); /* max_rows 截断:每单词一 bar,max_rows=3 → 只出 3 段 */ r = meow_lyric_wrap("a b c d e f g", 28, 1, seg, 3, uw, NULL); CHECK(r == 3, "max_rows=3 截断为 3 段"); /* 单字符超宽(maxw=0):能放一个字符且不死循环,返回完整逐字符段 */ r = meow_lyric_wrap("abc", 28, 0, seg, 5, uw, NULL); CHECK(r == 3, "maxw=0 逐字符折为 3 段(无死循环)"); CHECK(strcmp(seg[0], "a") == 0 && strcmp(seg[2], "c") == 0, "seg0==a / seg2==c"); /* width 回调 NULL → 字节近似兜底(maxw 作字节预算) */ r = meow_lyric_wrap("abcd", 28, 2, seg, 4, NULL, NULL); CHECK(r == 2, "width=NULL 按字节近似折为 2 段"); CHECK(strcmp(seg[0], "ab") == 0 && strcmp(seg[1], "cd") == 0, "seg0==ab / seg1==cd"); /* 空/超短:txt 空 → 1 段空串 */ r = meow_lyric_wrap("", 28, 10, seg, 4, uw, NULL); CHECK(r == 1 && seg[0][0] == 0, "空文本 → 1 段空串"); CHECK(meow_lyric_wrap(NULL, 28, 10, seg, 4, uw, NULL) == 1, "NULL 文本 → 1 段"); } int main(void) { printf("== meow_lyric 宿主单测 ==\n"); t1_basic(); t2_line_at(); t3_translation(); t4_literal_newline(); t5_limit(); t6_timefrac(); t7_oob(); t8_clear_and_empty(); t9_wrap(); printf("%s\n", g_fail ? "== FAIL ==" : "== PASS =="); return g_fail ? 1 : 0; }