mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-17 05:35:36 +02:00
88 lines
3.7 KiB
JavaScript
88 lines
3.7 KiB
JavaScript
'use strict';
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const { spawn } = require('child_process');
|
|
const worker = path.join(__dirname, 'helpers', 'session-alias-worker.js');
|
|
|
|
async function waitFor(file, children) {
|
|
const deadline = Date.now() + 10000;
|
|
while (!fs.existsSync(file)) {
|
|
for (const child of children) {
|
|
if (child.exitCode !== null || child.signalCode !== null) throw new Error('Child exited before barrier: ' + file);
|
|
}
|
|
if (Date.now() > deadline) throw new Error('Barrier timed out: ' + file);
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
}
|
|
}
|
|
function start(root, role, action) {
|
|
const child = spawn(process.execPath, [worker, root, role, action], {
|
|
env: { ...process.env, HOME: root, USERPROFILE: root, ECC_AGENT_DATA_HOME: path.join(root, '.claude') },
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
});
|
|
let stdout = '';
|
|
let stderr = '';
|
|
child.stdout.on('data', data => { stdout += data; });
|
|
child.stderr.on('data', data => { stderr += data; });
|
|
child.done = new Promise((resolve, reject) => {
|
|
child.on('error', reject);
|
|
child.on('close', (code, signal) => {
|
|
if (code !== 0) reject(new Error('Worker failed: ' + code + '/' + signal + ' ' + stderr));
|
|
else {
|
|
try { resolve(JSON.parse(stdout)); } catch (error) { reject(error); }
|
|
}
|
|
});
|
|
});
|
|
child.done.catch(() => {});
|
|
return child;
|
|
}
|
|
async function race(action) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-alias-race-'));
|
|
const dataDir = path.join(root, '.claude');
|
|
fs.mkdirSync(dataDir);
|
|
const aliasesPath = path.join(dataDir, 'session-aliases.json');
|
|
fs.writeFileSync(aliasesPath, JSON.stringify({
|
|
version: '1.0',
|
|
aliases: { existing: { sessionPath: '/existing', createdAt: '2026-01-01', title: 'Original' } },
|
|
metadata: {},
|
|
}));
|
|
const children = [];
|
|
const timer = setTimeout(() => children.forEach(child => child.kill('SIGKILL')), 20000);
|
|
try {
|
|
const alpha = start(root, 'alpha', action);
|
|
children.push(alpha);
|
|
await waitFor(path.join(root, 'alpha.read'), children);
|
|
const beta = start(root, 'beta', action);
|
|
children.push(beta);
|
|
await waitFor(path.join(root, 'beta.attempt'), children);
|
|
fs.writeFileSync(path.join(root, 'alpha.release'), '');
|
|
assert.strictEqual((await alpha.done).success, true);
|
|
fs.writeFileSync(path.join(root, 'beta.release'), '');
|
|
assert.strictEqual((await beta.done).success, true);
|
|
const data = JSON.parse(fs.readFileSync(aliasesPath, 'utf8'));
|
|
assert.ok(data.aliases.alpha, action + ' must preserve the preceding writer');
|
|
if (action === 'set') assert.ok(data.aliases.beta);
|
|
if (['delete', 'cleanup', 'rename'].includes(action)) assert.ok(!data.aliases.existing);
|
|
if (action === 'rename') assert.ok(data.aliases.renamed);
|
|
if (action === 'title') assert.strictEqual(data.aliases.existing.title, 'Updated');
|
|
assert.strictEqual(data.metadata.totalCount, Object.keys(data.aliases).length);
|
|
assert.deepStrictEqual(fs.readdirSync(dataDir), ['session-aliases.json']);
|
|
} finally {
|
|
clearTimeout(timer);
|
|
for (const child of children) {
|
|
if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL');
|
|
}
|
|
await Promise.allSettled(children.map(child => child.done));
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
}
|
|
(async () => {
|
|
let failed = 0;
|
|
for (const action of ['set', 'delete', 'rename', 'title', 'cleanup']) {
|
|
try { await race(action); console.log('PASS concurrent ' + action); }
|
|
catch (error) { failed++; console.error('FAIL concurrent ' + action + ': ' + error.message); }
|
|
}
|
|
process.exitCode = failed ? 1 : 0;
|
|
})();
|