删除: - AUDIT-aria-lsm-v0.7.4.md(50KB) - AUDIT-query-layer-v0.7.4.md(39KB) - AUDIT-storage-engines-v0.7.4.md(39KB) - PLAN-v0.7.5.md(98KB,含附录 G/H/I) 删除前先清除引用面,避免留下断链(共 18 处): - 源码注释 7 处(change-notifier / kvstore index / column-value / expression / sql-compare / where-matcher / validation):保留设计意图,引用改为"v0.8.0 审计根因 N" - 测试注释 9 处(opfs.spec / aria-opfs-backend / faulty-backend / storage-harness / v080-b6 / v080-kvstore / v080-query-layer / v080-sql-three-valued / v080-unified-validation / parser):同上 - CHANGELOG 3 处:改为不依赖已删除文档的自洽表述(B-6 交付物见各条;门禁订正三处 按内容重写),并把变异数量同步为 42 - 校验:三个 md 之间无断链;仓库内已无 PLAN-v0.7.5/AUDIT-* 的任何引用 (git 历史仍可追溯,需要时可 `git show <commit>:PLAN-v0.7.5.md` 找回) 验证:93 套件 / 1985 用例全绿;覆盖率 90.59 / 82.61 / 94.14 / 93.50(阈值 90/82/94/93); e2e 14/14;lint + 两份 tsc 干净;dist 已重建(注释只影响非压缩产物,min 产物 251,731 B / gzip 63,431 B 不变)。 说明:审查记录的核心内容仍在 CHANGELOG.md("全量回归审查"与"现场失败修复"两节), 随 PLAN 一起删除的是附录 G/H/I 的详细表格(门禁逐条验收、交付物清单、未修复项表)。
175 lines
6.8 KiB
TypeScript
175 lines
6.8 KiB
TypeScript
/**
|
||
* AriaEngine — OPFSBackend v2 测试
|
||
*
|
||
* 覆盖:
|
||
* 1. read/write/append/exists/delete/clear 基本语义
|
||
* 2. append 追加语义(keepExistingData + position)与字节顺序
|
||
* 3. 写队列串行化(并发写顺序一致)
|
||
* 4. 单次任务失败不中断队列链(后续写正常)
|
||
* 5. close 等待挂起写完成
|
||
* 6. open 清理崩溃残留临时文件(.crswap/.tmp)
|
||
* 7. writeMany/deleteMany 语义
|
||
*/
|
||
|
||
// ===================================================================
|
||
// v0.8.0: 删除本文件内的第三份 OPFS mock —— 改用共享的 storage-harness。
|
||
//
|
||
// 原实现的问题(见 v0.8.0 迭代工作流 C-1):
|
||
// - 与 tests/helpers/opfs-mock.ts 重复(两份都在测同一件事,语义各自漂移);
|
||
// - `close()` 是空函数、写入立即可见 → 无法表达"提交前崩溃";
|
||
// - 定义了 `writeCalls` 记录但**从未被任何断言使用**(死代码);
|
||
// - `entry.content.subarray ? entry.content : entry.content` 是恒等表达式(无意义分支)。
|
||
//
|
||
// 共享 harness 提供:真实提交语义(close 才可见)、读返回副本、
|
||
// keepExistingData:false 截断、字节级故障注入、真崩溃模拟。
|
||
// ===================================================================
|
||
import { OPFSBackend } from '../../src/engine/aria/store/opfs_backend';
|
||
import { resetOPFSMock } from '../helpers/storage-harness';
|
||
import { decode as decodeBytes } from '../helpers/assertions';
|
||
|
||
const enc = (s: string) => new Uint8Array(new TextEncoder().encode(s)).buffer;
|
||
|
||
describe('AriaEngine — OPFSBackend v2', () => {
|
||
beforeEach(() => {
|
||
resetOPFSMock();
|
||
});
|
||
|
||
it('read/write/exists/delete 基本语义', async () => {
|
||
const backend = new OPFSBackend();
|
||
await backend.open('opfs-test-1');
|
||
expect(backend.isOpen()).toBe(true);
|
||
|
||
await backend.write('k1', enc('hello'));
|
||
expect(await backend.exists('k1')).toBe(true);
|
||
expect(decodeBytes(await backend.read('k1'))).toBe('hello');
|
||
expect(await backend.read('missing')).toBeNull();
|
||
|
||
await backend.delete('k1');
|
||
expect(await backend.exists('k1')).toBe(false);
|
||
await backend.close();
|
||
expect(backend.isOpen()).toBe(false);
|
||
});
|
||
|
||
it('append 追加语义:字节顺序与位置正确', async () => {
|
||
const backend = new OPFSBackend();
|
||
await backend.open('opfs-test-2');
|
||
await backend.append('wal', enc('AAA'));
|
||
await backend.append('wal', enc('BBB'));
|
||
await backend.append('wal', enc('CCC'));
|
||
const all = await backend.read('wal');
|
||
expect(decodeBytes(all)).toBe('AAABBBCCC');
|
||
await backend.close();
|
||
});
|
||
|
||
it('write 覆盖语义(COW 原子替换)', async () => {
|
||
const backend = new OPFSBackend();
|
||
await backend.open('opfs-test-3');
|
||
await backend.write('f', enc('OLD-CONTENT'));
|
||
await backend.write('f', enc('NEW'));
|
||
expect(decodeBytes(await backend.read('f'))).toBe('NEW');
|
||
await backend.close();
|
||
});
|
||
|
||
it('并发写串行化:写入顺序与调用顺序一致', async () => {
|
||
const backend = new OPFSBackend();
|
||
await backend.open('opfs-test-4');
|
||
// 同时发起多个写(不 await 中间结果)
|
||
const p1 = backend.append('log', enc('A'));
|
||
const p2 = backend.append('log', enc('B'));
|
||
const p3 = backend.append('log', enc('C'));
|
||
await Promise.all([p1, p2, p3]);
|
||
expect(decodeBytes(await backend.read('log'))).toBe('ABC');
|
||
await backend.close();
|
||
});
|
||
|
||
it('单次写失败不中断队列链,后续写正常', async () => {
|
||
const backend = new OPFSBackend();
|
||
await backend.open('opfs-test-5');
|
||
|
||
// 注入失败:让 write 抛错一次
|
||
let failNext = true;
|
||
const origWrite = backend.write.bind(backend);
|
||
const mockDir = (backend as any).dbDir;
|
||
const origGetFileHandle = mockDir.getFileHandle.bind(mockDir);
|
||
mockDir.getFileHandle = async (name: string, opts?: { create?: boolean }) => {
|
||
if (failNext && name === 'boom') {
|
||
failNext = false;
|
||
throw new Error('Injected write failure');
|
||
}
|
||
return origGetFileHandle(name, opts);
|
||
};
|
||
(backend as any).dbDir = mockDir;
|
||
|
||
await expect(origWrite('boom', enc('x'))).rejects.toThrow('Injected write failure');
|
||
// 队列链恢复:后续写成功
|
||
await backend.write('ok', enc('fine'));
|
||
expect(decodeBytes(await backend.read('ok'))).toBe('fine');
|
||
await backend.close();
|
||
});
|
||
|
||
it('close 等待挂起写完成(挂起写不丢失)', async () => {
|
||
const backend = new OPFSBackend();
|
||
await backend.open('opfs-test-6');
|
||
|
||
// 发起一个写但不 await,立即 close
|
||
const pending = backend.append('late', enc('LATE-DATA'));
|
||
await backend.close();
|
||
await pending; // close 已等队列 → 写已完成
|
||
expect(pending).resolves.toBeUndefined();
|
||
});
|
||
|
||
it('open 清理崩溃残留临时文件(.crswap/.tmp)', async () => {
|
||
const backend = new OPFSBackend();
|
||
await backend.open('opfs-test-7');
|
||
await backend.write('data', enc('real'));
|
||
// 手工制造残留
|
||
const mockDir = (backend as any).dbDir;
|
||
await mockDir.getFileHandle('data.crswap', { create: true });
|
||
await mockDir.getFileHandle('data.tmp', { create: true });
|
||
await backend.close();
|
||
|
||
// 重开:open 自动清理
|
||
const backend2 = new OPFSBackend();
|
||
await backend2.open('opfs-test-7');
|
||
const keys = await backend2.listKeys();
|
||
expect(keys).toEqual(['data']);
|
||
expect(keys.some((k) => k.endsWith('.crswap') || k.endsWith('.tmp'))).toBe(false);
|
||
// 正常数据不受影响
|
||
expect(decodeBytes(await backend2.read('data'))).toBe('real');
|
||
await backend2.close();
|
||
});
|
||
|
||
it('writeMany / deleteMany 语义', async () => {
|
||
const backend = new OPFSBackend();
|
||
await backend.open('opfs-test-8');
|
||
await backend.writeMany({ a: enc('AAA'), b: enc('BBB') });
|
||
expect(decodeBytes(await backend.read('a'))).toBe('AAA');
|
||
expect(decodeBytes(await backend.read('b'))).toBe('BBB');
|
||
await backend.deleteMany(['a']);
|
||
expect(await backend.exists('a')).toBe(false);
|
||
expect(await backend.exists('b')).toBe(true);
|
||
await backend.clear();
|
||
expect(await backend.listKeys()).toEqual([]);
|
||
await backend.close();
|
||
});
|
||
|
||
it('close 后所有操作安全返回(dbDir 为 null 不抛错)', async () => {
|
||
const backend = new OPFSBackend();
|
||
await backend.open('opfs-test-9');
|
||
await backend.write('k', enc('v'));
|
||
await backend.close();
|
||
|
||
expect(backend.isOpen()).toBe(false);
|
||
expect(await backend.read('k')).toBeNull();
|
||
await backend.write('late', enc('x')); // 静默忽略
|
||
await backend.append('late2', enc('x'));
|
||
await backend.writeMany({ a: enc('1') });
|
||
await backend.delete('k');
|
||
await backend.deleteMany(['a']);
|
||
expect(await backend.listKeys()).toEqual([]);
|
||
expect(await backend.exists('k')).toBe(false);
|
||
await backend.clear();
|
||
await backend.cleanupStaleFiles();
|
||
});
|
||
});
|