Files
MeowOS-SDK/tuku/test/test_mode_persist.c
T
AskaEth ff97f3cee7 feat(tuku): M7 条漫模式与续读——模式一键切换/每册记忆、视口±1 懒解码、滑块跳页、册间衔接
- 条漫:meow_scroll 跟手+fling,按滚动位置懒解码视口±1 页(驻留上限 4 页);
  排版用 mr_book_meta 累加适宽高度(nav.c 纯逻辑,宿主可测)
- 模式:阅读 chrome 图标一键切翻页/条漫;每册模式与 last page 经 meow_config 持久化
  (reader.mode/page.<FNV1a64(册路径)>),重进该册恢复模式与页码
- 续读:离开/翻页/滚动停止节流落盘;书库扫描给含图册副标题追加「· 续读 Pn」
- 册间衔接:末页前进→同父目录下一含图册首页,首页后退→上一册末页;无相邻册轻提示不退出
- 跳页:chrome 内 meow_slider,目标页预解码完成后落位(2.5s 超时降级提示)
- 解码:改走 M12 预算解码 meow_img_load_file_dim;超大 PNG/BMP 先 probe 预判,
  超 40MiB 原始 RGBA 跳过并显示「图片无法显示」,12MiB 以上记 WARN(如实保留残余风险)
- 测试:新增条漫排版/跳页状态/模式续读持久化/册间边界宿主测试,以及真实 SDK 源码树上的
  阅读器集成测试(模式切换/跳页落位/续读/跨册),test/run_webtoon_tests.sh 一键运行
2026-11-04 21:49:33 +08:00

