Ver código fonte

test(subprocess): align the teardown race with the settlement contract

`disposal contains a spawn-failure rejection that races teardown` asserted one
winner of the 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. Assert the contract instead: a rejection
carries the failure, a stop carries SIGTERM, which only the Linux scope records
because the win32 job owner and the fallback launcher both reject the start.

The two terminal cases that drive a mocked PTY exit state why they pin
`internals.platform`: under the host's native scope the mocked exit races the
scope bootstrap and fails as `terminal scope exited before its bootstrap
consumed the launch request`.
Chinesezjc 3 dias atrás
pai
commit
df709f442c

+ 22 - 2
packages/subprocess/subprocess-local/tests/local.spec.ts

@@ -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 () => {