diff --git a/engine/runner.py b/engine/runner.py index 9f21061..b567b72 100644 --- a/engine/runner.py +++ b/engine/runner.py @@ -9,22 +9,30 @@ from engine.models import RunResult def run_command(command: str, cwd: Path, timeout_seconds: int) -> RunResult: start = time.perf_counter() - completed = subprocess.run( - command, - cwd=str(cwd), - shell=True, - capture_output=True, - text=True, - encoding="utf-8", - timeout=timeout_seconds, - check=False, - ) + try: + completed = subprocess.run( + command, + cwd=str(cwd), + shell=True, + capture_output=True, + text=True, + encoding="utf-8", + timeout=timeout_seconds, + check=False, + ) + exit_code = completed.returncode + stdout = completed.stdout + stderr = completed.stderr + except subprocess.TimeoutExpired as exc: + exit_code = 124 + stdout = exc.stdout or exc.output or "" + stderr = exc.stderr or f"command timed out after {timeout_seconds} seconds" runtime_seconds = time.perf_counter() - start return RunResult( command=command, cwd=cwd, - exit_code=completed.returncode, + exit_code=exit_code, runtime_seconds=runtime_seconds, - stdout=completed.stdout, - stderr=completed.stderr, + stdout=stdout, + stderr=stderr, ) diff --git a/tests/test_execution_pipeline.py b/tests/test_execution_pipeline.py index 5201fa4..5ffbc9d 100644 --- a/tests/test_execution_pipeline.py +++ b/tests/test_execution_pipeline.py @@ -15,6 +15,16 @@ class ExecutionPipelineTest(unittest.TestCase): self.assertEqual(result.exit_code, 0) self.assertIn("ok", result.stdout) + def test_run_command_returns_result_on_timeout(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + result = run_command( + "python -c \"import time; time.sleep(2)\"", + Path(tmp), + timeout_seconds=1, + ) + self.assertNotEqual(result.exit_code, 0) + self.assertIn("timed out", result.stderr.lower()) + def test_parse_score_output_reads_primary_score(self) -> None: score = parse_score_output( '{"score": 4.5, "metrics": {"violation_count": 0}}',