/* test/seg_smoke.c — os.meow.reader 分段流式缓存宿主单测(纯 C,无 SDK/Freetype)。 * * 覆盖: * S1 UTF-8 大文件按 ~512KB 切段:段数>1、每段为合法 UTF-8(码位对齐,不切在字符中间)、 * 拼接后 == 原文件字节。 * S2 GBK(GB18030) 大文件切段:每段 raw 在完整双字节边界、逐段 rd_to_utf8 拼接 == 整文件 * 一次转换(边界不把双字节对拆开)。 * S3 幂等:同 (size,mtime,enc) 再次 build 段数不变、不重写。 * S4 每段原始字节长与 rdseg_load 读回一致。 */ #include "seg.h" #include "rdenc.h" #include #include #include static int g_pass = 0, g_fail = 0; #define CHECK(cond, msg) do { if (cond) { g_pass++; } else { g_fail++; printf("FAIL %s:%d %s\n", __FILE__, __LINE__, msg); } } while (0) /* 生成一个 utf8 大文件:CJK 3 字节 + ASCII + 换行,填充到 > 512KB */ static void write_utf8_big(const char *path) { FILE *f = fopen(path, "wb"); const uint8_t cjk[] = { 0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD }; /* 你好 */ size_t total = 0; while (total < RDSEG_SIZE + 100000) { for (int i = 0; i < 16; i++) { fwrite(cjk, 1, sizeof cjk, f); total += sizeof cjk; } const char *line = "这是一段用于验证分段对齐的中文正文,English words 123.\n"; size_t ll = strlen(line); fwrite(line, 1, ll, f); total += ll; } fclose(f); } /* 生成一个 GBK 大文件:双字节对(你好世界)重复,填到 > 512KB */ static void write_gbk_big(const char *path) { FILE *f = fopen(path, "wb"); const uint8_t gbk[] = { 0xC4, 0xE3, 0xBA, 0xC3, 0xCA, 0xC0, 0xBD, 0xE7 }; /* 你好世界 */ size_t total = 0; while (total < RDSEG_SIZE + 100000) { for (int i = 0; i < 16; i++) { fwrite(gbk, 1, sizeof gbk, f); total += sizeof gbk; } fputc('\n', f); total += 1; } fclose(f); } static void test_align_utf8(const char *seg_dir, const char *path) { /* 每段合法 UTF-8 */ int n = rdseg_n(seg_dir); CHECK(n >= 2, "utf8 segs >= 2"); for (int i = 0; i < n; i++) { size_t rl = 0; CHECK(rdseg_rawlen(seg_dir, i, &rl) == 0 && rl > 0, "utf8 rawlen>0"); uint8_t *b = (uint8_t *)malloc(rl ? rl : 1); size_t got = 0; CHECK(rdseg_load(seg_dir, i, b, rl, &got) == 0 && got == rl, "utf8 load==rawlen"); CHECK(rd_utf8_validate(b, got) == 1, "utf8 seg valid (no mid-char split)"); free(b); } /* 拼接后 == 原文件字节 */ size_t fz = 0; FILE *src = fopen(path, "rb"); fseek(src, 0, SEEK_END); fz = (size_t)ftell(src); fseek(src, 0, SEEK_SET); uint8_t *full = (uint8_t *)malloc(fz ? fz : 1); (void)!fread(full, 1, fz, src); fclose(src); size_t o = 0, ok = 1; for (int i = 0; i < n; i++) { size_t rl = 0; rdseg_rawlen(seg_dir, i, &rl); uint8_t *b = (uint8_t *)malloc(rl ? rl : 1); size_t got = 0; rdseg_load(seg_dir, i, b, rl, &got); if (o + got > fz || memcmp(full + o, b, got) != 0) ok = 0; o += got; free(b); } CHECK(ok && o == fz, "utf8 concat == original bytes"); free(full); } static void test_gbk_roundtrip(const char *seg_dir, const char *path) { int n = rdseg_n(seg_dir); CHECK(n >= 2, "gbk segs >= 2"); /* 整文件一次转换(金标准) */ FILE *src = fopen(path, "rb"); fseek(src, 0, SEEK_END); long fz = ftell(src); fseek(src, 0, SEEK_SET); uint8_t *raw = (uint8_t *)malloc((size_t)fz ? (size_t)fz : 1); (void)!fread(raw, 1, (size_t)fz, src); fclose(src); uint8_t *full = (uint8_t *)malloc((size_t)fz * 2 + 64); size_t fl = 0; int rc = rd_gb18030_to_utf8(raw, (size_t)fz, full, (size_t)fz * 2 + 64, &fl); CHECK(rc == RD_ENC_OK, "gbk full convert ok"); free(raw); /* 逐段转换拼接 */ uint8_t *concat = (uint8_t *)malloc((size_t)fz * 2 + 64); size_t o = 0, ok = 1; for (int i = 0; i < n; i++) { size_t rl = 0; rdseg_rawlen(seg_dir, i, &rl); uint8_t *b = (uint8_t *)malloc(rl ? rl : 1); size_t got = 0; rdseg_load(seg_dir, i, b, rl, &got); uint8_t *u8 = (uint8_t *)malloc(got * 2 + 64); size_t ul = 0; if (rd_gb18030_to_utf8(b, got, u8, got * 2 + 64, &ul) != RD_ENC_OK) ok = 0; if (o + ul > (size_t)fz * 2 + 64) ok = 0; if (ok) { memcpy(concat + o, u8, ul); o += ul; } free(u8); free(b); } CHECK(ok && o == fl && memcmp(concat, full, fl) == 0, "gbk per-seg convert concat == full convert"); free(concat); free(full); } int main(void) { rdseg_set_base("/tmp/meow_seg_base"); char dir[600]; const char *u8 = "/tmp/seg_u8.txt", *gbk = "/tmp/seg_gbk.txt"; write_utf8_big(u8); write_gbk_big(gbk); /* UTF-8 */ CHECK(rdseg_dir_for(u8, dir, sizeof dir) == 0, "utf8 dir_for"); int n = rdseg_build(u8, RD_ENC_UTF8, dir); CHECK(n >= 2, "utf8 build nsegs>=2"); test_align_utf8(dir, u8); /* 幂等:同键再建段数不变 */ CHECK(rdseg_build(u8, RD_ENC_UTF8, dir) == n, "utf8 build idempotent"); /* GBK */ CHECK(rdseg_dir_for(gbk, dir, sizeof dir) == 0, "gbk dir_for"); n = rdseg_build(gbk, RD_ENC_GB18030, dir); CHECK(n >= 2, "gbk build nsegs>=2"); test_gbk_roundtrip(dir, gbk); /* 大文件不切(清空再建,确保得到多个段也覆盖单段路径) */ { char small[600]; rdseg_set_base("/tmp/meow_seg_base2"); const char *tiny = "/tmp/seg_tiny.txt"; FILE *f = fopen(tiny, "wb"); fwrite("hello 你好 world\n", 1, strlen("hello 你好 world\n"), f); fclose(f); CHECK(rdseg_dir_for(tiny, small, sizeof small) == 0, "tiny dir"); int tn = rdseg_build(tiny, RD_ENC_UTF8, small); CHECK(tn == 1, "tiny build nsegs==1"); size_t rl = 0; CHECK(rdseg_rawlen(small, 0, &rl) == 0 && rl == strlen("hello 你好 world\n"), "tiny rawlen"); } remove(u8); remove(gbk); remove("/tmp/seg_tiny.txt"); printf("seg_smoke: %d pass, %d fail\n", g_pass, g_fail); return g_fail ? 1 : 0; }