Files

206 lines
9.2 KiB
C
Raw Permalink 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_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 <stdio.h>
#include <string.h>
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;
}