166 lines
7.4 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_mode_persist.c — 阅读模式 + 每册续读状态持久化宿主测试(无 SDK/硬件)。
*
* 覆盖:
* P1 模式值:valid/name/parse(paged/webtoon,非法值回退);
* P2 册标识键:reader.page/mode.<FNV1a64(规范路径)>,末尾斜杠等价、不同册不同键、
* 键字符集安全([A-Za-z0-9._-]);
* P3 默认值:无记录时 get_mode/get_page 返回调用方给的默认值;
* P4 读写往返 + 册间隔离 + 一次 save 写两键;
* P5 坏值/非法模式忽略(不覆盖已有记录);写失败只 WARN 不崩;
* P6 书库角标:page>0 → "续读 Pn";page==0/无记录 → 无角标。
*
* 编译(在 tuku/ 下):
* gcc -Wall -Wextra -O1 -I. test/test_mode_persist.c logic/reader.c \
* test/settings_reader_stub.c -o test/test_mode_persist
* 或 test/run_webtoon_tests.sh。
*/
#include "logic/reader.h"
#include "test/settings_reader_stub.h"
#include "include/meow_ui_config.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
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)
static void test_mode_values(void)
{
CHECK(mr_mode_valid(MR_MODE_PAGED) == 1, "paged valid");
CHECK(mr_mode_valid(MR_MODE_WEBTOON) == 1, "webtoon valid");
CHECK(mr_mode_valid(-1) == 0 && mr_mode_valid(2) == 0, "invalid modes");
CHECK(strcmp(mr_mode_name(MR_MODE_PAGED), "paged") == 0, "name paged");
CHECK(strcmp(mr_mode_name(MR_MODE_WEBTOON), "webtoon") == 0, "name webtoon");
CHECK(strcmp(mr_mode_name(99), "paged") == 0, "name invalid -> paged");
CHECK(mr_mode_parse("paged") == MR_MODE_PAGED, "parse paged");
CHECK(mr_mode_parse("webtoon") == MR_MODE_WEBTOON, "parse webtoon");
CHECK(mr_mode_parse("WEBTOON") == -1, "parse case sensitive");
CHECK(mr_mode_parse("") == -1 && mr_mode_parse(NULL) == -1, "parse empty/NULL");
}
static void test_keys(void)
{
char a[64], b[64], c[64];
CHECK(mr_cont_key("/sd/book1", "page", a, (int)sizeof a) == 0, "key ok");
CHECK(strncmp(a, "reader.page.", 12) == 0, "key prefix %s", a);
CHECK(strlen(a) == 12 + 16, "key len=%zu", strlen(a));
for (size_t i = 12; i < strlen(a); i++)
CHECK(isxdigit((unsigned char)a[i]) != 0, "key hex char");
CHECK(mr_cont_key("/sd/book1/", "page", b, (int)sizeof b) == 0, "key trailing slash");
CHECK(strcmp(a, b) == 0, "trailing slash normalizes: %s vs %s", a, b);
CHECK(mr_cont_key("/sd/book1", "mode", c, (int)sizeof c) == 0, "mode key");
CHECK(strncmp(c, "reader.mode.", 12) == 0, "mode prefix %s", c);
CHECK(strcmp(a, c) != 0, "field differs");
CHECK(mr_cont_key("/sd/book2", "page", c, (int)sizeof c) == 0, "book2 key");
CHECK(strcmp(a, c) != 0, "different books differ");
/* 参数/缓冲边界 */
CHECK(mr_cont_key(NULL, "page", a, (int)sizeof a) == -1, "NULL dir");
CHECK(mr_cont_key("", "page", a, (int)sizeof a) == -1, "empty dir");
CHECK(mr_cont_key("/a", NULL, a, (int)sizeof a) == -1, "NULL field");
CHECK(mr_cont_key("/a", "page", NULL, 32) == -1, "NULL out");
CHECK(mr_cont_key("/a", "page", a, 8) == -1 && a[0] == '\0', "small out");
}
static void test_defaults_and_roundtrip(void)
{
mr_test_cfg_clear();
CHECK(mr_cont_get_mode("/sd/a", MR_MODE_PAGED) == MR_MODE_PAGED, "default paged");
CHECK(mr_cont_get_mode("/sd/a", MR_MODE_WEBTOON) == MR_MODE_WEBTOON, "default param used");
CHECK(mr_cont_get_page("/sd/a", 0) == 0, "default page 0");
CHECK(mr_cont_get_page("/sd/a", 7) == 7, "default page param");
CHECK(mr_cont_badge("/sd/a", NULL, 0) == 0, "badge NULL safe");
char badge[24];
CHECK(mr_cont_badge("/sd/a", badge, (int)sizeof badge) == 0 && badge[0] == '\0',
"no record -> no badge");
mr_cont_set_mode("/sd/a", MR_MODE_WEBTOON);
CHECK(mr_cont_get_mode("/sd/a", MR_MODE_PAGED) == MR_MODE_WEBTOON, "mode roundtrip");
CHECK(mr_cont_get_mode("/sd/b", MR_MODE_PAGED) == MR_MODE_PAGED, "mode isolated per book");
mr_cont_set_page("/sd/a", 17);
CHECK(mr_cont_get_page("/sd/a", -1) == 17, "page roundtrip");
CHECK(mr_cont_get_page("/sd/b", -1) == -1, "page isolated per book");
CHECK(mr_cont_badge("/sd/a", badge, (int)sizeof badge) == 1, "badge shown");
CHECK(strcmp(badge, "续读 P18") == 0, "badge text %s", badge);
/* 一次 save 两键(模式 + 页码) */
mr_cont_save("/sd/c", 3, MR_MODE_WEBTOON);
CHECK(mr_cont_get_page("/sd/c", -1) == 3, "save page");
CHECK(mr_cont_get_mode("/sd/c", MR_MODE_PAGED) == MR_MODE_WEBTOON, "save mode");
CHECK(mr_cont_badge("/sd/c", badge, (int)sizeof badge) == 1 &&
strcmp(badge, "续读 P4") == 0, "save badge %s", badge);
/* page 0 视为「没有进度」,不出角标 */
mr_cont_save("/sd/d", 0, MR_MODE_PAGED);
CHECK(mr_cont_badge("/sd/d", badge, (int)sizeof badge) == 0 && badge[0] == '\0',
"page 0 -> no badge");
/* NULL/空路径安全 */
mr_cont_set_page(NULL, 5);
mr_cont_set_mode(NULL, MR_MODE_WEBTOON);
mr_cont_save(NULL, 1, 1);
mr_cont_save("", 1, 1);
CHECK(mr_cont_get_page(NULL, 9) == 9 && mr_cont_get_mode(NULL, MR_MODE_PAGED) == MR_MODE_PAGED,
"NULL dir safe");
}
static void test_bad_values(void)
{
mr_test_cfg_clear();
char key[64];
char buf[24];
/* 坏模式值 → 默认;坏页码(负数/非数字/超上限)→ 默认 */
CHECK(mr_cont_key("/sd/x", "mode", key, (int)sizeof key) == 0, "bad key");
mr_test_cfg_put(key, "vertical");
CHECK(mr_cont_get_mode("/sd/x", MR_MODE_PAGED) == MR_MODE_PAGED, "bad mode -> def");
CHECK(mr_cont_key("/sd/x", "page", key, (int)sizeof key) == 0, "bad page key");
mr_test_cfg_put(key, "-3");
CHECK(mr_cont_get_page("/sd/x", 11) == 11, "negative page -> def");
mr_test_cfg_put(key, "abc");
CHECK(mr_cont_get_page("/sd/x", 12) == 12, "non-numeric page -> def");
mr_test_cfg_put(key, "99999999");
CHECK(mr_cont_get_page("/sd/x", 13) == 13, "too-large page -> def");
mr_test_cfg_put(key, "0");
CHECK(mr_cont_get_page("/sd/x", 14) == 0, "zero page readable");
/* 非法模式值不覆盖已有记录 */
mr_cont_set_mode("/sd/y", MR_MODE_WEBTOON);
mr_cont_set_mode("/sd/y", 42);
CHECK(mr_cont_get_mode("/sd/y", MR_MODE_PAGED) == MR_MODE_WEBTOON, "invalid set ignored");
/* 写失败:只告警,内存保持旧值;不崩 */
mr_cont_save("/sd/z", 5, MR_MODE_PAGED);
int warns = mr_test_log_warns();
mr_test_cfg_fail_writes(1);
mr_cont_set_page("/sd/z", 9);
mr_cont_set_mode("/sd/z", MR_MODE_WEBTOON);
mr_cont_save("/sd/z", 10, MR_MODE_WEBTOON);
CHECK(mr_test_log_warns() > warns, "write failure warns (warns=%d)", mr_test_log_warns());
CHECK(mr_cont_get_page("/sd/z", -1) == 5, "failed write keeps old page");
CHECK(mr_cont_get_mode("/sd/z", MR_MODE_PAGED) == MR_MODE_PAGED, "failed write keeps old mode");
mr_test_cfg_fail_writes(0);
/* 写入后读回同一字符串(便于排查:paged/webtoon 明文) */
CHECK(mr_cont_key("/sd/y", "mode", key, (int)sizeof key) == 0, "read mode key");
CHECK(mr_test_cfg_get(key, buf, (int)sizeof buf) == 1 && strcmp(buf, "webtoon") == 0,
"stored mode string %s", buf);
}
int main(void)
{
test_mode_values();
test_keys();
test_defaults_and_roundtrip();
test_bad_values();
printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail);
return g_fail ? 1 : 0;
}