import io import os import select import signal import subprocess import sys import unittest from unittest.mock import Mock, patch from car_tracker import container_entrypoint class ContainerEntrypointTests(unittest.TestCase): def test_requires_a_command(self): with patch("sys.argv", ["entrypoint"]), patch("sys.stderr", new_callable=io.StringIO): self.assertEqual(container_entrypoint.main(), 3) def test_missing_display_has_distinct_safe_error(self): error = FileNotFoundError(2, "missing", "/private/display") with patch("sys.argv", ["entrypoint", "command"]), \ patch("sys.stderr", new_callable=io.StringIO) as stderr, \ patch.object(container_entrypoint.subprocess, "Popen", side_effect=error): self.assertEqual(container_entrypoint.main(), 3) self.assertIn("Virtual display executable could not be found", stderr.getvalue()) self.assertNotIn("/private/display", stderr.getvalue()) def test_missing_command_has_distinct_safe_error_and_cleans_display(self): display = Mock() error = FileNotFoundError(2, "missing", "/private/command") with patch("sys.argv", ["entrypoint", "/private/command"]), \ patch("sys.stderr", new_callable=io.StringIO) as stderr, \ patch.object(container_entrypoint.subprocess, "Popen", side_effect=[display, error]), \ patch.object(container_entrypoint.select, "select", return_value=([3], [], [])), \ patch.object(container_entrypoint.os, "read", return_value=b"0\n"): self.assertEqual(container_entrypoint.main(), 3) self.assertIn("Container command could not be found", stderr.getvalue()) self.assertNotIn("/private/command", stderr.getvalue()) display.terminate.assert_called_once() display.wait.assert_called_once() @unittest.skipUnless(os.environ.get("RUN_BROWSER_TESTS") == "1", "Xvfb container tests are opt-in") class VirtualDisplayTests(unittest.TestCase): def test_child_exit_code_is_preserved(self): result = subprocess.run([ sys.executable, "-m", "car_tracker.container_entrypoint", sys.executable, "-c", "raise SystemExit(7)", ], capture_output=True, text=True, timeout=20) self.assertEqual(result.returncode, 7, result.stderr) def test_sigterm_waits_for_child_finalization(self): command = """ import signal, sys, time def stop(signum, frame): print('cleanup-start', flush=True) time.sleep(1) print('cleanup-done', flush=True) sys.exit(130) signal.signal(signal.SIGTERM, stop) print('ready', flush=True) while True: signal.pause() """ process = subprocess.Popen([ sys.executable, "-m", "car_tracker.container_entrypoint", sys.executable, "-c", command, ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) try: ready, _, _ = select.select([process.stdout], [], [], 15) self.assertTrue(ready, "Child did not become ready") self.assertEqual(process.stdout.readline().strip(), "ready") process.send_signal(signal.SIGTERM) stdout, stderr = process.communicate(timeout=15) self.assertEqual(process.returncode, 130, stderr) self.assertIn("cleanup-start", stdout) self.assertIn("cleanup-done", stdout) finally: if process.poll() is None: process.terminate() process.communicate(timeout=15)