diff --git a/packages/agent-bundle/tests/cli.test.ts b/packages/agent-bundle/tests/cli.test.ts index 315c58697..39b109dcf 100644 --- a/packages/agent-bundle/tests/cli.test.ts +++ b/packages/agent-bundle/tests/cli.test.ts @@ -7,7 +7,7 @@ import { promisify } from 'node:util'; import { expect, it } from '@rstest/core'; import { runCli as runSourceCli, type CliDependencies } from '../src/cli.ts'; -import { cachedNpmInstallArguments } from './support/shared-pack.ts'; +import { cachedNpmInstallArguments, packOutputFromJson } from './support/shared-pack.ts'; import { timeScale } from './support/time-scale.ts'; const execFile = promisify(executeFile); @@ -130,7 +130,7 @@ const createPackedConsumer = async (): Promise<{ readonly cli: string; readonly const { stdout } = await execFile( 'npm', ['pack', '--json', '--pack-destination', root], { cwd: packageRoot }, ); - const [packed] = JSON.parse(stdout) as Array<{ readonly filename: string }>; + const packed = packOutputFromJson(stdout); await writeFile(join(root, 'package.json'), '{"type":"module"}\n'); await execFile( 'npm', ['install', ...cachedNpmInstallArguments, join(root, packed.filename)], diff --git a/packages/agent-bundle/tests/doctor.test.ts b/packages/agent-bundle/tests/doctor.test.ts index 4dd9e2427..9d6f10e35 100644 --- a/packages/agent-bundle/tests/doctor.test.ts +++ b/packages/agent-bundle/tests/doctor.test.ts @@ -1,4 +1,5 @@ import { cp, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import { spawn, type ChildProcess } from 'node:child_process'; import { createServer, type Server } from 'node:net'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; @@ -558,16 +559,16 @@ const close = (server: Server): Promise => new Promise((resolvePromise, re }); }); -const findDeadPid = (): number => { - for (let pid = 4_194_000; pid > 0; pid -= 1) { - try { - process.kill(pid, 0); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ESRCH') return pid; - } +const findDeadPid = (): Promise => new Promise((resolvePromise, reject) => { + const child: ChildProcess = spawn(process.execPath, ['--input-type=module', '-e', ''], { stdio: 'ignore' }); + const { pid } = child; + if (pid === undefined) { + reject(new Error('Unable to spawn a child process for a dead pid fixture.')); + return; } - throw new Error('No dead pid found.'); -}; + child.once('error', reject); + child.once('exit', () => { resolvePromise(pid); }); +}); it('scans live sockets and a lock with a live sibling without warnings', async () => { const fixture = await temporaryDoctor(); @@ -595,11 +596,12 @@ it('reports stale sockets and stale locks as warnings', async () => { const fixture = await temporaryDoctor(); const staleSocket = join(fixture.endpointDirectory, 'event-stale.sock'); const staleLock = join(fixture.endpointDirectory, 'event-claimed.sock.lock'); - const staleServer = await listen(staleSocket); - await close(staleServer); - await writeFile(staleSocket, ''); - await writeFile(staleLock, ''); try { + await mkdir(fixture.endpointDirectory, { recursive: true }); + // Match event-ipc stale-endpoint fixtures: a regular file at the socket + // path refuses connections and is classified as stale by Doctor. + await writeFile(staleSocket, 'stale socket'); + await writeFile(staleLock, ''); const report = await runDoctor({ endpointDirectory: fixture.endpointDirectory, home: fixture.home, @@ -610,6 +612,7 @@ it('reports stale sockets and stale locks as warnings', async () => { expect.objectContaining({ path: staleLock, state: 'stale-lock' }), ])); expect(report.endpoints.summary.staleLocks).toBe(1); + expect(report.endpoints.summary.staleSockets).toBe(1); expect(report.diagnostics).toEqual(expect.arrayContaining([ expect.objectContaining({ code: 'AB7314', @@ -630,7 +633,7 @@ it('reports stale sockets and stale locks as warnings', async () => { it('reports a lock with a provably dead owner as a stale lock warning', async () => { const fixture = await temporaryDoctor(); const staleLock = join(fixture.endpointDirectory, 'event-dead-claim.sock.lock'); - const deadPid = findDeadPid(); + const deadPid = await findDeadPid(); try { await mkdir(fixture.endpointDirectory, { recursive: true }); await writeFile(staleLock, `${JSON.stringify({ pid: deadPid })}\n`); diff --git a/packages/agent-bundle/tests/support/packed-native-smoke.ts b/packages/agent-bundle/tests/support/packed-native-smoke.ts index aeeebbe5d..c62f10484 100644 --- a/packages/agent-bundle/tests/support/packed-native-smoke.ts +++ b/packages/agent-bundle/tests/support/packed-native-smoke.ts @@ -17,7 +17,7 @@ import { promisify } from 'node:util'; // The native smoke installs the production closure a real consumer would get, // so it stays on npm's default metadata staleness checks. -import { npmInstallArguments, sharedPackedTarball } from './shared-pack.ts'; +import { npmInstallArguments, packOutputFromJson, sharedPackedTarball } from './shared-pack.ts'; import { deepFreeze } from '../../src/core/freeze.ts'; @@ -356,15 +356,15 @@ export const runPackedNativeSmoke = async (options: { cwd: packageRoot, environment, }); - const listing = JSON.parse(packed.stdout) as readonly { readonly filename: string }[]; - if (packed.exitCode !== 0 || listing.length !== 1 || listing[0] === undefined) { + const packOutput = packOutputFromJson(packed.stdout); + if (packed.exitCode !== 0) { throw new Error('Packed native smoke could not create exactly one release tarball.'); } const installed = await runNodeEntrypoint(npmEntrypoint, [ 'install', '--omit=dev', ...npmInstallArguments, - join(tarballs, listing[0].filename), + join(tarballs, packOutput.filename), ], { cwd: consumer, environment }); if (installed.exitCode !== 0) throw new Error('Packed native smoke could not install the release tarball.');