Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: XNLLLLH <XNLLLLH@users.noreply.github.com>
151 lines
5.9 KiB
Python
151 lines
5.9 KiB
Python
"""pytest wrapper for the platform-specific shell tests.
|
|
|
|
Runs tests/shell/test_launchd_install.sh on macOS and
|
|
tests/shell/test_systemd_install.sh on Linux WHEN the env var
|
|
`IAI_MCP_RUN_SHELL_INSTALL_TESTS=1` is set. CI sets this env var on the
|
|
correct runner; local dev does NOT (the scripts perform a real launchctl
|
|
bootstrap / systemctl --user enable cycle which would install the daemon
|
|
on the developer's machine and produce a persistent background process).
|
|
|
|
When the env var is unset, the actual-execution tests skip but the static
|
|
verification tests still run (executable bit, skip branches, C4 invariants
|
|
referenced in script source).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import platform
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
SHELL_DIR = Path(__file__).resolve().parent / "shell"
|
|
LAUNCHD_SCRIPT = SHELL_DIR / "test_launchd_install.sh"
|
|
SYSTEMD_SCRIPT = SHELL_DIR / "test_systemd_install.sh"
|
|
RUN_SHELL = os.environ.get("IAI_MCP_RUN_SHELL_INSTALL_TESTS") == "1"
|
|
|
|
|
|
def _bash_available() -> bool:
|
|
return shutil.which("bash") is not None
|
|
|
|
|
|
@pytest.mark.skipif(not RUN_SHELL, reason="set IAI_MCP_RUN_SHELL_INSTALL_TESTS=1 to run real launchctl bootstrap test")
|
|
@pytest.mark.skipif(not LAUNCHD_SCRIPT.exists(), reason="launchd shell test missing")
|
|
@pytest.mark.skipif(not _bash_available(), reason="bash unavailable")
|
|
@pytest.mark.skipif(platform.system() != "Darwin", reason="macOS-only")
|
|
def test_launchd_install_idempotency() -> None:
|
|
"""C4 + Pitfall 5 + DAEMON-10 idempotency end-to-end on the host."""
|
|
result = subprocess.run(
|
|
["bash", str(LAUNCHD_SCRIPT)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
assert result.returncode == 0, (
|
|
f"launchd shell test FAILED:\n"
|
|
f"--- STDOUT ---\n{result.stdout}\n"
|
|
f"--- STDERR ---\n{result.stderr}\n"
|
|
)
|
|
# Either PASS or SKIP is acceptable (skip happens when user has a
|
|
# pre-existing plist we won't clobber).
|
|
assert "PASS" in result.stdout or "SKIP" in result.stdout, result.stdout
|
|
|
|
|
|
@pytest.mark.skipif(not RUN_SHELL, reason="set IAI_MCP_RUN_SHELL_INSTALL_TESTS=1 to run real systemctl --user enable test")
|
|
@pytest.mark.skipif(not SYSTEMD_SCRIPT.exists(), reason="systemd shell test missing")
|
|
@pytest.mark.skipif(not _bash_available(), reason="bash unavailable")
|
|
@pytest.mark.skipif(platform.system() != "Linux", reason="Linux-only")
|
|
def test_systemd_install_idempotency() -> None:
|
|
"""C4 + Pitfall 5 + DAEMON-10 idempotency end-to-end on the host."""
|
|
result = subprocess.run(
|
|
["bash", str(SYSTEMD_SCRIPT)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
assert result.returncode == 0, (
|
|
f"systemd shell test FAILED:\n"
|
|
f"--- STDOUT ---\n{result.stdout}\n"
|
|
f"--- STDERR ---\n{result.stderr}\n"
|
|
)
|
|
assert "PASS" in result.stdout or "SKIP" in result.stdout, result.stdout
|
|
|
|
|
|
@pytest.mark.skipif(not LAUNCHD_SCRIPT.exists(), reason="launchd shell test missing")
|
|
@pytest.mark.skipif(not _bash_available(), reason="bash unavailable")
|
|
def test_launchd_script_skips_on_non_macos_platform() -> None:
|
|
"""Self-skip branch verification (always-runnable smoke test).
|
|
|
|
Invokes bash with `uname` reporting Linux via env override is not
|
|
portable, so we instead verify the SKIP branch executes correctly when
|
|
the script source contains the right guard. On non-macOS hosts, running
|
|
the script directly should exit 0 with `SKIP: not macOS` printed.
|
|
"""
|
|
if platform.system() == "Darwin":
|
|
pytest.skip("on Darwin -- this asserts the non-Darwin skip branch")
|
|
result = subprocess.run(
|
|
["bash", str(LAUNCHD_SCRIPT)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10,
|
|
)
|
|
assert result.returncode == 0
|
|
assert "SKIP: not macOS" in result.stdout
|
|
|
|
|
|
@pytest.mark.skipif(not SYSTEMD_SCRIPT.exists(), reason="systemd shell test missing")
|
|
@pytest.mark.skipif(not _bash_available(), reason="bash unavailable")
|
|
def test_systemd_script_skips_on_non_linux_platform() -> None:
|
|
"""Self-skip branch verification for the systemd script."""
|
|
if platform.system() == "Linux":
|
|
pytest.skip("on Linux -- this asserts the non-Linux skip branch")
|
|
result = subprocess.run(
|
|
["bash", str(SYSTEMD_SCRIPT)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10,
|
|
)
|
|
assert result.returncode == 0
|
|
assert "SKIP: not Linux" in result.stdout
|
|
|
|
|
|
def test_shell_scripts_are_executable() -> None:
|
|
"""Both scripts must have the executable bit so CI can invoke directly."""
|
|
import os
|
|
if LAUNCHD_SCRIPT.exists():
|
|
assert os.access(LAUNCHD_SCRIPT, os.X_OK), (
|
|
f"{LAUNCHD_SCRIPT} not executable"
|
|
)
|
|
if SYSTEMD_SCRIPT.exists():
|
|
assert os.access(SYSTEMD_SCRIPT, os.X_OK), (
|
|
f"{SYSTEMD_SCRIPT} not executable"
|
|
)
|
|
|
|
|
|
def test_shell_scripts_have_skip_branch() -> None:
|
|
"""Cross-platform skip branch must exist in both scripts (Plan 04-05 AC)."""
|
|
if LAUNCHD_SCRIPT.exists():
|
|
text = LAUNCHD_SCRIPT.read_text()
|
|
assert "SKIP: not macOS" in text, "launchd script missing macOS skip branch"
|
|
if SYSTEMD_SCRIPT.exists():
|
|
text = SYSTEMD_SCRIPT.read_text()
|
|
assert "SKIP: not Linux" in text, "systemd script missing Linux skip branch"
|
|
|
|
|
|
def test_shell_scripts_check_c4_invariant() -> None:
|
|
"""Both scripts must verify C4 cleanup of all 3 state files."""
|
|
for script in (LAUNCHD_SCRIPT, SYSTEMD_SCRIPT):
|
|
if not script.exists():
|
|
continue
|
|
text = script.read_text()
|
|
assert "C4" in text, f"{script.name} missing C4 reference"
|
|
assert ".lock" in text, f"{script.name} does not check lock file removal"
|
|
assert ".daemon.sock" in text or "SOCK" in text, (
|
|
f"{script.name} does not check socket file removal"
|
|
)
|
|
assert ".daemon-state.json" in text or "STATE" in text, (
|
|
f"{script.name} does not check state file removal"
|
|
)
|