From 1eeffd7c09412aaeb23c9cb4f4e5e6699fccf54d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 00:25:42 +0000 Subject: [PATCH] perf(test): remove sleeps from session store lock suite --- src/config/sessions/store.lock.test.ts | 104 ++++++++++++++++--------- 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/src/config/sessions/store.lock.test.ts b/src/config/sessions/store.lock.test.ts index 65e366a590..826b3715e3 100644 --- a/src/config/sessions/store.lock.test.ts +++ b/src/config/sessions/store.lock.test.ts @@ -1,9 +1,8 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import type { SessionEntry } from "./types.js"; -import { sleep } from "../../utils.js"; import { clearSessionStoreCacheForTest, getSessionStoreLockQueueSizeForTest, @@ -18,11 +17,34 @@ describe("session store lock (Promise chain mutex)", () => { let caseId = 0; let tmpDirs: string[] = []; + function createDeferred() { + let resolve!: (value: T) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + } + + async function waitForFile(filePath: string, maxTicks = 50): Promise { + for (let tick = 0; tick < maxTicks; tick += 1) { + try { + await fs.access(filePath); + return; + } catch { + // Works under both real + fake timers (setImmediate is faked). + await new Promise((resolve) => process.nextTick(resolve)); + } + } + throw new Error(`timed out waiting for file: ${filePath}`); + } + async function makeTmpStore( initial: Record = {}, ): Promise<{ dir: string; storePath: string }> { const dir = path.join(fixtureRoot, `case-${caseId++}`); - await fs.mkdir(dir, { recursive: true }); + await fs.mkdir(dir); tmpDirs.push(dir); const storePath = path.join(dir, "sessions.json"); if (Object.keys(initial).length > 0) { @@ -280,30 +302,44 @@ describe("session store lock (Promise chain mutex)", () => { }); it("times out queued operations strictly and does not run them later", async () => { - const { storePath } = await makeTmpStore({ - x: { sessionId: "x", updatedAt: 100 }, - }); - let timedOutRan = false; + vi.useFakeTimers(); + try { + const { storePath } = await makeTmpStore({ + x: { sessionId: "x", updatedAt: 100 }, + }); + let timedOutRan = false; - const lockHolder = withSessionStoreLockForTest( - storePath, - async () => { - await sleep(15); - }, - { timeoutMs: 1_000 }, - ); - const timedOut = withSessionStoreLockForTest( - storePath, - async () => { - timedOutRan = true; - }, - { timeoutMs: 5 }, - ); + const lockPath = `${storePath}.lock`; + const releaseLock = createDeferred(); + const lockHolder = withSessionStoreLockForTest( + storePath, + async () => { + await releaseLock.promise; + }, + { timeoutMs: 1_000 }, + ); + await waitForFile(lockPath); + const timedOut = withSessionStoreLockForTest( + storePath, + async () => { + timedOutRan = true; + }, + { timeoutMs: 5 }, + ); - await expect(timedOut).rejects.toThrow("timeout waiting for session store lock"); - await lockHolder; - await sleep(2); - expect(timedOutRan).toBe(false); + // Attach rejection handler before advancing fake timers to avoid unhandled rejections. + const timedOutExpectation = expect(timedOut).rejects.toThrow( + "timeout waiting for session store lock", + ); + await vi.advanceTimersByTimeAsync(5); + await timedOutExpectation; + releaseLock.resolve(); + await lockHolder; + await vi.runOnlyPendingTimersAsync(); + expect(timedOutRan).toBe(false); + } finally { + vi.useRealTimers(); + } }); it("creates and removes lock file while operation runs", async () => { @@ -312,23 +348,15 @@ describe("session store lock (Promise chain mutex)", () => { [key]: { sessionId: "s1", updatedAt: 100 }, }); + const lockPath = `${storePath}.lock`; + const allowWrite = createDeferred(); const write = updateSessionStore(storePath, async (store) => { - await sleep(8); + await allowWrite.promise; store[key] = { ...store[key], modelOverride: "v" } as unknown as SessionEntry; }); - const lockPath = `${storePath}.lock`; - let lockSeen = false; - for (let i = 0; i < 20; i += 1) { - try { - await fs.access(lockPath); - lockSeen = true; - break; - } catch { - await sleep(1); - } - } - expect(lockSeen).toBe(true); + await waitForFile(lockPath); + allowWrite.resolve(); await write; const files = await fs.readdir(dir);