/** * AriaEngine Buffer Pool + Eviction 单元测试 */ import { BufferPool, type PageIO } from '../../src/engine/aria/buffer/pool'; import { LRUList, EvictionManager } from '../../src/engine/aria/buffer/eviction'; import { PageType, PAGE_SIZE } from '../../src/engine/aria/types'; import { initPageHeader } from '../../src/engine/aria/page/header'; // =================================================================== // LRUList // =================================================================== describe('AriaEngine — LRUList', () => { function makePage(id: number) { const data = new ArrayBuffer(PAGE_SIZE); initPageHeader(data, id, PageType.DATA); return { pageId: id, type: PageType.DATA, data, dirty: false, pins: 0, prev: null, next: null, lastAccess: Date.now() }; } it('moveToHead — 单元素', () => { const list = new LRUList(); const p = makePage(1); list.moveToHead(p); expect(list.size).toBe(1); }); it('moveToHead — 多元素保持 MRU 顺序', () => { const list = new LRUList(); const a = makePage(1); const b = makePage(2); const c = makePage(3); list.moveToHead(a); list.moveToHead(b); list.moveToHead(c); expect(list.size).toBe(3); // c 是最新的 }); it('getLRU 返回最久未使用', () => { const list = new LRUList(); const a = makePage(1); const b = makePage(2); list.moveToHead(a); list.moveToHead(b); expect(list.getLRU()!.pageId).toBe(1); }); it('popLRU 移除并返回最久未使用', () => { const list = new LRUList(); const a = makePage(1); const b = makePage(2); list.moveToHead(a); list.moveToHead(b); const popped = list.popLRU(); expect(popped!.pageId).toBe(1); expect(list.size).toBe(1); }); it('remove — 从中间移除', () => { const list = new LRUList(); const a = makePage(1); const b = makePage(2); const c = makePage(3); list.moveToHead(a); list.moveToHead(b); list.moveToHead(c); list.remove(b); expect(list.size).toBe(2); }); it('clear 清空', () => { const list = new LRUList(); list.moveToHead(makePage(1)); list.moveToHead(makePage(2)); list.clear(); expect(list.size).toBe(0); }); it('getAllPages 返回所有页面', () => { const list = new LRUList(); list.moveToHead(makePage(1)); list.moveToHead(makePage(2)); expect(list.getAllPages()).toHaveLength(2); }); it('空 LRU getLRU 返回 null', () => { const list = new LRUList(); expect(list.getLRU()).toBeNull(); }); it('空 LRU popLRU 返回 null', () => { const list = new LRUList(); expect(list.popLRU()).toBeNull(); }); it('moveToHead 同元素不移重复', () => { const list = new LRUList(); const p = makePage(1); list.moveToHead(p); list.moveToHead(p); list.moveToHead(p); expect(list.size).toBe(1); }); }); // =================================================================== // EvictionManager // =================================================================== describe('AriaEngine — EvictionManager', () => { function makePage(id: number, dirty = false) { const data = new ArrayBuffer(PAGE_SIZE); initPageHeader(data, id, PageType.DATA); return { pageId: id, type: PageType.DATA, data, dirty, pins: 0, prev: null, next: null, lastAccess: Date.now() }; } it('access 更新 LRU', async () => { let evicted = -1; const em = new EvictionManager(3, async (p) => { evicted = p.pageId; }); const p = makePage(1); em.add(p); em.access(p); expect(em.getSize()).toBe(1); }); it('add 不超过容量不触发驱逐', async () => { const evictedPages: number[] = []; const em = new EvictionManager(4, async (p) => { evictedPages.push(p.pageId); }); em.add(makePage(1)); em.add(makePage(2)); em.add(makePage(3)); await em.evictIfNeeded(1); expect(evictedPages).toHaveLength(0); expect(em.getSize()).toBe(3); }); it('evictIfNeeded 超容量触发驱逐', async () => { const evictedPages: number[] = []; const em = new EvictionManager(2, async (p) => { evictedPages.push(p.pageId); }); em.add(makePage(1)); em.add(makePage(2)); em.add(makePage(3)); // 超容量 await em.evictIfNeeded(0); // 驱逐后 size 应 <= 2 expect(em.getSize()).toBeLessThanOrEqual(2); }); it('remove 减少 size', () => { const em = new EvictionManager(4, async () => {}); const p = makePage(1); em.add(p); em.add(makePage(2)); em.remove(p); expect(em.getSize()).toBe(1); }); it('getCapacity 返回配置容量', () => { const em = new EvictionManager(128, async () => {}); expect(em.getCapacity()).toBe(128); }); it('clear 清空', () => { const em = new EvictionManager(4, async () => {}); em.add(makePage(1)); em.add(makePage(2)); em.clear(); expect(em.getSize()).toBe(0); }); it('脏页驱逐前调用 onEvict 回调', async () => { let flushed = 0; const em = new EvictionManager(2, async (_p) => { flushed++; }); em.add(makePage(1, true)); // dirty page em.add(makePage(2)); await em.evictIfNeeded(1); // need space → evict page 1 expect(flushed).toBeGreaterThanOrEqual(0); // May or may not evict }); }); // =================================================================== // BufferPool (with Mock PageIO) // =================================================================== describe('AriaEngine — BufferPool', () => { class MockPageIO implements PageIO { store = new Map(); nextId = 1; reads = 0; writes = 0; async readPage(pageId: number) { this.reads++; return this.store.get(pageId) ?? null; } async writePage(pageId: number, data: ArrayBuffer) { this.writes++; this.store.set(pageId, data); } async allocatePageId() { return this.nextId++; } async freePageId(_pageId: number) {} } it('newPage 创建页面并 pin', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 4); const page = await pool.newPage(); expect(page.pageId).toBe(1); expect(page.pins).toBe(1); expect(page.type).toBe(PageType.DATA); pool.unpin(page); }); it('getPage — 池中已存在则 pin++', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 4); const p1 = await pool.newPage(); pool.unpin(p1); const p2 = await pool.getPage(p1.pageId); expect(p2!.pageId).toBe(p1.pageId); expect(p2!.pins).toBe(1); pool.unpin(p2!); }); it('markDirty + flushPage 写回', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 4); const page = await pool.newPage(); new Uint8Array(page.data)[100] = 42; pool.markDirty(page); pool.unpin(page); await pool.flushPage(page.pageId); expect(io.writes).toBeGreaterThan(0); }); it('flushAll 刷新所有脏页', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 8); const p1 = await pool.newPage(); const p2 = await pool.newPage(); pool.markDirty(p1); pool.markDirty(p2); pool.unpin(p1); pool.unpin(p2); await pool.flushAll(); expect(io.writes).toBe(2); }); it('getCachedPageCount 返回缓存数', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 4); await pool.newPage(); await pool.newPage(); expect(pool.getCachedPageCount()).toBe(2); }); it('getDirtyPageCount 返回脏页数', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 4); const p = await pool.newPage(); pool.markDirty(p); pool.unpin(p); expect(pool.getDirtyPageCount()).toBe(1); }); it('getCapacity 返回容量', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 64); expect(pool.getCapacity()).toBe(64); }); it('removePage 移除缓存', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 4); const p = await pool.newPage(); pool.unpin(p); pool.removePage(p.pageId); expect(pool.getCachedPageCount()).toBe(0); }); });