fix(e2b): close proven provider boundary gaps
This commit is contained in:
@@ -116,6 +116,7 @@ class FakeSandbox {
|
||||
environmentRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
|
||||
processGroupId = '4242\n'
|
||||
exitStatus = ''
|
||||
statusReads = 0
|
||||
readonly processGroupReads: string[] = []
|
||||
afterStatusRead: (() => void) | undefined
|
||||
beforeProbe: (() => void) | undefined
|
||||
@@ -222,6 +223,7 @@ class FakeSandbox {
|
||||
this.statusError = undefined
|
||||
throw error
|
||||
}
|
||||
this.statusReads += 1
|
||||
this.afterStatusRead?.()
|
||||
return this.exitStatus
|
||||
},
|
||||
@@ -415,9 +417,10 @@ describe('E2BSubprocessHandle', () => {
|
||||
expect(command).not.toContain('explicit-secret')
|
||||
expect(command).not.toContain('hyphen-value')
|
||||
expect(command).not.toContain('${!dsh_e2b_name}')
|
||||
expect(fake.commandsSeen).toContain(
|
||||
'set -o pipefail; printf \'%s\' "$PWD" | base64 -w 0; printf \'\\n\'; env -0 | base64 -w 0',
|
||||
)
|
||||
const environmentProbe = fake.commandsSeen.find(value => value.includes('env -0 | base64'))
|
||||
expect(environmentProbe).toContain('getent passwd "$(id -u)"')
|
||||
expect(environmentProbe).toContain('test -n "$dsh_e2b_home" -a -d "$dsh_e2b_home"')
|
||||
expect(environmentProbe).not.toContain('"$PWD"')
|
||||
expect(command).toContain('mapfile -d')
|
||||
expect(command).toContain('dsh_e2b_node="$(command -v node)"')
|
||||
expect(command).toContain('"$dsh_e2b_env_bin" -i "$dsh_e2b_node" -e')
|
||||
@@ -535,11 +538,13 @@ describe('E2BSubprocessHandle', () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
expect(settled).toBe(false)
|
||||
expect(fake.handle.disconnects).toBe(0)
|
||||
expect(fake.statusReads).toBe(0)
|
||||
|
||||
await fake.stdout('complete protocol frame')
|
||||
fake.finish()
|
||||
await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null })
|
||||
expect(output).toBe('complete protocol frame')
|
||||
expect(fake.statusReads).toBe(1)
|
||||
})
|
||||
|
||||
it('accepts clean encoder completion inside the output-drain grace', async () => {
|
||||
|
||||
@@ -102,6 +102,9 @@ class FakeTerminalSandbox {
|
||||
sendError: unknown
|
||||
commandFailure: unknown
|
||||
makeDirRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
|
||||
sendInputRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
|
||||
foregroundRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
|
||||
signalRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
|
||||
sessionGroupsFailure: unknown
|
||||
foregroundFailure: unknown
|
||||
termFailure: unknown
|
||||
@@ -180,6 +183,8 @@ class FakeTerminalSandbox {
|
||||
return { exitCode: 0, stdout: this.sessionId, stderr: '' }
|
||||
}
|
||||
if (command.startsWith('ps -o tpgid=')) {
|
||||
await this.foregroundRequest?.(options?.signal)
|
||||
options?.signal?.throwIfAborted()
|
||||
if (this.foregroundFailure !== undefined) throw this.foregroundFailure
|
||||
return { exitCode: 0, stdout: this.foreground, stderr: '' }
|
||||
}
|
||||
@@ -197,6 +202,10 @@ class FakeTerminalSandbox {
|
||||
this.handle.fail(143)
|
||||
}
|
||||
}
|
||||
if (command.startsWith('kill -INT -- ')) {
|
||||
await this.signalRequest?.(options?.signal)
|
||||
options?.signal?.throwIfAborted()
|
||||
}
|
||||
if (command.startsWith('kill -KILL -- ') && this.clearOnKill) this.groups = []
|
||||
return { exitCode: 0, stdout: '', stderr: '' }
|
||||
},
|
||||
@@ -211,6 +220,8 @@ class FakeTerminalSandbox {
|
||||
return this.handle.asHandle()
|
||||
},
|
||||
sendInput: async (pid: number, data: Uint8Array, options?: { signal?: AbortSignal }): Promise<void> => {
|
||||
options?.signal?.throwIfAborted()
|
||||
await this.sendInputRequest?.(options?.signal)
|
||||
options?.signal?.throwIfAborted()
|
||||
this.inputs.push({ pid, data: Buffer.from(data) })
|
||||
if (this.sendError !== undefined) throw this.sendError
|
||||
@@ -257,6 +268,19 @@ function spec(overrides: Partial<SubprocessTerminalSpawnSpec> = {}): SubprocessT
|
||||
}
|
||||
}
|
||||
|
||||
function holdRequestUntilAbort(started: PromiseWithResolvers<AbortSignal>) {
|
||||
return async (signal: AbortSignal | undefined): Promise<void> => {
|
||||
if (signal === undefined) throw new Error('expected an operation signal')
|
||||
signal.throwIfAborted()
|
||||
started.resolve(signal)
|
||||
await new Promise<void>((_resolve, reject) => {
|
||||
signal.addEventListener('abort', () => {
|
||||
reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)))
|
||||
}, { once: true })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe('E2B terminal allocation', () => {
|
||||
it('hides bootstrap-shell bytes and preserves requested-shell bytes across the output boundary', async () => {
|
||||
const fake = new FakeTerminalSandbox()
|
||||
@@ -544,6 +568,43 @@ describe('E2B terminal allocation', () => {
|
||||
})
|
||||
|
||||
describe('E2B terminal lifecycle', () => {
|
||||
it('aborts and joins in-flight terminal operations before cleanup', async () => {
|
||||
const fake = new FakeTerminalSandbox()
|
||||
const terminal = await spawnE2BTerminal(runtime(fake), spec(), '/runtime/in-flight-operations')
|
||||
const writeStarted = Promise.withResolvers<AbortSignal>()
|
||||
const inspectStarted = Promise.withResolvers<AbortSignal>()
|
||||
const signalStarted = Promise.withResolvers<AbortSignal>()
|
||||
fake.sendInputRequest = holdRequestUntilAbort(writeStarted)
|
||||
let foregroundRequests = 0
|
||||
fake.foregroundRequest = async (signal) => {
|
||||
foregroundRequests += 1
|
||||
if (foregroundRequests === 1) await holdRequestUntilAbort(inspectStarted)(signal)
|
||||
}
|
||||
let signalCompleted = false
|
||||
fake.signalRequest = async (operationSignal) => {
|
||||
await holdRequestUntilAbort(signalStarted)(operationSignal)
|
||||
signalCompleted = true
|
||||
}
|
||||
const write = terminal.write('late input')
|
||||
const inspect = terminal.inspectForeground()
|
||||
await Promise.all([writeStarted.promise, inspectStarted.promise])
|
||||
const signal = terminal.signalForeground('SIGINT')
|
||||
await signalStarted.promise
|
||||
|
||||
const terminating = terminal.terminate()
|
||||
await expect(write).rejects.toThrow('terminal is terminating')
|
||||
await expect(inspect).rejects.toThrow('terminal is terminating')
|
||||
await expect(signal).rejects.toThrow('terminal is terminating')
|
||||
await terminating
|
||||
expect(signalCompleted).toBe(false)
|
||||
expect(fake.inputs).toHaveLength(1)
|
||||
const commandCount = fake.commands.length
|
||||
await expect(terminal.write('after termination')).rejects.toThrow('terminal is terminating')
|
||||
await expect(terminal.inspectForeground()).rejects.toThrow('terminal is terminating')
|
||||
await expect(terminal.signalForeground('SIGINT')).rejects.toThrow('terminal is terminating')
|
||||
expect(fake.commands).toHaveLength(commandCount)
|
||||
})
|
||||
|
||||
it('maps ordinary exits, closes output, and reports an absent foreground after exit', async () => {
|
||||
const fake = new FakeTerminalSandbox()
|
||||
fake.groups = []
|
||||
|
||||
Reference in New Issue
Block a user