Files
Firelink/scripts/engine-staging-lock.node-test.js
T
NimBold 1d629873b5 fix(deps): refresh packages and harden engine staging
- update npm, Cargo, Deno, and FFmpeg lockfiles
- refresh verified cross-platform engine payload metadata
- serialize staging, verification, and Tauri packaging across concurrent runs
- recover interrupted lock records and cover crash and ownership races
2026-08-30 10:22:38 +03:30

187 lines
6.4 KiB
JavaScript

import assert from 'node:assert/strict';
import { once } from 'node:events';
import { spawn } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { pathToFileURL } from 'node:url';
import {
acquireExclusiveFileLock,
assertExclusiveFileLockHeld,
readExclusiveFileLockOwner,
} from './engine-staging-lock.js';
const childOutputStates = new WeakMap();
function temporaryLockPath() {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'firelink-stage-lock-'));
return path.join(directory, 'engine-dist.lock');
}
function waitForOutput(child, expected) {
const state = childOutputStates.get(child);
assert.ok(state, 'child output tracking must be installed before waiting');
if (state.output.includes(expected)) return Promise.resolve(state.output);
if (state.error) return Promise.reject(state.error);
if (state.exit) {
return Promise.reject(new Error(
`lock child exited before '${expected}' (code=${state.exit.code}, signal=${state.exit.signal})`,
));
}
return new Promise((resolve, reject) => {
state.waiters.push({ expected, resolve, reject });
});
}
function trackChildOutput(child) {
const state = { error: null, exit: null, output: '', waiters: [] };
childOutputStates.set(child, state);
child.stdout.setEncoding('utf8');
child.stdout.on('data', chunk => {
state.output += chunk;
for (const waiter of state.waiters.splice(0)) {
if (state.output.includes(waiter.expected)) {
waiter.resolve(state.output);
} else {
state.waiters.push(waiter);
}
}
});
child.once('error', error => {
state.error = error;
for (const waiter of state.waiters.splice(0)) waiter.reject(error);
});
child.once('exit', (code, signal) => {
state.exit = { code, signal };
const error = new Error(`lock child exited before a requested output (code=${code}, signal=${signal})`);
for (const waiter of state.waiters.splice(0)) waiter.reject(error);
});
}
function spawnLockChild(lockPath) {
const moduleUrl = pathToFileURL(path.resolve('scripts/engine-staging-lock.js')).href;
const source = `
import { acquireExclusiveFileLock } from ${JSON.stringify(moduleUrl)};
process.stdout.write('started\\n');
const release = await acquireExclusiveFileLock(process.argv[1], { timeoutMs: 5_000 });
process.stdout.write('acquired\\n');
process.stdin.once('data', () => {
release();
process.exit(0);
});
process.stdin.resume();
`;
const child = spawn(process.execPath, ['--input-type=module', '-e', source, lockPath], {
stdio: ['pipe', 'pipe', 'pipe'],
});
trackChildOutput(child);
return child;
}
test('exclusive staging lock waits for a live owner and releases idempotently', async () => {
const lockPath = temporaryLockPath();
const releaseFirst = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
const secondAcquisition = acquireExclusiveFileLock(lockPath, {
timeoutMs: 1_000,
});
assert.equal(fs.existsSync(lockPath), true);
releaseFirst();
releaseFirst();
const releaseSecond = await secondAcquisition;
assert.equal(fs.existsSync(lockPath), true);
releaseSecond();
assert.equal(fs.existsSync(lockPath), false);
});
test('exclusive staging lock recovers empty and malformed legacy records', async () => {
for (const contents of ['', '{not-json']) {
const lockPath = temporaryLockPath();
fs.writeFileSync(lockPath, contents);
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
const owner = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
assert.equal(owner.pid, process.pid);
release();
assert.equal(fs.existsSync(lockPath), false);
}
});
test('exclusive staging lock serializes separate processes', async () => {
const lockPath = temporaryLockPath();
const first = spawnLockChild(lockPath);
let second;
try {
await waitForOutput(first, 'acquired\n');
second = spawnLockChild(lockPath);
await waitForOutput(second, 'started\n');
const owner = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
assert.equal(owner.pid, first.pid);
first.stdin.write('\n');
await once(first, 'exit');
await waitForOutput(second, 'acquired\n');
second.stdin.write('\n');
await once(second, 'exit');
} finally {
for (const child of [first, second]) {
if (child && child.exitCode === null && child.signalCode === null) child.kill();
}
}
});
test('exclusive staging lock recovers after an owner is force-killed', async () => {
const lockPath = temporaryLockPath();
const owner = spawnLockChild(lockPath);
try {
await waitForOutput(owner, 'acquired\n');
assert.equal(owner.kill('SIGKILL'), true);
await once(owner, 'exit');
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
const replacement = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
assert.equal(replacement.pid, process.pid);
release();
assert.equal(fs.existsSync(lockPath), false);
} finally {
if (owner.exitCode === null && owner.signalCode === null) owner.kill();
}
});
test('exclusive staging lock recovers a lock owned by a dead process', async () => {
const lockPath = temporaryLockPath();
fs.writeFileSync(lockPath, JSON.stringify({
pid: 999_999_999,
token: 'dead-owner',
}));
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
const owner = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
assert.equal(owner.pid, process.pid);
assert.notEqual(owner.token, 'dead-owner');
release();
assert.equal(fs.existsSync(lockPath), false);
});
test('staging lock release fails closed when ownership changes', async () => {
const lockPath = temporaryLockPath();
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
fs.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, token: 'other-owner' }));
assert.throws(() => release(), /ownership changed/);
fs.unlinkSync(lockPath);
});
test('inherited staging lock validation rejects a released lease', async () => {
const lockPath = temporaryLockPath();
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
const owner = readExclusiveFileLockOwner(lockPath);
assert.doesNotThrow(() => assertExclusiveFileLockHeld(lockPath, owner));
release();
assert.throws(() => assertExclusiveFileLockHeld(lockPath, owner), /owner record is unavailable/);
});