/* test_jump_page.c — 跳页状态机 + 条漫落位映射宿主测试(无 SDK/硬件)。 * * 覆盖: * J1 跳页状态:begin 夹取/非法取消、active/target、预解码就绪落位、超时降级落位; * J2 落位映射:mr_strip_offset_for_page 与 page_at 互逆(首/中/末页,max 夹取); * J3 边界:空册/越界/重复取消;正常等待不提前落位(不白屏——没有图就不落)。 * * 编译(在 tuku/ 下): * gcc -Wall -Wextra -O1 -I. test/test_jump_page.c logic/reader/nav.c \ * -o test/test_jump_page && ./test/test_jump_page * 或 test/run_webtoon_tests.sh。 */ #include "logic/reader/nav.h" #include #include static int g_pass, g_fail; #define CHECK(cond, ...) do { \ if (cond) { g_pass++; } \ else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ } while (0) #define VW 480 #define VH 720 #define TIMEOUT 2500ULL static int g_meta_w[8], g_meta_h[8], g_meta_n; static int meta_cb(void *ctx, int i, int *w, int *h) { (void)ctx; if (i < 0 || i >= g_meta_n) return 0; *w = g_meta_w[i]; *h = g_meta_h[i]; return 1; } static void build_strip(mr_strip *s, int pages) { memset(s, 0, sizeof *s); g_meta_n = pages; for (int i = 0; i < pages; i++) { g_meta_w[i] = 800; g_meta_h[i] = 1200; /* 适宽后每页 720px */ } CHECK(mr_strip_build(s, pages, VW, VH, meta_cb, NULL) == 0, "build strip pages=%d", pages); } static void test_jump_state(void) { mr_jump j; memset(&j, 0, sizeof j); CHECK(mr_jump_active(&j) == 0, "fresh inactive"); CHECK(mr_jump_target(&j) == -1, "fresh target -1"); /* 正常:开始 → 未就绪继续等 → 就绪落位 */ mr_jump_begin(&j, 4, 10, 100); CHECK(mr_jump_active(&j) == 1 && mr_jump_target(&j) == 4, "begin active target=4"); int to = -1; CHECK(mr_jump_poll(&j, 0, 200, TIMEOUT, &to) == 0 && to == 0, "waiting no timeout"); CHECK(mr_jump_active(&j) == 1, "still active while waiting"); CHECK(mr_jump_poll(&j, 1, 300, TIMEOUT, &to) == 1 && to == 0, "ready lands"); CHECK(mr_jump_active(&j) == 0 && mr_jump_target(&j) == -1, "cleared after land"); /* 超时:仍然落位,但标记降级 */ mr_jump_begin(&j, 2, 10, 1000); CHECK(mr_jump_poll(&j, 0, 1000 + TIMEOUT - 1, TIMEOUT, &to) == 0, "just before timeout waits"); CHECK(mr_jump_poll(&j, 0, 1000 + TIMEOUT, TIMEOUT, &to) == 1 && to == 1, "timeout lands"); CHECK(mr_jump_active(&j) == 0, "timeout clears"); /* 就绪与超时同时成立:优先算正常就绪 */ mr_jump_begin(&j, 1, 10, 0); CHECK(mr_jump_poll(&j, 1, 999999, TIMEOUT, &to) == 1 && to == 0, "ready wins over timeout"); /* timeout=0 表示不限时 */ mr_jump_begin(&j, 1, 10, 0); CHECK(mr_jump_poll(&j, 0, 999999999ULL, 0, &to) == 0 && to == 0, "no timeout when 0"); /* begin 夹取/非法 */ mr_jump_begin(&j, -1, 10, 0); CHECK(mr_jump_active(&j) == 0, "begin(-1) cancels"); mr_jump_begin(&j, 10, 10, 0); CHECK(mr_jump_active(&j) == 0, "begin(count) cancels"); mr_jump_begin(&j, 0, 0, 0); CHECK(mr_jump_active(&j) == 0, "begin empty book cancels"); /* 取消幂等;NULL 安全 */ mr_jump_begin(&j, 3, 10, 5); mr_jump_cancel(&j); mr_jump_cancel(&j); CHECK(mr_jump_active(&j) == 0, "cancel idempotent"); CHECK(mr_jump_poll(NULL, 1, 1, TIMEOUT, &to) == 0, "poll NULL"); mr_jump_cancel(NULL); mr_jump_begin(NULL, 0, 10, 0); /* timed_out 可为 NULL */ mr_jump_begin(&j, 5, 10, 0); CHECK(mr_jump_poll(&j, 0, TIMEOUT + 1, TIMEOUT, NULL) == 1, "poll NULL timed_out"); } static void test_land_mapping(void) { mr_strip s; build_strip(&s, 5); CHECK(mr_strip_total(&s) == 3600, "total=%d", mr_strip_total(&s)); CHECK(mr_strip_max_offset(&s) == 2880, "max=%d", mr_strip_max_offset(&s)); int expect_top[5] = { 0, 720, 1440, 2160, 2880 }; for (int i = 0; i < 5; i++) { int off = mr_strip_offset_for_page(&s, i); CHECK(off == expect_top[i], "page %d offset %d (want %d)", i, off, expect_top[i]); CHECK(mr_strip_page_at(&s, off) == i, "page_at(offset(%d)) == %d", i, i); } /* 末页顶端恰在 max(整页铺满视口),落位不会越界 */ CHECK(mr_strip_offset_for_page(&s, 4) == mr_strip_max_offset(&s), "last page == max"); /* page+1 的偏移等于 page 的末端(页间无缝) */ CHECK(mr_strip_top(&s, 1) == mr_strip_top(&s, 0) + mr_strip_page_h(&s, 0), "page tops contiguous"); /* 跳页目标页必须在解码窗里(否则会被淘汰):窗口只给 1 槽时也能覆盖目标 */ int f = -1, l = -1; CHECK(mr_strip_window(&s, 0, 1, &f, &l) == 1 && f == l, "single-slot window"); mr_strip_free(&s); /* 空册:没有可落位目标 */ mr_strip e; memset(&e, 0, sizeof e); CHECK(mr_strip_offset_for_page(&e, 0) == -1, "empty book no offset"); CHECK(mr_strip_page_at(&e, 0) == -1, "empty book no page"); } int main(void) { test_jump_state(); test_land_mapping(); printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail); return g_fail ? 1 : 0; }