|
7 | 7 | """ |
8 | 8 |
|
9 | 9 | import os |
10 | | -import signal |
11 | 10 | import sqlite3 |
12 | | -import sys |
13 | | -import time |
14 | 11 | from argparse import Namespace |
15 | 12 | from pathlib import Path |
16 | 13 |
|
@@ -658,93 +655,3 @@ def test_performance_multiple_test_methods_inner_loop(self, java_project): |
658 | 655 | ) |
659 | 656 |
|
660 | 657 |
|
661 | | -# --------------------------------------------------------------------------- |
662 | | -# Tests for _run_cmd_kill_pg_on_timeout |
663 | | -# Root-cause fix for "database is locked": when Maven times out, we must kill |
664 | | -# its forked Surefire child JVMs (entire process group), not just the Maven |
665 | | -# parent process. Plain subprocess.run() only kills the parent, orphaning the |
666 | | -# child JVMs which continue to hold the SQLite file open. |
667 | | -# --------------------------------------------------------------------------- |
668 | | - |
669 | | -class TestRunCmdKillPgOnTimeout: |
670 | | - """Unit tests for _run_cmd_kill_pg_on_timeout in test_runner.py.""" |
671 | | - |
672 | | - def _helper(self): |
673 | | - from codeflash.languages.java.test_runner import _run_cmd_kill_pg_on_timeout |
674 | | - return _run_cmd_kill_pg_on_timeout |
675 | | - |
676 | | - def test_successful_command_returns_correct_output(self): |
677 | | - """A command that finishes normally should return its stdout/stderr/returncode.""" |
678 | | - fn = self._helper() |
679 | | - result = fn(["echo", "hello"], timeout=10) |
680 | | - assert result.returncode == 0 |
681 | | - assert "hello" in result.stdout |
682 | | - |
683 | | - def test_failing_command_returns_nonzero_returncode(self): |
684 | | - """A command that exits non-zero should propagate the returncode.""" |
685 | | - fn = self._helper() |
686 | | - result = fn(["false"], timeout=10) |
687 | | - assert result.returncode != 0 |
688 | | - |
689 | | - @pytest.mark.skipif(sys.platform == "win32", reason="Process groups are POSIX only") |
690 | | - def test_timeout_kills_child_processes_not_just_parent(self, tmp_path): |
691 | | - """On timeout, child processes must be killed (not left orphaned). |
692 | | -
|
693 | | - We start a shell script that forks a long-running child (sleep 60). |
694 | | - The script writes the child PID to a file. We set a short timeout so |
695 | | - _run_cmd_kill_pg_on_timeout kills the entire process group. After the |
696 | | - call returns, the child must be dead (not still running). |
697 | | - """ |
698 | | - fn = self._helper() |
699 | | - child_pid_file = tmp_path / "child.pid" |
700 | | - # Shell: start sleep in background, write its PID, then sleep ourselves |
701 | | - # so the parent also runs long enough to be timed out. |
702 | | - script = ( |
703 | | - f"sleep 60 & echo $! > {child_pid_file}; sleep 60" |
704 | | - ) |
705 | | - result = fn(["bash", "-c", script], timeout=2) |
706 | | - |
707 | | - # The result should indicate a timeout (returncode -2) |
708 | | - assert result.returncode == -2 |
709 | | - |
710 | | - # Wait briefly for the child PID file to be written (race between |
711 | | - # the script writing it and us being killed). |
712 | | - deadline = time.monotonic() + 3 |
713 | | - while not child_pid_file.exists() and time.monotonic() < deadline: |
714 | | - time.sleep(0.1) |
715 | | - |
716 | | - if not child_pid_file.exists(): |
717 | | - # Script was killed before it could write the PID — that's fine; |
718 | | - # it means the child never started, so no orphan problem. |
719 | | - return |
720 | | - |
721 | | - child_pid_str = child_pid_file.read_text().strip() |
722 | | - if not child_pid_str.isdigit(): |
723 | | - return |
724 | | - |
725 | | - child_pid = int(child_pid_str) |
726 | | - # Give the child a moment to be killed by the process-group SIGKILL. |
727 | | - time.sleep(0.5) |
728 | | - |
729 | | - # Check if the child is still alive by sending signal 0. |
730 | | - try: |
731 | | - os.kill(child_pid, 0) |
732 | | - # If we get here the process is still running — that's the bug we fixed. |
733 | | - assert False, ( |
734 | | - f"Child process {child_pid} is still running after timeout kill; " |
735 | | - "orphaned JVMs would keep the SQLite file locked" |
736 | | - ) |
737 | | - except ProcessLookupError: |
738 | | - pass # Process is dead — expected behaviour |
739 | | - |
740 | | - def test_returncode_is_minus_two_on_timeout(self): |
741 | | - """Timed-out commands must return returncode=-2 (our convention).""" |
742 | | - fn = self._helper() |
743 | | - result = fn(["sleep", "60"], timeout=1) |
744 | | - assert result.returncode == -2 |
745 | | - |
746 | | - def test_timeout_message_in_stderr(self): |
747 | | - """The stderr of a timed-out command should describe the timeout.""" |
748 | | - fn = self._helper() |
749 | | - result = fn(["sleep", "60"], timeout=1) |
750 | | - assert "timeout" in result.stderr.lower() or "killed" in result.stderr.lower() |
0 commit comments