release: v0.5.1 — 存储后端生产级硬化(CRC-32/全库加密/WAL分片/页面化存储/多标签页锁/e2e)+ 深度审查修复(假实现接线/死代码清理)
CI / test (18.x) (push) Successful in 10m10s
CI / test (20.x) (push) Successful in 10m10s
CI / test (22.x) (push) Successful in 10m6s
CI / e2e (push) Successful in 9m51s
CI / test (24.x) (push) Successful in 10m28s

This commit is contained in:
thzxx
2026-08-10 12:07:00 +08:00
parent cff98b0903
commit 334067d89e
88 changed files with 15713 additions and 10626 deletions
+228
View File
@@ -0,0 +1,228 @@
/**
* 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 语义
*/
import { OPFSBackend } from '../../src/engine/aria/store/opfs_backend';
// ===================================================================
// 真实语义 OPFS mock:记录文件内容、支持 keepExistingData+position
// ===================================================================
interface MockFile {
content: ArrayBuffer;
writeCalls: { position?: number; data: ArrayBuffer; keepExistingData?: boolean }[];
}
function createOPFSMock() {
const files = new Map<string, MockFile>();
const getFileHandle = async (name: string, opts?: { create?: boolean }) => {
if (!files.has(name)) {
if (!opts?.create) throw new Error(`NotFoundError: ${name}`);
files.set(name, { content: new ArrayBuffer(0), writeCalls: [] });
}
const entry = files.get(name)!;
return {
getFile: async () => ({ size: entry.content.byteLength, arrayBuffer: async () => entry.content }),
createWritable: async (wOpts?: { keepExistingData?: boolean }) => {
const w: {
write: (arg: ArrayBuffer | { type: string; position: number; data: ArrayBuffer }) => Promise<void>;
close: () => Promise<void>;
} = {
write: async (arg) => {
const keepExisting = wOpts?.keepExistingData ?? false;
const isChunk = typeof arg !== 'object' || !('type' in (arg as object)) || (arg as { type?: string }).type === undefined
? { data: arg as ArrayBuffer, position: keepExisting ? entry.content.byteLength : 0 }
: { data: (arg as { data: ArrayBuffer }).data, position: (arg as { position: number }).position };
entry.writeCalls.push({ position: isChunk.position, data: isChunk.data, keepExistingData: keepExisting });
const merged = new Uint8Array(isChunk.position + isChunk.data.byteLength);
if (keepExisting || isChunk.position > 0) {
merged.set(new Uint8Array(entry.content.subarray ? entry.content : entry.content), 0);
}
merged.set(new Uint8Array(isChunk.data), isChunk.position);
entry.content = merged.buffer;
},
close: async () => { /* no-op */ },
};
return w;
},
};
};
const dir = {
getFileHandle,
entries: async function* () {
for (const [name] of files) yield [name];
},
removeEntry: async (name: string) => {
files.delete(name);
},
};
Object.defineProperty(globalThis, 'navigator', {
value: {
storage: {
getDirectory: async () => ({
getDirectoryHandle: async (_name: string, _opts?: unknown) => dir,
}),
},
},
configurable: true,
writable: true,
});
return { files, dir };
}
const enc = (s: string) => new Uint8Array(new TextEncoder().encode(s)).buffer;
describe('AriaEngine — OPFSBackend v2', () => {
beforeEach(() => {
createOPFSMock();
});
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(new TextDecoder().decode(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(new TextDecoder().decode(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(new TextDecoder().decode(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(new TextDecoder().decode(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(new TextDecoder().decode(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(new TextDecoder().decode(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(new TextDecoder().decode(await backend.read('a'))).toBe('AAA');
expect(new TextDecoder().decode(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();
});
});