test: listen for both singleton exits before either can happen (#54593)

* test: listen for both singleton exits before either can happen

'prevents the second launch of app' only started listening for the first
instance's 'exit' after the second instance's exit had been handled. The
two copies quit within milliseconds of each other, so when the spec
process's event loop is busy and sees both exits in the same pass, the
first copy's 'exit' is emitted with no listener, and the test then waits
for it until the 120s test timeout.

Register both exit listeners as each process is spawned, as the other
requestSingleInstanceLock tests already do, kill both copies on the way
out, and fail within 30s naming which copy stalled instead of waiting
out the test timeout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XT1kpjmqsAdPLBVZMV1pVX

* test: use waitUntil for the singleton launch waits

Record each copy's exit code from an 'exit' listener attached at spawn
and wait on it with the existing waitUntil helper instead of a local
within() helper. Only kill a copy that is still running.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XT1kpjmqsAdPLBVZMV1pVX

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
claude[bot]andClaude Opus 5.5 authored and GitHub committed 2026-09-30 14:06:21 -04:00
1 parent bc8e748ec8
commit 338a350f7e
1 file changed
+27 -5
+27 -5
View File
@@ -351,14 +351,36 @@ describe('app module', () => {
it('prevents the second launch of app', async function () {
this.timeout(120000);
const appPath = path.join(fixturesPath, 'api', 'singleton-data');
// The two copies quit within milliseconds of each other, so record each
// exit as soon as the copy is spawned: an 'exit' emitted before a
// listener is attached is lost, and the test would wait for it until it
// timed out.
const first = cp.spawn(process.execPath, [appPath]);
await once(first.stdout, 'data');
let firstCode: number | null = null;
first.on('exit', (code) => {
firstCode = code;
});
defer(() => {
if (first.exitCode === null && first.signalCode === null) first.kill();
});
let firstOutput = '';
first.stdout.on('data', (data) => {
firstOutput += data;
});
await waitUntil(() => firstOutput.length > 0, { timeout: 30000 });
// Start second app when received output.
const second = cp.spawn(process.execPath, [appPath]);
const [code2] = await once(second, 'exit');
expect(code2).to.equal(1);
const [code1] = await once(first, 'exit');
expect(code1).to.equal(0);
let secondCode: number | null = null;
second.on('exit', (code) => {
secondCode = code;
});
defer(() => {
if (second.exitCode === null && second.signalCode === null) second.kill();
});
await waitUntil(() => secondCode !== null, { timeout: 30000 });
expect(secondCode).to.equal(1);
await waitUntil(() => firstCode !== null, { timeout: 30000 });
expect(firstCode).to.equal(0);
});
it('returns true when setting non-existent user data folder', async function () {