fix(v0.8.0): 全量回归审查 —— 1 处 P0 数据丢失 + 4 处 P1 + 9 处 P2 根因修复
方法:四个对抗性子代理分头审查(数据正确性 / 文档宣称 vs 实现 / 公共 API 契约 / 测试质量),每条结论要求可复现证据;逐条复核 + 探针确认 + 变异验证(40 项全部 被对应用例拦住)。 P0:事务活跃期间 repair()/close()/周期 checkpoint 推进 WAL 水位 → 已 COMMIT 的 事务整批消失且恢复报告"干净"。根因 hasPendingFlushData()/computeDurableLsn() 不看 txnSnapshot;守卫此前只在 CheckpointManager 两个回调里。修复:守卫下沉到 computeDurableLsn() 与 advanceWalCheckpoint() 入口(唯一实现)。 P1: - WAL 前缀缺失丢弃整段活分片(回退上一代 manifest 时 kept 为空)→ 前缀缺失单独 记录,后缀照常重放;仅 fromLsn === 0 时才算真异常 - 孤儿回收门槛只看引擎层 dataLossSuspected,漏掉 LSM 层被丢的 SSTable → 统一 describeRecoveryDamage() 聚合判定(损坏时绝不删"引用不到"的文件) - vacuum() 逐层压缩绕过维护链 → vacuumLevels() 每层作为维护链任务执行 - reclaimRetiredNow() 无视在途读者(读者把"已退休"读成"文件损坏")→ 有读者时 退化为延迟回收 P2:WAL 记录级 CRC 损坏不计数不上报;旧格式表结构记录形状损坏静默当空库; bloomFilterBitsPerKey 配置被接受却完全不生效(构建器写死默认值,实现缺陷); 幽灵 meta;介质读故障等于文件损坏的语义无用例;manifest 回读校验两条守卫无用例; 文件名≠载荷世代判定无用例;pageIdWatermark 单调性无用例;分片号两条真实不变量 无用例。 覆盖率口径(第二处漏洞):interface.ts 混着三个运行时函数(cloneRow 等)却被 描述为"纯类型、不纳入统计" → 实现搬到 src/engine/row_clone.ts;搬完门禁真的 失败(functions 93.84% < 94%),补测退化路径后通过。 测试质量:3 条空壳用例改值级断言;1 条"全损坏"用例实际只走缓存 → 拆成两条真 用例;5 秒墙钟 race 改门控 + 失败上限;setTimeout 改 whenIdle();<= 收紧为 <。 变异脚本加固:正控(干净基线必须全绿)、编译失败/0 用例单独归类、300s 超时、 逐字节 sha256 恢复校验、O_EXCL 进程锁、锚点唯一性;变异 22 → 40 项。 文档两轮订正(16 + 11 条不成立宣称):MVCC 快照隔离、backup 一致性快照、 "空洞检测截断"、体积(251,109 B / gzip 63,145 B)、测试与覆盖率数字、 "5 种存储引擎"、Tree-shakable、错误码表补 16 个码、恢复报告字段、已知限制 (回退单向 / 多实例依赖 Web Locks / manifest 体积 / 尾部 WAL 分片不可识别)。 验证:常规套件 92 套件 / 1980 用例全绿;覆盖率 90.59 / 82.59 / 94.14 / 93.50 (阈值 90/82/94/93);e2e 14/14(真实 Chromium + OPFS + CDP 崩溃); 重型套件 4 套件 / 27 用例;变异 40/40;lint + 两份 tsc 干净;dist 已重建。
This commit is contained in:
+18
-3
@@ -75,7 +75,7 @@ src/
|
||||
├── plugin/ # Plugin system (14 lifecycle hooks)
|
||||
└── integrations/ # React & Vue hooks
|
||||
|
||||
tests/ # Test suite (1304 test cases, 76 suites + 12 e2e)
|
||||
tests/ # Test suite (1980 test cases, 92 suites + 14 e2e)
|
||||
tests/helpers/ # 共享测试工具(OPFS mock 等)
|
||||
tests/e2e/ # Playwright e2e(真实 Chromium + OPFS)
|
||||
site/ # Documentation site (index / docs / demo)
|
||||
@@ -155,7 +155,7 @@ npm run build
|
||||
|
||||
# Output in dist/
|
||||
# ├── metona-sqlark.js UMD
|
||||
# ├── metona-sqlark.min.js UMD minified (~105KB / ~27KB gzip)
|
||||
# ├── metona-sqlark.min.js UMD minified (251,109 B / gzip 63,145 B)
|
||||
# ├── metona-sqlark.esm.js ES Module
|
||||
# ├── metona-sqlark.cjs CommonJS
|
||||
# └── metona-sqlark.d.ts TypeScript declarations
|
||||
@@ -237,7 +237,7 @@ Open an issue at [git.metona.cn/MetonaTeam/MetonaSqlark/issues](https://git.meto
|
||||
不会失败的用例等于没有保护。
|
||||
|
||||
```bash
|
||||
# B-6(存储单一提交点 / LSM 结构根治)17 项变异验证
|
||||
# 全部 40 项变异(存储单一提交点 / LSM 结构根治 / v0.8.0 全量审查修复)
|
||||
python3 scripts/mutation-b6.py
|
||||
|
||||
# 只跑其中一条(按名字子串匹配)
|
||||
@@ -246,3 +246,18 @@ python3 scripts/mutation-b6.py "WAL 分片号复用"
|
||||
|
||||
脚本会临时改写 `src/`、运行对应用例、再恢复源码(收到 SIGINT/SIGTERM 也会恢复),
|
||||
最后打印每一条是"被拦住"还是"仍然通过"。**出现任何一条"仍然通过",本次提交不算完成。**
|
||||
|
||||
### 脚本自身的保证(v0.8.0 审查后加固)
|
||||
|
||||
| 机制 | 为什么需要 |
|
||||
| --- | --- |
|
||||
| **正控** | 先跑一遍未变异的干净基线(B-6 套件 + 分片 WAL 套件必须全绿)。没有正控就无法区分"变异被测试拦住"与"这套件本来就是红的" |
|
||||
| **编译失败 ≠ 被拦住** | `Test suite failed to run` / `error TS####` 单独归类为 `BAD(变异破坏编译)`:一个把源码改成语法错误的"变异"什么都没验证 |
|
||||
| **用例未匹配 ≠ 被拦住** | `Tests: 0 total` / `No tests found` 归类为 `BAD(用例未匹配)`(模式写错时最容易被误读成"测试通过了") |
|
||||
| **锚点唯一性** | 锚点在文件里出现多次时拒绝变异(否则可能改到另一处、验错位置) |
|
||||
| **超时** | 单条变异 300s 上限,超时归类为 `BAD(超时)` 而不是放过 |
|
||||
| **逐字节恢复校验** | 每条变异跑完用 sha256 比对恢复结果;不一致立即 `FATAL` 退出 |
|
||||
| **进程锁** | `.mutation-b6.lock`(`O_EXCL`):并发运行会互相踩掉对方的源码,直接拒绝启动 |
|
||||
|
||||
判定顺序固定为:编译失败 → 用例未匹配 → `✕`/`Tests: N failed`(= 被拦住)→ 其余为"仍然通过"。
|
||||
只有 `OK(变异被拦住)` 计入成功,其余任何状态都会让脚本以非零退出码结束。
|
||||
|
||||
Reference in New Issue
Block a user