104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
"""LIVE Windows E2E for background-executor spawn parity (#70716 / PR salvage).
|
|
|
|
Runs ONLY on a real Windows host (the on-demand ``windows-venv-e2e.yml``
|
|
lane). The systemd cgroup-isolation feature for local background executors
|
|
must be a strict no-op on Windows: jobs spawn exactly as before, output is
|
|
captured, exit codes are correct, and no systemd code path is ever reached
|
|
— even when the process claims gateway identity.
|
|
|
|
These tests drive the REAL ``ProcessRegistry.spawn_local`` pipe path on the
|
|
live Windows process table (real Popen, real Git Bash shell, real reader
|
|
thread) — no mocked spawn.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
sys.platform != "win32", reason="live Windows background-executor E2E"
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def registry(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes-home"))
|
|
import tools.process_registry as pr
|
|
|
|
reg = pr.ProcessRegistry()
|
|
yield reg
|
|
for sid in list(reg._running):
|
|
try:
|
|
reg.kill_process(sid)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _wait_exit(reg, sid, timeout=60):
|
|
deadline = time.time() + timeout
|
|
while time.time() < deadline:
|
|
sess = reg._finished.get(sid) or reg._running.get(sid)
|
|
if sess is not None and sess.exited:
|
|
return sess
|
|
time.sleep(0.2)
|
|
raise AssertionError(f"session {sid} did not exit within {timeout}s")
|
|
|
|
|
|
class TestWindowsSpawnParity:
|
|
def test_background_job_runs_output_and_exit_code_unchanged(self, registry):
|
|
"""Plain background job: spawned, output captured, exit code correct."""
|
|
session = registry.spawn_local("echo win-live-parity; exit 7")
|
|
done = _wait_exit(registry, session.id)
|
|
|
|
assert done.exit_code == 7
|
|
assert "win-live-parity" in done.output_buffer
|
|
# The systemd scope identity must never be recorded on Windows.
|
|
assert done.systemd_unit == ""
|
|
|
|
def test_gateway_identity_never_reaches_systemd_path_on_windows(
|
|
self, registry, monkeypatch
|
|
):
|
|
"""Even with full (faked) gateway identity, the Windows spawn takes
|
|
the legacy path: no scope argv is built, no probe runs, and the job
|
|
behaves exactly as without the identity."""
|
|
import tools.process_registry as pr
|
|
|
|
monkeypatch.setenv("_HERMES_GATEWAY", "1")
|
|
monkeypatch.setattr(
|
|
"gateway.status.get_running_pid",
|
|
lambda *, cleanup_stale=False: os.getpid(),
|
|
)
|
|
monkeypatch.setattr(
|
|
"gateway.restart.is_gateway_supervisor_process", lambda: True
|
|
)
|
|
monkeypatch.setattr(pr, "_SYSTEMD_SCOPE_AVAILABLE", None)
|
|
|
|
scope_builds = []
|
|
monkeypatch.setattr(
|
|
pr,
|
|
"_build_systemd_scope_argv",
|
|
lambda *a, **k: scope_builds.append(a) or a[0],
|
|
)
|
|
|
|
session = registry.spawn_local("echo win-live-gateway; exit 3")
|
|
done = _wait_exit(registry, session.id)
|
|
|
|
assert done.exit_code == 3
|
|
assert "win-live-gateway" in done.output_buffer
|
|
assert done.systemd_unit == ""
|
|
assert scope_builds == [], "Windows must never build a systemd scope argv"
|
|
# The availability probe must not have flipped to True on Windows.
|
|
assert pr._SYSTEMD_SCOPE_AVAILABLE is not True
|
|
|
|
def test_kill_process_windows_plain_path(self, registry):
|
|
"""kill_process on Windows works without any systemd unit cleanup."""
|
|
session = registry.spawn_local("sleep 60")
|
|
time.sleep(1.0)
|
|
result = registry.kill_process(session.id)
|
|
assert result.get("status") in {"killed", "already_exited"}
|
|
assert session.systemd_unit == ""
|