/** * Tests for live/source-lock.mjs — the per-source-file mutex guarding the * accept/publish critical sections. * Run with: node --test tests/live-source-lock.test.mjs */ import { describe, it, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { existsSync, mkdtempSync, readFileSync, rmSync, utimesSync, writeFileSync, mkdirSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { tmpdir } from 'node:os'; import { sourceLockPath, withSourceLockSync } from '../skill/scripts/live/source-lock.mjs'; const TARGET = 'src/page.html'; describe('live source-lock', () => { let tmp; beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), 'impeccable-source-lock-')); mkdirSync(join(tmp, 'src'), { recursive: true }); writeFileSync(join(tmp, TARGET), '
original
\n'); }); afterEach(() => rmSync(tmp, { recursive: true, force: true })); const writeLock = (body) => { const lockPath = sourceLockPath(TARGET, tmp); mkdirSync(dirname(lockPath), { recursive: true }); writeFileSync(lockPath, JSON.stringify(body) + '\n'); return lockPath; }; it('runs the critical section and releases the lock', () => { const lockPath = sourceLockPath(TARGET, tmp); const result = withSourceLockSync(TARGET, 'accept:a', () => { assert.equal(existsSync(lockPath), true, 'lock must exist while held'); return 'done'; }, { cwd: tmp }); assert.equal(result, 'done'); assert.equal(existsSync(lockPath), false, 'lock must be released'); }); it('throws SOURCE_LOCKED when a live owner holds the lock', () => { // process.pid is this very process, so the recorded owner is alive. writeLock({ owner: 'publish:x', token: 'other', pid: process.pid, at: Date.now() }); assert.throws( () => withSourceLockSync(TARGET, 'accept:a', () => 'should not run', { cwd: tmp }), (err) => err.code === 'SOURCE_LOCKED', ); }); it('does not sweep a live owner’s lock no matter how old it is', () => { // Age alone must not make a lock stale: a holder suspended mid-write would // otherwise have a second writer admitted to the same source file. const lockPath = writeLock({ owner: 'publish:x', token: 'other', pid: process.pid, at: 0 }); const ancient = new Date(Date.now() - 10 * 60_000); utimesSync(lockPath, ancient, ancient); assert.throws( () => withSourceLockSync(TARGET, 'accept:a', () => 'should not run', { cwd: tmp }), (err) => err.code === 'SOURCE_LOCKED', 'an old but live lock was stolen', ); }); it('reclaims a lock whose owner process is gone, without waiting out a timeout', () => { // PID 2^22 is above the platform maximum, so it can never be running. writeLock({ owner: 'publish:crashed', token: 'other', pid: 4194304, at: Date.now() }); const result = withSourceLockSync(TARGET, 'accept:a', () => 'acquired', { cwd: tmp }); assert.equal(result, 'acquired', 'a crashed holder must not block the next writer'); }); it('leaves a replacement lock alone when its own was swept', () => { // Simulates: our lock got reclaimed and another writer now owns the file. // Releasing must not unlink the replacement and admit a third writer. const lockPath = sourceLockPath(TARGET, tmp); withSourceLockSync(TARGET, 'accept:a', () => { writeFileSync(lockPath, JSON.stringify({ owner: 'publish:other', token: 'a-different-token', pid: process.pid, at: Date.now(), }) + '\n'); }, { cwd: tmp }); assert.equal(existsSync(lockPath), true, 'another owner’s lock must survive our release'); assert.match(readFileSync(lockPath, 'utf-8'), /a-different-token/); }); it('retires an unreadable lock only once it is older than the fallback window', () => { const lockPath = writeLock(''); assert.throws( () => withSourceLockSync(TARGET, 'accept:a', () => 'x', { cwd: tmp }), (err) => err.code === 'SOURCE_LOCKED', 'a fresh unreadable lock is an in-flight acquisition, not garbage', ); const ancient = new Date(Date.now() - 120_000); utimesSync(lockPath, ancient, ancient); assert.equal( withSourceLockSync(TARGET, 'accept:a', () => 'acquired', { cwd: tmp }), 'acquired', 'a stale unreadable lock must be retired', ); }); it('releases the lock even when the critical section throws', () => { const lockPath = sourceLockPath(TARGET, tmp); assert.throws(() => withSourceLockSync(TARGET, 'accept:a', () => { throw new Error('boom'); }, { cwd: tmp }), /boom/); assert.equal(existsSync(lockPath), false, 'a thrown critical section must not leak the lock'); }); });