The OS lanes are marker-driven: list_os_marked_tests.py picks the files
a lane imports from their platforms() specs and the lane selects with
-m platforms. A test gated with skipif(sys.platform != "win32") is
therefore never imported on the Windows lane and skipped everywhere else
— it runs on no host. skipif(sys.platform == "win32") tests were merely
invisible to the lane bookkeeping, but the rule the tree now follows is
one host marker, never a bare skipif.
Mechanical mapping, semantics preserved: skip-on-Windows → "posix",
skip-off-Windows → "windows", skip-off-Linux → "linux", skip-on-macOS →
"not macos". The former skip reasons stay as trailing comments. A
non-host condition (os.geteuid() == 0) stays a separate skipif beside
the marker, spelled getattr(os, "geteuid", ...) so the decorator still
imports on Windows.
Where the conversion would stack two platforms() marks on one test (the
conftest rejects that at collection) the narrower mark wins:
- test_update_wedged_gateway: the class is already platforms("linux");
its per-test "needs UNIX sockets" marks were redundant and are gone.
- test_process_registry.TestSystemdCgroupIsolation: the class-level
skip-on-Windows moves onto the 11 methods that had no host mark; the
11 platforms("linux") methods keep theirs.
- test_file_ops_single_roundtrip: the two fifo tests drop their
platforms("linux") in favour of the module's "posix" (mkfifo exists on
macOS; both tests already skip when it does not).
- test_linux_desktop_entry / test_gateway_job_teardown_live: duplicate
or wider marks removed.
110 lines
3.4 KiB
Python
110 lines
3.4 KiB
Python
"""pause-for-update control-socket verb (#92091 step 2, campaign #91277).
|
|
|
|
The updater asks a running gateway to drain and exit cleanly (releasing its
|
|
venv file handles) instead of tree-killing it mid-turn. Fallback contract:
|
|
older gateways without the verb answer nothing, and callers keep the legacy
|
|
marker/force-kill path.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from gateway.control_socket import (
|
|
GatewayControlServer,
|
|
pause_gateway_for_update,
|
|
query_gateway_control,
|
|
)
|
|
|
|
|
|
def _make_server(tmp_path, handler):
|
|
server = GatewayControlServer(
|
|
home=tmp_path, verb_handlers={"pause-for-update": handler}
|
|
)
|
|
return server
|
|
|
|
|
|
def test_pause_verb_dispatches_and_returns_ack(tmp_path):
|
|
calls = []
|
|
|
|
def handler():
|
|
calls.append(1)
|
|
return {"pausing": True, "already_stopping": False, "pid": 111,
|
|
"drain_timeout": 30.0}
|
|
|
|
server = _make_server(tmp_path, handler)
|
|
raw = json.dumps({"verb": "pause-for-update", "id": 7}).encode()
|
|
response = json.loads(server.handle_request_line(raw).decode())
|
|
assert response["ok"] is True
|
|
assert response["result"]["pausing"] is True
|
|
assert response["result"]["drain_timeout"] == 30.0
|
|
assert response["id"] == 7
|
|
assert calls == [1]
|
|
|
|
|
|
def test_unknown_verb_still_lists_pause(tmp_path):
|
|
server = _make_server(tmp_path, lambda: {})
|
|
raw = json.dumps({"verb": "nope"}).encode()
|
|
response = json.loads(server.handle_request_line(raw).decode())
|
|
assert response["ok"] is False
|
|
assert "pause-for-update" in response["supported_verbs"]
|
|
|
|
|
|
@pytest.mark.platforms("posix") # unix socket transport
|
|
def test_pause_client_roundtrip_over_real_socket(tmp_path):
|
|
"""Full client→socket→handler→ACK path over a REAL unix socket."""
|
|
|
|
async def scenario():
|
|
acks = []
|
|
|
|
def handler():
|
|
acks.append(1)
|
|
return {"pausing": True, "already_stopping": False,
|
|
"pid": 4242, "drain_timeout": 12.5}
|
|
|
|
server = _make_server(tmp_path, handler)
|
|
assert await server.start()
|
|
try:
|
|
loop = asyncio.get_running_loop()
|
|
result = await loop.run_in_executor(
|
|
None, lambda: pause_gateway_for_update(tmp_path, timeout=5.0)
|
|
)
|
|
finally:
|
|
await server.stop()
|
|
return result, acks
|
|
|
|
result, acks = asyncio.run(scenario())
|
|
assert acks == [1]
|
|
assert result is not None
|
|
assert result["pausing"] is True and result["drain_timeout"] == 12.5
|
|
|
|
|
|
@pytest.mark.platforms("posix") # unix socket transport
|
|
def test_pause_client_none_when_gateway_lacks_verb(tmp_path):
|
|
"""Back-compat: a step-1 gateway (identify/status only) answers ok:false
|
|
for the unknown verb → the client returns None → caller keeps the legacy
|
|
kill path."""
|
|
|
|
async def scenario():
|
|
server = GatewayControlServer(home=tmp_path) # no pause handler
|
|
assert await server.start()
|
|
try:
|
|
loop = asyncio.get_running_loop()
|
|
return await loop.run_in_executor(
|
|
None, lambda: pause_gateway_for_update(tmp_path, timeout=5.0)
|
|
)
|
|
finally:
|
|
await server.stop()
|
|
|
|
assert asyncio.run(scenario()) is None
|
|
|
|
|
|
def test_pause_client_none_when_no_socket(tmp_path):
|
|
assert pause_gateway_for_update(tmp_path, timeout=0.5) is None
|