|
|
@@ -393,6 +393,8 @@ describe('LocalSubprocessRuntime', () => {
|
|
|
const ctx = new Context()
|
|
|
const fiber = await ctx.plugin(IsolatedLocalSubprocessRuntime)
|
|
|
const service = ctx.subprocess as InstanceType<typeof IsolatedLocalSubprocessRuntime>
|
|
|
+ // Pins the containment choice: with the host's native scope a mocked PTY
|
|
|
+ // exit races the scope bootstrap.
|
|
|
service.internals = { platform: 'darwin' }
|
|
|
const handle = await ctx.subprocess.spawnTerminal({
|
|
|
argv: ['shell'], cwd: process.cwd(), rows: 24, cols: 80, graceMs: 1,
|
|
|
@@ -600,6 +602,8 @@ describe('LocalSubprocessRuntime', () => {
|
|
|
ctx.logger.error = ((error: unknown) => { disposalErrors.push(error) }) as typeof ctx.logger.error
|
|
|
const fiber = await ctx.plugin(IsolatedLocalSubprocessRuntime)
|
|
|
const alive = new Set([124])
|
|
|
+ // Pins the containment choice: with the host's native scope a mocked PTY
|
|
|
+ // exit races the scope bootstrap.
|
|
|
;(ctx.subprocess as InstanceType<typeof IsolatedLocalSubprocessRuntime>).internals = { platform: 'darwin' }
|
|
|
;(ctx.subprocess as InstanceType<typeof IsolatedLocalSubprocessRuntime>).terminalInspector = {
|
|
|
foregroundPgid: () => 123,
|
|
|
@@ -878,10 +882,26 @@ describe('LocalSubprocessRuntime', () => {
|
|
|
const ctx = new Context()
|
|
|
const fiber = await ctx.plugin(LocalSubprocessRuntime)
|
|
|
// Dispose before the rejection continuation removes the handle from the
|
|
|
- // live set, so teardown itself must swallow the rejected done.
|
|
|
+ // live set, so teardown itself must swallow the rejected done. Two
|
|
|
+ // settlements are valid and the winner is a race: a bootstrap that
|
|
|
+ // publishes its pre-exec failure rejects with that failure, and a teardown
|
|
|
+ // that stops the bootstrap first settles as the requested termination —
|
|
|
+ // the recorded failure only outranks the stop when it was published before
|
|
|
+ // the stop landed.
|
|
|
const handle = ctx.subprocess.spawn(spec('true', { cwd: '/nonexistent-dir-dsh-subprocess-test' }))
|
|
|
await fiber.dispose()
|
|
|
- await expect(handle.done).rejects.toThrow()
|
|
|
+ const settlement = await handle.done.then(
|
|
|
+ outcome => ({ kind: 'stopped' as const, outcome }),
|
|
|
+ (error: unknown) => ({ kind: 'failed' as const, error }),
|
|
|
+ )
|
|
|
+ if (settlement.kind === 'failed') {
|
|
|
+ expect(settlement.error).toBeInstanceOf(Error)
|
|
|
+ } else {
|
|
|
+ // Only the Linux scope records a stop this way: the win32 job owner
|
|
|
+ // rejects a cancelled start and the fallback launcher rejects the ENOENT,
|
|
|
+ // so neither can produce the stopped branch.
|
|
|
+ expect(settlement.outcome.signal).toBe('SIGTERM')
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
it('loading a second implementation throws (one processes service per context — cordis standard)', async () => {
|