mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
fix(code-runtime-python): align the resolvePythonBin docs and the exception-group guard
The review's follow-ups on the pythonBin change: (1) the JSDoc and the two call-site comments still described the old fallback-to-bare-name contract; they now state the load-rejection behavior. (2) the ExceptionGroup case's version guard raised a skip message on Python < 3.11 but the assertion still required the truncation marker unconditionally — the assertion now matches either the truncation marker (3.11+) or the skip message (3.10). (3) the shell wrappers quote the resolved interpreter path.
This commit is contained in:
@@ -387,11 +387,12 @@ export function readProcessStart(pid: number): string | undefined {
|
||||
* falls back to the platform default (`/usr/bin:/bin`) and misses interpreters
|
||||
* that live only on the caller's `PATH` (Nix, pyenv, Homebrew, conda). An
|
||||
* absolute or explicitly relative path is used verbatim. When no `PATH` entry
|
||||
* holds an executable match, the original value is returned unchanged so the
|
||||
* spawn produces its normal ENOENT `error` event (a settled `worker-exit`),
|
||||
* not a thrown exception here.
|
||||
* holds an executable match, `undefined` is returned and the LOAD check rejects
|
||||
* the configuration: falling back to the bare name would let spawn's `env: {}`
|
||||
* execvp silently start a system interpreter from the platform default PATH
|
||||
* that the caller never asked for.
|
||||
* @param bin - the configured interpreter (absolute path or bare command).
|
||||
* @returns an absolute path when resolvable, else `bin` unchanged.
|
||||
* @returns an absolute path when resolvable, else `undefined`.
|
||||
*/
|
||||
export function resolvePythonBin(bin: string): string | undefined {
|
||||
if (isAbsolute(bin) || bin.includes('/')) return bin
|
||||
|
||||
@@ -511,7 +511,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
|
||||
const wrapper = join(dir, 'python3-capped')
|
||||
// 256 MiB, half the 512 MiB addressSpaceMb default, so the requested cap is
|
||||
// unambiguously above the inherited ceiling.
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -v 262144\nexec ${PYABS} "$@"\n`, { mode: 0o755 })
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -v 262144\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
|
||||
const { runtime } = await setup({ pythonBin: wrapper })
|
||||
const result = await runtime.run({
|
||||
program: 'import resource\nreturn resource.getrlimit(resource.RLIMIT_AS)[1]',
|
||||
@@ -537,7 +537,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
|
||||
// (macOS ignores `ulimit -v`); there the run proceeds.
|
||||
const dir = await mkdtemp(join(tmpdir(), 'dsh-rlimit-'))
|
||||
const wrapper = join(dir, 'python3-tight')
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -v 131072\nexec ${PYABS} "$@"\n`, { mode: 0o755 })
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -v 131072\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
|
||||
const { runtime } = await setup({ pythonBin: wrapper, maxLogBytes: 32 * 1024 * 1024, addressSpaceMb: 512 })
|
||||
const result = await runtime.run({ program: 'return 1', bindings: [] })
|
||||
if (process.platform === 'darwin') {
|
||||
@@ -580,7 +580,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'dsh-rlimit-soft-'))
|
||||
const wrapper = join(dir, 'python3-soft-capped')
|
||||
// Soft CPU 5 s, well below the configured 30 s, hard left unlimited.
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -S -t 5\nexec ${PYABS} "$@"\n`, { mode: 0o755 })
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -S -t 5\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
|
||||
const { runtime } = await setup({ pythonBin: wrapper, cpuSeconds: 30 })
|
||||
const result = await runtime.run({
|
||||
program: 'import resource\nreturn resource.getrlimit(resource.RLIMIT_CPU)[0]',
|
||||
@@ -605,7 +605,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'dsh-rlimit-dual-'))
|
||||
const wrapper = join(dir, 'python3-dual-capped')
|
||||
// Both soft and hard CPU 2 s; configured cpuSeconds 30 s.
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -t 2\nexec ${PYABS} "$@"\n`, { mode: 0o755 })
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -t 2\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
|
||||
const { runtime } = await setup({ pythonBin: wrapper, cpuSeconds: 30, maxWallMs: 12_000 })
|
||||
const result = await runtime.run({
|
||||
program: [
|
||||
@@ -653,7 +653,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
|
||||
// before model code runs, so a busy loop still ends as a timeout rather
|
||||
// than running to the hard limit and being misclassified as worker-exit.
|
||||
const wrapper = join(tmpdir(), `dsh-xcpu-ignore-${process.pid}.sh`)
|
||||
writeFileSync(wrapper, `#!/bin/sh\ntrap "" XCPU\nexec ${PYABS} "$@"\n`, { mode: 0o755 })
|
||||
writeFileSync(wrapper, `#!/bin/sh\ntrap "" XCPU\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
|
||||
try {
|
||||
const { runtime } = await setup({ maxWallMs: 30_000, cpuSeconds: 1, pythonBin: wrapper })
|
||||
const result = await runtime.run({
|
||||
@@ -707,7 +707,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
|
||||
// re-deliver SIGXCPU so the host classifies the run as a timeout.
|
||||
const dir = await mkdtemp(join(tmpdir(), 'dsh-cpu-recheck-'))
|
||||
const wrapper = join(dir, 'python3-cpu-capped')
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -S -t 1\nexec ${PYABS} "$@"\n`, { mode: 0o755 })
|
||||
await writeFile(wrapper, `#!/bin/sh\nulimit -S -t 1\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
|
||||
const { runtime } = await setup({ pythonBin: wrapper, cpuSeconds: 30, maxWallMs: 12_000 })
|
||||
const result = await runtime.run({
|
||||
program: [
|
||||
@@ -2162,7 +2162,10 @@ describe('PythonCodeRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
})
|
||||
expect(result.error?.kind).toBe('exception')
|
||||
expect(result.error?.message).toContain('exception chain truncated at 100 links')
|
||||
// The version guard skips on Python < 3.11 (ExceptionGroup is a 3.11+
|
||||
// builtin) with a distinct message; the truncation assertion applies on
|
||||
// 3.11+ where the group nesting is what is being probed.
|
||||
expect(result.error?.message).toMatch(/exception chain truncated at 100 links|skip-old/)
|
||||
}, 20_000)
|
||||
|
||||
it('filters every bootstrap frame from the traceback of an uncaught binding rejection', async () => {
|
||||
|
||||
Reference in New Issue
Block a user