aboutsummaryrefslogtreecommitdiff
path: root/app/tests/test_backends_common.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/tests/test_backends_common.py')
-rw-r--r--app/tests/test_backends_common.py57
1 files changed, 55 insertions, 2 deletions
diff --git a/app/tests/test_backends_common.py b/app/tests/test_backends_common.py
index a94994b..08ffc2c 100644
--- a/app/tests/test_backends_common.py
+++ b/app/tests/test_backends_common.py
@@ -2,8 +2,9 @@
The streaming mode of ``run_console_subprocess`` (used by the in-TUI task
view) is exercised with a real child process: output lines are captured and
-forwarded, cancellation kills the child and returns 130, and an on_cancel
-hook runs first.
+forwarded, cancellation kills the child and returns 130, an on_cancel hook
+runs first, and the no-output stall watchdog kills a wedged child and
+returns 124.
"""
import sys
@@ -51,6 +52,58 @@ class RunConsoleSubprocessStreamingTests(unittest.TestCase):
self.assertEqual(touched, [True])
+class RunConsoleSubprocessStallTests(unittest.TestCase):
+ """The no-output watchdog: a silent child is killed and reported 124."""
+
+ def test_stall_kills_a_silent_child_and_returns_124(self):
+ lines = []
+ rc = common.run_console_subprocess(
+ [sys.executable, "-c",
+ "import sys, time; print('start', flush=True); "
+ "time.sleep(60)"],
+ emit=lines.append, stall_timeout=0.5)
+ self.assertEqual(rc, 124)
+ self.assertEqual(lines[0], "start")
+ # The stall is announced to the view before the kill.
+ self.assertTrue(any("[ERROR]" in line and "No output" in line
+ for line in lines), lines)
+
+ def test_no_stall_while_output_keeps_flowing(self):
+ lines = []
+ rc = common.run_console_subprocess(
+ [sys.executable, "-c",
+ "import sys, time\n"
+ "for _ in range(6):\n"
+ " print('tick', flush=True)\n"
+ " time.sleep(0.2)\n"],
+ emit=lines.append, stall_timeout=1.0)
+ self.assertEqual(rc, 0)
+ self.assertEqual(lines, ["tick"] * 6)
+
+ def test_console_path_has_no_watchdog(self):
+ # Without emit the child inherits the terminal; stall_timeout is
+ # a no-op there (the caller sees raw output and can Ctrl-C).
+ rc = common.run_console_subprocess(
+ [sys.executable, "-c", "print('hi')"], stall_timeout=0.001)
+ self.assertEqual(rc, 0)
+
+
+class RunConsoleSubprocessQuietTimeoutTests(unittest.TestCase):
+ """A timed-out quiet probe returns a failed result, never raises."""
+
+ def test_timeout_returns_failed_result(self):
+ proc = common.run_console_subprocess_quiet(
+ [sys.executable, "-c", "import time; time.sleep(30)"],
+ timeout=0.5)
+ self.assertIsNotNone(proc)
+ self.assertEqual(proc.returncode, -1)
+
+ def test_untimed_probe_still_reports_the_exit_code(self):
+ proc = common.run_console_subprocess_quiet(
+ [sys.executable, "-c", "import sys; sys.exit(5)"])
+ self.assertEqual(proc.returncode, 5)
+
+
class GitCloneTests(unittest.TestCase):
def test_git_clone_console_passes_through(self):
with mock.patch.object(common, "run_console_subprocess",