test(e2e): tolerate bounded loader contention

This commit is contained in:
Tianyi Cui
2026-07-12 00:50:50 +08:00
parent cb05300ba7
commit c632b693c6
4 changed files with 35 additions and 15 deletions

View File

@@ -26,6 +26,11 @@ const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
// `paths` map; tsx searches UP from cwd, and we spawn from a temp dir outside
// the repo, so point it at the repo tsconfig.
const repoTsconfig = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
// The real-API workflow runs up to 14 e2e files at once. Cold tsx/Loader
// startup can therefore outlive a tight smoke-test deadline before the child
// emits any output; 30s still detects a wedged process without confusing
// bounded CI contention with a lifecycle failure.
const PROCESS_TIMEOUT_MS = 30_000
let child: ChildProcessWithoutNullStreams | undefined
let workdir: string | undefined
@@ -67,8 +72,8 @@ async function bootAndEof(): Promise<{ stdout: string; code: number }> {
const timer = setTimeout(() => {
proc.kill('SIGKILL')
reject(new Error(`code-mode overlay did not exit within 10s. stdout:\n${stdout}\nstderr:\n${stderr}`))
}, 10_000)
reject(new Error(`code-mode overlay did not exit within ${PROCESS_TIMEOUT_MS / 1_000}s. stdout:\n${stdout}\nstderr:\n${stderr}`))
}, PROCESS_TIMEOUT_MS)
proc.on('exit', (code) => {
clearTimeout(timer)
@@ -87,5 +92,5 @@ describe('code-mode overlay keyless smoke (real code-mode.cordis.yml via the Loa
const { stdout, code } = await bootAndEof()
expect(code).toBe(0)
expect(stdout).toContain('code-mode agent ready.')
}, 15_000)
}, PROCESS_TIMEOUT_MS + 5_000)
})

View File

@@ -35,6 +35,11 @@ const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
// `paths` map; tsx searches UP from cwd, and we spawn from a temp dir outside
// the repo, so point it at the repo tsconfig (root is four levels up).
const repoTsconfig = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
// The real-API workflow runs up to 14 e2e files at once. Cold tsx/Loader
// startup can therefore outlive a tight smoke-test deadline before the child
// emits any output; 30s still detects a wedged process without confusing
// bounded CI contention with a lifecycle failure.
const PROCESS_TIMEOUT_MS = 30_000
let child: ChildProcessWithoutNullStreams | undefined
let workdir: string | undefined
@@ -78,8 +83,8 @@ async function bootAndEof(): Promise<{ stdout: string; code: number }> {
const timer = setTimeout(() => {
proc.kill('SIGKILL')
reject(new Error(`coding-agent did not exit within 10s. stdout:\n${stdout}\nstderr:\n${stderr}`))
}, 10_000)
reject(new Error(`coding-agent did not exit within ${PROCESS_TIMEOUT_MS / 1_000}s. stdout:\n${stdout}\nstderr:\n${stderr}`))
}, PROCESS_TIMEOUT_MS)
proc.on('exit', (code) => {
clearTimeout(timer)
@@ -98,5 +103,5 @@ describe('coding-agent keyless smoke (real cordis.yml via the Loader)', () => {
const { stdout, code } = await bootAndEof()
expect(code).toBe(0)
expect(stdout).toContain('agent REPL ready.')
}, 15_000)
}, PROCESS_TIMEOUT_MS + 5_000)
})

View File

