Files
planet/scripts/harness/test_docker_command.py
linkong 60eefb19c9
Some checks are pending
ci / backend (push) Waiting to run
ci / frontend (push) Waiting to run
ci / delivery (push) Blocked by required conditions
release / images (push) Waiting to run
release: bump version to 0.74.7
2026-09-17 17:30:54 +08:00

94 lines
4.1 KiB
Python

"""Exercise stalled Docker substitutes without contacting a daemon."""
import os
from pathlib import Path
import signal
import subprocess
import sys
import tempfile
import unittest
RUNNER = Path(__file__).resolve().parents[1] / "docker_command.py"
class DockerCommandTests(unittest.TestCase):
def run_command(self, source: str) -> tuple[subprocess.CompletedProcess[str], str]:
with tempfile.TemporaryDirectory() as folder:
log = Path(folder) / "quoted ' $(literal).log"
status = Path(folder) / "status"
result = subprocess.run(
[sys.executable, str(RUNNER), "--label", "Compose up postgres redis",
"--timeout", "0.6", "--heartbeat", "0.15", "--log", str(log),
"--status-file", str(status), "--",
sys.executable, "-c", source],
capture_output=True, text=True, timeout=5,
)
self.assertEqual(log.stat().st_mode & 0o777, 0o600)
self.last_status = status.read_text()
return result, log.read_text()
def test_silent_hang_has_heartbeat_deadline_and_no_invented_cause(self) -> None:
result, log = self.run_command("import time; time.sleep(30)")
self.assertEqual(result.returncode, 124, result.stderr)
self.assertIn("Compose up postgres redis", self.last_status)
self.assertIn("超时", self.last_status)
self.assertEqual(result.stdout + result.stderr, "")
self.assertIn("PLANET_DOCKER_COMMAND_TIMEOUT", log)
self.assertNotIn("代理", log)
def test_partial_and_carriage_return_output_survives_timeout(self) -> None:
result, log = self.run_command(
"import time; print('postgres Pulling\\rredis Downloading', end='', flush=True); "
"time.sleep(30)"
)
self.assertEqual(result.returncode, 124)
self.assertIn("postgres Pulling", log)
self.assertIn("redis Downloading", self.last_status)
def test_failure_is_streamed_and_original_exit_code_preserved(self) -> None:
result, log = self.run_command(
"import sys; print('TLS handshake timeout', file=sys.stderr); sys.exit(17)"
)
self.assertEqual(result.returncode, 17)
self.assertIn("TLS handshake timeout", result.stdout)
self.assertEqual(log, "TLS handshake timeout\n")
self.assertEqual(self.last_status, "TLS handshake timeout")
def test_success_and_proxy_credentials(self) -> None:
result, log = self.run_command("print('https://user:secret@example.test/v2/ ready')")
self.assertEqual(result.returncode, 0)
self.assertEqual(self.last_status, "")
self.assertEqual(result.stderr, "")
self.assertIn("https://***@example.test/v2/ ready", log)
self.assertNotIn("secret", result.stdout + result.stderr + log)
def test_long_compose_network_error_keeps_the_port_in_secondary_line(self) -> None:
result, _ = self.run_command(
"import sys; print('Error response from daemon: failed to set up container networking: '"
" + 'x' * 160 + ': Bind for 127.0.0.1:5432 failed: port is already allocated'); sys.exit(1)"
)
self.assertEqual(result.returncode, 1)
self.assertEqual(self.last_status,
"Bind for 127.0.0.1:5432 failed: port is already allocated")
def test_timeout_kills_descendant_even_after_cli_parent_exits(self) -> None:
result, _ = self.run_command(
"import subprocess, sys; "
"p = subprocess.Popen([sys.executable, '-c', "
"'import signal,time; signal.signal(signal.SIGTERM, signal.SIG_IGN); "
"time.sleep(30)']); print(p.pid, flush=True)"
)
self.assertEqual(result.returncode, 124)
child = int(result.stdout.strip())
state = Path(f"/proc/{child}/stat")
try:
self.assertTrue(not state.exists() or state.read_text().split()[2] == "Z")
finally:
if state.exists() and state.read_text().split()[2] != "Z":
os.kill(child, signal.SIGKILL)
if __name__ == "__main__":
unittest.main()