from __future__ import annotations import json import os import signal import socket import subprocess import sys import time from pathlib import Path import pytest from admin_server import send_admin_command pytestmark = pytest.mark.skipif(os.name == "nt", reason="SIGTERM integration is POSIX-only") SERVER_DIR = Path(__file__).resolve().parents[1] def _free_port() -> int: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.bind(("127.0.0.1", 0)) return int(sock.getsockname()[1]) def test_sigterm_exits_cleanly(tmp_path: Path) -> None: game_port = _free_port() admin_port = _free_port() config_path = tmp_path / "commonwealth-server.json" config_path.write_text( json.dumps( { "host": "127.0.0.1", "port": game_port, "server_name": "CI Test Server", "server_description": "", "max_players": 4, "log_verbosity": "info", "admin_port": admin_port, }, indent=2, ) + "\n", encoding="utf-8", newline="\n", ) env = os.environ.copy() env["PYTHONUNBUFFERED"] = "1" env["NO_COLOR"] = "1" process = subprocess.Popen( [ sys.executable, "-u", str(SERVER_DIR / "consumer_server_cli.py"), "serve", "--config", str(config_path), ], cwd=str(SERVER_DIR), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=env, ) try: deadline = time.time() + 10.0 connected = False while time.time() < deadline: try: with socket.create_connection(("127.0.0.1", game_port), timeout=0.5) as conn: welcome = conn.recv(4096) if b"welcome" in welcome: connected = True break except OSError: time.sleep(0.1) assert connected, "server did not accept a test client in time" admin_deadline = time.time() + 5.0 response: dict[str, object] | None = None while time.time() < admin_deadline: try: response = send_admin_command({"cmd": "ping"}, port=admin_port, timeout_seconds=0.5) break except (OSError, RuntimeError, ConnectionError): time.sleep(0.05) assert response is not None assert response.get("ok") is True process.send_signal(signal.SIGTERM) try: exit_code = process.wait(timeout=10.0) except subprocess.TimeoutExpired: process.kill() pytest.fail("server did not exit after SIGTERM") assert exit_code == 0 finally: if process.poll() is None: process.kill() process.wait(timeout=5.0)