@@ -29,6 +29,11 @@ const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
// `paths` map; tsx searches UP from cwd, and we spawn from a temp dir outside
// the repo, so point it at the repo tsconfig (root is three levels up).
const repoTsconfig = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
// The real-API workflow runs up to 14 e2e files at once. Cold tsx/Loader
// startup can therefore outlive a tight smoke-test deadline before the child
// emits any output; 30s still detects a wedged process without confusing
// bounded CI contention with a lifecycle failure.
const PROCESS_TIMEOUT_MS = 30_000
let child: ChildProcessWithoutNullStreams | undefined
let workdir: string | undefined
@@ -70,8 +75,8 @@ async function bootAndEof(): Promise<{ stdout: string; code: number }> {
const timer = setTimeout(() => {
proc.kill('SIGKILL')
reject(new Error(`cordis-agent did not exit within 10s. stdout:\n${stdout}\nstderr:\n${stderr}`))
}, 10_000)
reject(new Error(`cordis-agent did not exit within ${PROCESS_TIMEOUT_MS / 1_000}s. stdout:\n${stdout}\nstderr:\n${stderr}`))
}, PROCESS_TIMEOUT_MS)
proc.on('exit', (code) => {
clearTimeout(timer)
@@ -90,5 +95,5 @@ describe('cordis-agent keyless smoke (real cordis.yml via the Loader)', () => {
const { stdout, code } = await bootAndEof()
expect(code).toBe(0)
expect(stdout).toContain('cordis-agent ready.')
}, 15_000)
}, PROCESS_TIMEOUT_MS + 5_000)
})

View File

@@ -37,6 +37,11 @@ const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
// a temp cwd OUTSIDE the repo, so point tsx at the repo tsconfig explicitly
// (repo root is four levels up from examples/echo-agent/tests).
const repoTsconfig = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
// The real-API workflow runs up to 14 e2e files at once. Cold tsx/Loader
// startup can therefore outlive a tight smoke-test deadline before the child
// emits any output; 30s still detects a wedged process without confusing
// bounded CI contention with a lifecycle failure.
const PROCESS_TIMEOUT_MS = 30_000
let child: ChildProcessWithoutNullStreams | undefined
let workdir: string | undefined
@@ -51,7 +56,7 @@ afterEach(async () => {
/**
* Boot echo-agent, write `lines` to its stdin, close stdin, and resolve with
* the full stdout once the process exits (the stdio UI exits on EOF after the
* agent settles). Rejects on a non-zero exit or a 10s timeout.
* agent settles). Rejects on a non-zero exit or the process deadline.
*/
async function runEcho(lines: string[]): Promise<{ stdout: string; code: number }> {
workdir = await mkdtemp(join(tmpdir(), 'echo-smoke-'))
@@ -84,8 +89,8 @@ async function runEcho(lines: string[]): Promise<{ stdout: string; code: number
const timer = setTimeout(() => {
proc.kill('SIGKILL')
reject(new Error(`echo-agent did not exit within 10s. stdout:\n${stdout}\nstderr:\n${stderr}`))
}, 10_000)
reject(new Error(`echo-agent did not exit within ${PROCESS_TIMEOUT_MS / 1_000}s. stdout:\n${stdout}\nstderr:\n${stderr}`))
}, PROCESS_TIMEOUT_MS)
proc.on('exit', (code) => {
clearTimeout(timer)
@@ -105,19 +110,19 @@ describe('echo-agent keyless smoke (real cordis.yml via the Loader)', () => {
const { stdout, code } = await runEcho([])
expect(code).toBe(0)
expect(stdout).toContain('echo-agent ready.')
}, 15_000)
}, PROCESS_TIMEOUT_MS + 5_000)
it('runs the echo tool round-trip for an "echo …" line', async () => {
const { stdout } = await runEcho(['echo hello world'])
// mock-llm.ts emits a tool-call for the echo tool; echo-tool.ts uppercases.
expect(stdout).toContain('[tool call] echo')
expect(stdout).toContain('[tool result] ECHO: HELLO WORLD')
}, 15_000)
}, PROCESS_TIMEOUT_MS + 5_000)
it('streams a direct canned reply for a non-echo line', async () => {
const { stdout } = await runEcho(['just chatting'])
// The direct-response branch of mock-llm.ts quotes the input back.
expect(stdout).toContain('just chatting')
expect(stdout).not.toContain('[tool call]')
}, 15_000)
}, PROCESS_TIMEOUT_MS + 5_000)
})