Files
MeowOS-SDK/tuku/test/test_settings_reader_persist.c
T
AskaEth cca3906c80 图库: M5 设置页(阅读方向/PIN/主题状态)与启动 PIN 锁屏;阅读方向持久化(评审 P1)
logic/pin(纯 C):加盐迭代哈希(FNV-1a ×1000 轮 + 每次设置新随机盐,16 位 hex)经
meow_config_* 持久化 pin.enabled/pin.hash/pin.salt;提供 set/verify/change/disable 与
4-8 位纯数字校验;enabled=1 但盐/哈希残缺时 fail-closed;落盘半途失败会还原旧盐,不留
「新盐+旧哈希」的双失效状态。

ui/settings(ACT_SETTINGS):MD3 分组表单(meow_listview 容器圆角卡 + 段头标签)——
阅读方向 RTL/LTR 走 mr_reader_set_direction;启动 PIN 开关与修改(开启/修改均两遍
MEOW_IME_NUM 确认);版本与主题状态行(跟随系统 + 当前明暗,随 app.c 主题 listener
整帧重绘)。控件一律在 on_enter 建(栈顶=ACT_SETTINGS),避免 §6.1 owner 绑到
ACT_LIBRARY 的跨页命中泄漏。

ui/lock(ACT_LOCK):lock_view_should_show() 读 logic/pin;启用时启动先入锁屏,验证前
不绘制任何书库内容;「输入 PIN」按钮弹系统数字键盘(MEOW_IME_NUM),错 PIN 就地提示
重试(无惩罚性锁定),正确则 page_pop 回书库;on_event 消费 SWIPE——左缘返回不能绕过
PIN 或退出 App(meow_view_dispatch_event 先于 app.c 的 on_swipe)。

logic/reader(评审 P1 修复,最小持久化):mr_reader_set_direction 成功切换后 best-effort
写 meow_config("reader.dir","rtl"/"ltr"),写失败只打 WARN 不阻断本次切换;
mr_reader_get_direction 首次调用惰性读一次配置恢复(无配置/非法值 → 默认 RTL),之后
纯内存快路径。宿主测试经 -DMR_READER_TEST_HOOKS 的 mr_reader_test_reset() 模拟重启。

测试/构建:test_pin(85 项,PIN 全分支,含单键半途落盘失败);test_settings_reader_persist
(19 项:默认/落盘/重启恢复/坏值/非法值/只读一次/写失败只告警);可选宿主集成
test_settings_lock_host(真实 SDK view/activity/widget + IME 注入,端到端验证错/对 PIN
解锁与设置页两遍开启;SDK 无源码时 SKIP);run_tests.sh 保持 46/46 并纳入上述测试
(reader.c 的配置/日志依赖由 test/settings_reader_stub.c 存根),build_tuku.sh 加入
logic/pin.c。交叉构建静态 MIPS ELF(zig-mipsel)零警告。

安全口径:PIN 防随手翻看,不是安全边界——TF 卡可拔出直读、配置可改;存储不使用加密库,
锁屏与设置页文案均如实标注该局限。
2026-11-04 21:25:17 +08:00

