import { setTimeout } from 'node:timers/promises'; import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { mutex } from 'moroutine'; describe('Mutex', () => { it('lock and unlock', async () => { const m = mutex(); const guard = await m.lock(); m.unlock(); }); it('lock returns a MutexGuard', async () => { const m = mutex(); const guard = await m.lock(); assert.equal(typeof guard[Symbol.dispose], 'function'); m.unlock(); }); it('MutexGuard dispose calls unlock', async () => { const m = mutex(); const guard = await m.lock(); guard[Symbol.dispose](); // Should be able to lock again immediately const guard2 = await m.lock(); m.unlock(); }); it('serializes concurrent access', async () => { const m = mutex(); const order: number[] = []; const task = async (id: number) => { const guard = await m.lock(); order.push(id); await setTimeout(10); order.push(id); m.unlock(); }; await Promise.all([task(1), task(2)]); // Each task should push its id twice in a row (not interleaved) assert.ok(order[0] === order[1] && order[2] === order[3], `Expected non-interleaved order, got: ${order}`); }); it('self-allocates', () => { const m = mutex(); assert.ok(m); }); it('exposes byteSize of 4', () => { assert.equal(mutex.byteSize, 4); }); it('tryLock returns a guard when unlocked', () => { const m = mutex(); const guard = m.tryLock(); assert.ok(guard); assert.equal(typeof guard[Symbol.dispose], 'function'); guard[Symbol.dispose](); // Should be able to lock again after dispose const guard2 = m.tryLock(); assert.ok(guard2); m.unlock(); }); it('tryLock returns null when held', async () => { const m = mutex(); await m.lock(); const guard = m.tryLock(); assert.equal(guard, null); m.unlock(); }); it('tryLock succeeds again after unlock', () => { const m = mutex(); const g1 = m.tryLock(); assert.ok(g1); m.unlock(); const g2 = m.tryLock(); assert.ok(g2); m.unlock(); }); });