"""Tests for signal-termination exit code interpretation. Ported from Kilo-Org/kilocode#12698 ("settle signal-terminated shell commands as 128 + signum"): the model must see a human-readable note for signal deaths instead of a bare exit_code=-9 / 137 it burns turns mis-diagnosing. """ import pytest from tools.terminal_tool import _interpret_exit_code, _interpret_signal_exit class TestInterpretSignalExit: # ---- negative codes: subprocess -signum semantics (definite) ---- @pytest.mark.parametrize("code,expect", [ (-9, "SIGKILL"), (-11, "SIGSEGV"), (-15, "SIGTERM"), (-6, "SIGABRT"), (-8, "SIGFPE"), (-13, "SIGPIPE"), ]) def test_negative_known_signals(self, code, expect): note = _interpret_signal_exit(code) assert note is not None assert expect in note assert "terminated by" in note.lower() def test_negative_oom_mentions_oom(self): note = _interpret_signal_exit(-9) assert "OOM" in note def test_negative_unknown_signal_still_reports(self): # signum without a curated note still yields a generic note. note = _interpret_signal_exit(-31) assert note is not None assert "31" in note # ---- 128+signum band: shell convention (hedged) ---- @pytest.mark.parametrize("code,expect", [ (137, "SIGKILL"), (139, "SIGSEGV"), (143, "SIGTERM"), (134, "SIGABRT"), (141, "SIGPIPE"), ]) def test_shell_band_known_signals(self, code, expect): note = _interpret_signal_exit(code) assert note is not None assert expect in note # Hedged: a program can legitimately exit with these codes. assert "usually" in note def test_shell_band_uncurated_signum_returns_none(self): # 128+signum for a signum outside the curated table must stay # silent — we never guess on ambiguous application exit codes. assert _interpret_signal_exit(128 + 40) is None # ---- exclusions ---- def test_sigint_130_excluded(self): # rc=130 has bespoke interrupt-marker handling in the executor. assert _interpret_signal_exit(130) is None assert _interpret_signal_exit(-2) is None @pytest.mark.parametrize("code", [0, 1, 2, 42, 100, 127, 128]) def test_normal_codes_return_none(self, code): assert _interpret_signal_exit(code) is None class TestSignalExitWiring: """_interpret_exit_code surfaces signal notes and keeps old semantics.""" def test_signal_note_via_interpret_exit_code(self): note = _interpret_exit_code("python3 crash.py", -11) assert note is not None and "SIGSEGV" in note def test_shell_band_via_interpret_exit_code(self): note = _interpret_exit_code("./run_build.sh", 137) assert note is not None and "SIGKILL" in note def test_signal_note_wins_over_command_semantics(self): # A grep killed by SIGKILL must report the signal, not "no matches". note = _interpret_exit_code("grep foo huge.log", 137) assert "SIGKILL" in note def test_grep_exit_1_unchanged(self): note = _interpret_exit_code("grep foo bar.txt", 1) assert note is not None and "no matches" in note.lower() def test_success_unchanged(self): assert _interpret_exit_code("ls", 0) is None