132 lines
5.2 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_settings_reader_persist.c — 阅读方向持久化宿主测试(M5 评审 P1 修复)。
*
* 被测:logic/reader.c 的 meow_config("reader.dir") 持久化。
* 「新进程语义」用 reader.c 的测试钩子 mr_reader_test_reset()(-DMR_READER_TEST_HOOKS)
* 模拟:清掉静态恢复标记,配置存根保留——等价于重启后首次 get 从配置恢复。
*
* 覆盖:
* T1 无配置默认 RTL,且 get 不写配置;
* T2 set LTR 落盘 + 内存立即生效;T3 reset 后从配置恢复 LTR;
* T4 set RTL 落盘 + reset 恢复 RTL;
* T5 坏值("sideways")忽略 → 默认 RTL,且 get 不回写坏值;
* T6 非法方向(99)忽略且不落盘;
* T7 坏值后 set 合法方向会覆盖配置;
* T8 配置只读一次(首次 get 后配置再变不影响本次内存态 = 快路径);
* T9 写失败:切换仍生效(内存)、只打一条 WARN、配置未变。
*
* 编译(tuku/ 下,SDK 仅需公共头):
* gcc -Wall -Wextra -O1 -DMR_READER_TEST_HOOKS -I. -I"$SDK" \
* test/test_settings_reader_persist.c logic/reader.c test/settings_reader_stub.c \
* -o test/test_settings_reader_persist && ./test/test_settings_reader_persist
*/
#include "logic/reader.h"
#include "test/settings_reader_stub.h"
#include <stdio.h>
#include <string.h>
/* reader.c 在 -DMR_READER_TEST_HOOKS 下导出(正式构建无此符号,故不写进 reader.h,
* 避免生产头暴露测试入口)。 */
extern void mr_reader_test_reset(void);
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 int cfg_is(const char *want)
{
char buf[32] = "";
if (!mr_test_cfg_get("reader.dir", buf, (int)sizeof buf)) return 0;
return strcmp(buf, want) == 0;
}
static int cfg_has(const char *key)
{
char buf[32];
return mr_test_cfg_get(key, buf, (int)sizeof buf);
}
static void test_default_and_ltr(void)
{
/* T1 无配置 → 默认 RTL;get 是只读的。 */
mr_test_cfg_clear();
mr_reader_test_reset();
CHECK(mr_reader_get_direction() == MR_DIR_RTL, "T1 default RTL with no config");
CHECK(!cfg_has("reader.dir"), "T1 get does not write config");
/* T2 set LTR:内存立即生效 + 落盘 "ltr"。 */
mr_reader_set_direction(MR_DIR_LTR);
CHECK(mr_reader_get_direction() == MR_DIR_LTR, "T2 memory updated immediately");
CHECK(cfg_is("ltr"), "T2 set LTR persists reader.dir=ltr");
/* T3 模拟重启 → 从配置恢复 LTR。 */
mr_reader_test_reset();
CHECK(mr_reader_get_direction() == MR_DIR_LTR, "T3 restore LTR after restart");
/* T4 反向:set RTL 落盘并恢复。 */
mr_reader_set_direction(MR_DIR_RTL);
CHECK(cfg_is("rtl"), "T4 set RTL persists reader.dir=rtl");
mr_reader_test_reset();
CHECK(mr_reader_get_direction() == MR_DIR_RTL, "T4 restore RTL after restart");
}
static void test_bad_values(void)
{
/* T5 坏值忽略 → 默认 RTL,且 get 不改写坏值。 */
mr_test_cfg_clear();
mr_test_cfg_put("reader.dir", "sideways");
mr_reader_test_reset();
CHECK(mr_reader_get_direction() == MR_DIR_RTL, "T5 bad value ignored -> default RTL");
CHECK(cfg_is("sideways"), "T5 get leaves bad value untouched");
/* T6 非法方向:忽略且不落盘。 */
mr_test_cfg_clear();
mr_reader_test_reset();
mr_reader_set_direction(99);
CHECK(mr_reader_get_direction() == MR_DIR_RTL, "T6 invalid dir ignored (memory)");
CHECK(!cfg_has("reader.dir"), "T6 invalid dir writes nothing");
/* T7 坏值后 set 合法方向 → 覆盖配置并在重启后生效。 */
mr_test_cfg_clear();
mr_test_cfg_put("reader.dir", "bogus");
mr_reader_test_reset();
mr_reader_set_direction(MR_DIR_LTR);
CHECK(cfg_is("ltr"), "T7 valid set overwrites bad config");
mr_reader_test_reset();
CHECK(mr_reader_get_direction() == MR_DIR_LTR, "T7 restored after overwrite");
}
static void test_read_once_and_failure(void)
{
/* T8 配置只读一次:首次 get 之后配置变化不影响内存(快路径)。 */
mr_test_cfg_clear();
mr_reader_test_reset();
CHECK(mr_reader_get_direction() == MR_DIR_RTL, "T8 first get with no config");
mr_test_cfg_put("reader.dir", "ltr");
CHECK(mr_reader_get_direction() == MR_DIR_RTL, "T8 config read only once (fast path)");
/* T9 写失败:切换仍生效、WARN 一条、配置不变。 */
mr_test_cfg_clear();
mr_reader_test_reset();
mr_test_log_clear();
mr_test_cfg_fail_writes(1);
mr_reader_set_direction(MR_DIR_LTR);
CHECK(mr_reader_get_direction() == MR_DIR_LTR, "T9 switch still applies on write failure");
CHECK(mr_test_log_warns() == 1, "T9 one WARN on write failure (got %d)", mr_test_log_warns());
CHECK(!cfg_has("reader.dir"), "T9 config unchanged on failure");
mr_test_cfg_fail_writes(0);
mr_reader_set_direction(MR_DIR_RTL);
CHECK(cfg_is("rtl") && mr_test_log_warns() == 1, "T9 recovery writes rtl, no extra WARN");
}
int main(void)
{
test_default_and_ltr();
test_bad_values();
test_read_once_and_failure();
printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail);
return g_fail ? 1 : 0;
}