diff --git a/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts b/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts index 218b7d908f..e8618b5e07 100644 --- a/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts +++ b/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts @@ -725,6 +725,7 @@ describe('PythonCodeRuntime — programs and bindings', () => { expect(result.value).toBe('nope') }, 15_000) + it('still answers the call when the rejection value cannot be converted to a string', async () => { // `messageOf` calls `String(error)`, which runs the value's own conversion, // and this call site is a DETACHED async reply callback. A rejection whose @@ -2644,6 +2645,26 @@ describe('PythonCodeRuntime — budgets, termination, disposal', () => { expect(result.logs).toContain('leader-diagnostic-no-newline') }, 8000) + it('closes the child stdin so a program read sees EOF instead of blocking', async () => { + // The host closes the child's stdin write handle immediately after spawn + // (the program is an async body that reads nothing from fd 0; a live pipe + // would hold a host-side handle open past the run). A program that DOES + // read fd 0 therefore sees EOF at once. Fail-before: with the handle left + // open and no data written, `sys.stdin.read()` blocks and the run would + // hang to maxWallMs as a timeout. + const { runtime } = await setup({ maxWallMs: 8_000 }) + const result = await runtime.run({ + program: [ + 'import sys', + 'data = sys.stdin.read()', + 'return "read: " + repr(data)', + ].join('\n'), + bindings: [], + }) + expect(result.error).toBeUndefined() + expect(result.value).toBe("read: ''") + }, 15_000) + it('reaps a same-group child that ignores SIGTERM and releases the pipes before close', async () => { // The same-group counterpart to the setsid-orphan case above. A descendant // left in the child's OWN process group (no setsid, so `kill(-pid)` reaches