From c632b693c676dea22c9b8fc07dcc5b1528889c68 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 12 Jul 2026 00:50:50 +0800 Subject: [PATCH] test(e2e): tolerate bounded loader contention --- .../tests/code-mode-keyless-smoke.e2e.ts | 11 ++++++++--- .../coding-agent/tests/keyless-smoke.e2e.ts | 11 ++++++++--- .../cordis-agent/tests/keyless-smoke.e2e.ts | 11 ++++++++--- examples/echo-agent/tests/echo.e2e.ts | 17 +++++++++++------ 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/examples/coding-agent/tests/code-mode-keyless-smoke.e2e.ts b/examples/coding-agent/tests/code-mode-keyless-smoke.e2e.ts index 7894e9ff9f..b25e26ea05 100644 --- a/examples/coding-agent/tests/code-mode-keyless-smoke.e2e.ts +++ b/examples/coding-agent/tests/code-mode-keyless-smoke.e2e.ts @@ -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) }) diff --git a/examples/coding-agent/tests/keyless-smoke.e2e.ts b/examples/coding-agent/tests/keyless-smoke.e2e.ts index b4d73f4d90..b104d8af63 100644 --- a/examples/coding-agent/tests/keyless-smoke.e2e.ts +++ b/examples/coding-agent/tests/keyless-smoke.e2e.ts @@ -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) }) diff --git a/examples/cordis-agent/tests/keyless-smoke.e2e.ts b/examples/cordis-agent/tests/keyless-smoke.e2e.ts index b37ea8d83e..d170d0b9bf 100644 --- a/examples/cordis-agent/tests/keyless-smoke.e2e.ts +++ b/examples/cordis-agent/tests/keyless-smoke.e2e.ts @@ -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) }) diff --git a/examples/echo-agent/tests/echo.e2e.ts b/examples/echo-agent/tests/echo.e2e.ts index b446b2b191..546b69c0f3 100644 --- a/examples/echo-agent/tests/echo.e2e.ts +++ b/examples/echo-agent/tests/echo.e2e.ts @@ -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) })