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 卡可拔出直读、配置可改;存储不使用加密库,
锁屏与设置页文案均如实标注该局限。
132 lines
5.2 KiB
C
132 lines
5.2 KiB
C
/* 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;
|
||
}
|