diff --git a/tests/tools/test_terminal_timeout_output.py b/tests/tools/test_terminal_timeout_output.py new file mode 100644 index 000000000..52823581f --- /dev/null +++ b/tests/tools/test_terminal_timeout_output.py @@ -0,0 +1,27 @@ +"""Verify that terminal command timeouts preserve partial output.""" +from tools.environments.local import LocalEnvironment + + +class TestTimeoutPreservesPartialOutput: + """When a command times out, any output captured before the deadline + should be included in the result — not discarded.""" + + def test_timeout_includes_partial_output(self): + """A command that prints then sleeps past the deadline should + return both the printed text and the timeout notice.""" + env = LocalEnvironment() + result = env.execute("echo 'hello from test' && sleep 30", timeout=2) + + assert result["returncode"] == 124 + assert "hello from test" in result["output"] + assert "timed out" in result["output"].lower() + + def test_timeout_with_no_output(self): + """A command that produces nothing before timeout should still + return a clean timeout message.""" + env = LocalEnvironment() + result = env.execute("sleep 30", timeout=1) + + assert result["returncode"] == 124 + assert "timed out" in result["output"].lower() + assert not result["output"].startswith("\n") diff --git a/tools/environments/local.py b/tools/environments/local.py index 8cd416efa..27282b6ef 100644 --- a/tools/environments/local.py +++ b/tools/environments/local.py @@ -473,7 +473,12 @@ class LocalEnvironment(PersistentShellMixin, BaseEnvironment): except (ProcessLookupError, PermissionError): proc.kill() reader.join(timeout=2) - return self._timeout_result(effective_timeout) + partial = "".join(_output_chunks) + timeout_msg = f"\n[Command timed out after {effective_timeout}s]" + return { + "output": partial + timeout_msg if partial else timeout_msg.lstrip(), + "returncode": 124, + } time.sleep(0.2) reader.join(timeout=5)