import { describe, expect, test } from "bun:test"; import { AsyncLock, AsyncQueue } from "./async"; describe("AsyncLock", () => { test("serializes critical sections in FIFO order", async () => { const lock = new AsyncLock(); const order: number[] = []; const run = async (id: number): Promise => { const release = await lock.acquire(); order.push(id); // Yield a macrotask so overlapping sections would interleave if unlocked. await new Promise((resolve) => setTimeout(resolve, 1)); order.push(-id); release(); }; await Promise.all([run(1), run(2), run(3)]); // Each section runs to completion (id then -id) before the next starts, and // waiters resume in the order they queued. expect(order).toEqual([1, -1, 2, -2, 3, -3]); }); test("the release closure is idempotent — a double-call cannot free two waiters", async () => { const lock = new AsyncLock(); const first = await lock.acquire(); let bAcquired = false; let cAcquired = false; void lock.acquire().then(() => { bAcquired = true; }); void lock.acquire().then(() => { cAcquired = true; }); first(); first(); // second call must be a no-op await new Promise((resolve) => setTimeout(resolve, 1)); // Only one waiter may hold the lock; the double-release must not admit both. expect(bAcquired).toBe(true); expect(cAcquired).toBe(false); }); test("second acquire blocks until the first releases", async () => { const lock = new AsyncLock(); const first = await lock.acquire(); let secondAcquired = false; const second = lock.acquire().then((release) => { secondAcquired = true; return release; }); await new Promise((resolve) => setTimeout(resolve, 1)); expect(secondAcquired).toBe(false); first(); (await second)(); expect(secondAcquired).toBe(true); }); }); describe("AsyncQueue", () => { test("returns a buffered item immediately", async () => { const queue = new AsyncQueue(4); queue.push(7); expect(await queue.shift()).toBe(7); }); test("a pending shift wakes on the next push (no lost wakeup)", async () => { const queue = new AsyncQueue(4); const pending = queue.shift(); queue.push("hello"); expect(await pending).toBe("hello"); }); test("push delivers to a waiter FIFO and bypasses the buffer", async () => { const queue = new AsyncQueue(4); const a = queue.shift(); const b = queue.shift(); queue.push(1); queue.push(2); expect(await a).toBe(1); expect(await b).toBe(2); expect(queue.size).toBe(0); }); test("evicts oldest when over capacity", () => { const queue = new AsyncQueue(2); queue.push(1); queue.push(2); queue.push(3); expect(queue.evictions).toBe(1); expect(queue.size).toBe(2); }); test("close rejects pending shifts and drops later pushes", async () => { const queue = new AsyncQueue(4); const pending = queue.shift(); queue.close(); await expect(pending).rejects.toThrow("Queue closed"); expect(queue.push(1)).toBe(false); }); test("aborting a pending shift rejects it and removes the resolver", async () => { const queue = new AsyncQueue(4); const controller = new AbortController(); const pending = queue.shift(controller.signal); controller.abort(); await expect(pending).rejects.toThrow("Queue aborted"); // The aborted waiter must not steal the next pushed item. queue.push(42); expect(await queue.shift()).toBe(42); }); test("shift on a closed drained queue rejects instead of hanging", async () => { const queue = new AsyncQueue(4); queue.close(); await expect(queue.shift()).rejects.toThrow("Queue closed"); }); test("shift with an already-aborted signal rejects instead of hanging", async () => { const queue = new AsyncQueue(4); const controller = new AbortController(); controller.abort(); await expect(queue.shift(controller.signal)).rejects.toThrow("Queue aborted"); // No resolver leaked: a normal push/shift still works. queue.push(5); expect(await queue.shift()).toBe(5); }); });