/** * 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('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('remove 从尾部移除', () => { const list = new LRUList(); const a = makePage(1); list.moveToHead(a); list.remove(a); expect(list.size).toBe(0); }); it('空 LRU getLRU 返回 null', () => { const list = new LRUList(); expect(list.getLRU()).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 as unknown as { lru: { size: number } }).lru.size).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 as unknown as { lru: { size: number } }).lru.size).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 as unknown as { lru: { size: number } }).lru.size).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 as unknown as { lru: { size: number } }).lru.size).toBe(1); }); it('clear 清空', () => { const em = new EvictionManager(4, async () => {}); em.add(makePage(1)); em.add(makePage(2)); em.clear(); expect((em as unknown as { lru: { size: number } }).lru.size).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.newPages(1))[0]; 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.newPages(1))[0]; 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.newPages(1))[0]; 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.newPages(1))[0]; const p2 = (await pool.newPages(1))[0]; pool.markDirty(p1); pool.markDirty(p2); pool.unpin(p1); pool.unpin(p2); await pool.flushAll(); expect(io.writes).toBe(2); }); it('newPages 批量创建页面并 pin', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 8); const pages = await pool.newPages(3); expect(pages.map((p) => p.pageId)).toEqual([1, 2, 3]); expect(pages.every((p) => p.pins === 1)).toBe(true); expect(pages.every((p) => p.type === PageType.DATA)).toBe(true); for (const p of pages) pool.unpin(p); }); it('removePage 移除缓存', async () => { const io = new MockPageIO(); const pool = new BufferPool(io, 4); const p = (await pool.newPages(1))[0]; pool.unpin(p); pool.removePage(p.pageId); expect((pool as unknown as { pages: Map }).pages.size).toBe(0); }); });