mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 12:18:39 +00:00
The platform guard refused a direct os.kill in workspace_executor.py (abe93702, CI 33661022574 red on every OS): the question the executor asks -- can WE signal this pid? -- is a platform question and now lives in platform_layer.pid_is_signalable (POSIX: signal 0, EPERM/ESRCH are "not ours"; Windows: liveness). Behaviour is that ofabe93702: our own hash-less child is killed, a forged pid-1 record is ignored; that rule is a non-root property, so the forged pin skips under root and the residual is in the ledger. The compaction suite no longer skips whole on Windows: its data_root fixture still skips where a compaction must LAND (the pass refuses on the name tier by design), but the four tier-agnostic pins -- the name-tier refusal itself, the locked-writer refusal, the two structural validator pins -- run everywhere through data_root_any_tier. Four probe pins in tests/test_lockfile_helpers.py whose mechanics the Windows short-circuit never reaches skip there with the reason. Ledger: the504bb20csentence is marked corrected and a new section records the macOS finding, the guard, the root residual and the emulated name-tier numbers (32 failed / 23 errors, not only the fixture errors).
253 lines
9.5 KiB
Python
253 lines
9.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
|
|
def _write_docker_records(state_dir, *, foreground_pidfile="/tmp/ouroboros-exec-test.pid", service_pid="12345"):
|
|
state_dir.mkdir(parents=True)
|
|
(state_dir / "foreground-docker.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"id": "foreground-docker",
|
|
"schema_version": 1,
|
|
"owner": "ouroboros_workspace_executor",
|
|
"record_type": "foreground",
|
|
"executor_type": "docker_exec",
|
|
"executor_id": "docker",
|
|
"host_pid": 0,
|
|
"container_name": "bench",
|
|
"backend_pidfile": foreground_pidfile,
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
(state_dir / "service-docker.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"id": "service-docker",
|
|
"schema_version": 1,
|
|
"owner": "ouroboros_workspace_executor",
|
|
"record_type": "service",
|
|
"service_id": "task:svc",
|
|
"task_id": "task",
|
|
"name": "svc",
|
|
"executor_type": "docker_exec",
|
|
"executor_id": "docker",
|
|
"container_name": "bench",
|
|
"backend_pid": service_pid,
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def _install_live_docker_service(workspace_executor, tmp_path):
|
|
executor = workspace_executor.ExecutorRef(
|
|
kind="docker_exec",
|
|
executor_id="docker",
|
|
network="none",
|
|
mappings=(),
|
|
container_name="bench",
|
|
)
|
|
with workspace_executor._STATE_LOCK:
|
|
workspace_executor._SERVICES.clear()
|
|
workspace_executor._SERVICES["task:live"] = workspace_executor._ExecutorService(
|
|
service_id="task:live",
|
|
task_id="task",
|
|
name="live",
|
|
executor=executor,
|
|
cmd=["sleep", "30"],
|
|
host_cwd=tmp_path,
|
|
backend_cwd="/workspace",
|
|
cwd_root="active_workspace",
|
|
outputs=[],
|
|
before_outputs={},
|
|
backend_pid="67890",
|
|
)
|
|
|
|
|
|
def test_executor_panic_cleanup_wait_false_uses_bounded_docker_stop(tmp_path, monkeypatch):
|
|
import ouroboros.workspace_executor as workspace_executor
|
|
|
|
data = tmp_path / "data"
|
|
state_dir = data / "state" / "workspace_executor_processes"
|
|
_write_docker_records(state_dir)
|
|
_install_live_docker_service(workspace_executor, tmp_path)
|
|
|
|
docker_run_calls: list[list[str]] = []
|
|
|
|
def fake_docker_wait(cmd, **kwargs):
|
|
docker_run_calls.append([str(part) for part in cmd])
|
|
if "kill -0" in str(cmd[-1]):
|
|
return subprocess.CompletedProcess(cmd, 0, stdout="exited\n", stderr="")
|
|
return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="")
|
|
|
|
class FakePopen:
|
|
def __init__(self, cmd, **kwargs):
|
|
raise AssertionError("panic Docker cleanup must not spawn untracked helpers")
|
|
|
|
monkeypatch.setattr(workspace_executor.subprocess, "run", fake_docker_wait)
|
|
monkeypatch.setattr(workspace_executor.subprocess, "Popen", FakePopen)
|
|
|
|
killed_foreground = workspace_executor.kill_all_foreground(data, wait=False)
|
|
killed_services = workspace_executor.kill_all_services(data, wait=False)
|
|
|
|
# Both in-memory and durable services perform an explicit kill-0
|
|
# confirmation after the stop shell, in addition to the dispatches.
|
|
assert len(docker_run_calls) == 5
|
|
assert all(call[:2] == ["docker", "exec"] for call in docker_run_calls)
|
|
assert any(item.get("executor_type") == "docker_exec" for item in killed_foreground)
|
|
assert any(item.get("state") == "stopped" for item in killed_services)
|
|
assert all(item.get("cleanup_dispatched") is True for item in killed_foreground + killed_services)
|
|
assert not list(state_dir.glob("*.json"))
|
|
with workspace_executor._STATE_LOCK:
|
|
assert "task:live" not in workspace_executor._SERVICES
|
|
|
|
|
|
def test_docker_executor_confirmed_cleanup_failure_preserves_records(tmp_path, monkeypatch):
|
|
import ouroboros.workspace_executor as workspace_executor
|
|
|
|
data = tmp_path / "data"
|
|
state_dir = data / "state" / "workspace_executor_processes"
|
|
_write_docker_records(state_dir)
|
|
_install_live_docker_service(workspace_executor, tmp_path)
|
|
|
|
def fake_failed_docker_wait(cmd, **kwargs):
|
|
return subprocess.CompletedProcess(cmd, 1, stdout="", stderr="permission denied")
|
|
|
|
monkeypatch.setattr(workspace_executor.subprocess, "run", fake_failed_docker_wait)
|
|
|
|
killed_foreground = workspace_executor.kill_all_foreground(data, wait=True)
|
|
killed_services = workspace_executor.kill_all_services(data, wait=True)
|
|
|
|
assert any(item.get("cleanup_dispatched") is False for item in killed_foreground)
|
|
assert any(item.get("state") == "cleanup_pending" for item in killed_foreground + killed_services)
|
|
assert {path.name for path in state_dir.glob("*.json")} == {"foreground-docker.json", "service-docker.json"}
|
|
with workspace_executor._STATE_LOCK:
|
|
assert "task:live" in workspace_executor._SERVICES
|
|
workspace_executor._SERVICES.clear()
|
|
|
|
|
|
def test_executor_cleanup_ignores_unowned_forged_process_records(tmp_path, monkeypatch):
|
|
import ouroboros.workspace_executor as workspace_executor
|
|
|
|
data = tmp_path / "data"
|
|
state_dir = data / "state" / "workspace_executor_processes"
|
|
state_dir.mkdir(parents=True)
|
|
(state_dir / "foreground-forged.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"id": "foreground-forged",
|
|
"record_type": "foreground",
|
|
"executor_type": "local",
|
|
"host_pid": 1,
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
monkeypatch.setattr(
|
|
workspace_executor,
|
|
"_kill_host_pid",
|
|
lambda _pid: (_ for _ in ()).throw(AssertionError("forged record should be ignored")),
|
|
)
|
|
|
|
assert workspace_executor.kill_all_foreground(data, wait=False) == []
|
|
|
|
|
|
@pytest.mark.skipif(getattr(os, "geteuid", lambda: 1)() == 0, reason="under root every live pid is signalable; the forged-record rule (pid 1 refuses signal 0) is a non-root property")
|
|
def test_executor_cleanup_ignores_owner_shaped_forged_host_pid_records(tmp_path, monkeypatch):
|
|
import ouroboros.workspace_executor as workspace_executor
|
|
|
|
data = tmp_path / "data"
|
|
state_dir = data / "state" / "workspace_executor_processes"
|
|
state_dir.mkdir(parents=True)
|
|
(state_dir / "foreground-forged.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"id": "foreground-forged",
|
|
"schema_version": 1,
|
|
"owner": "ouroboros_workspace_executor",
|
|
"record_type": "foreground",
|
|
"executor_type": "local",
|
|
"host_pid": 1,
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
monkeypatch.setattr(
|
|
workspace_executor,
|
|
"_kill_host_pid",
|
|
lambda _pid: (_ for _ in ()).throw(AssertionError("owner-shaped forged record should be ignored")),
|
|
)
|
|
|
|
assert workspace_executor.kill_all_foreground(data, wait=False) == []
|
|
|
|
|
|
def test_executor_cleanup_kills_a_hash_less_record_of_our_own_live_child(tmp_path, monkeypatch):
|
|
"""A record whose command line could not be captured at register time (macOS
|
|
`ps` right after the spawn, Windows always) still names OUR child: it is alive
|
|
and answers signal 0, so the panic cleanup must kill it — the forged-record
|
|
rule above refuses only pids we cannot signal (macOS full-test 33658408570)."""
|
|
import subprocess
|
|
import sys
|
|
|
|
import ouroboros.workspace_executor as workspace_executor
|
|
from ouroboros.platform_layer import subprocess_new_group_kwargs
|
|
|
|
data = tmp_path / "data"
|
|
child = subprocess.Popen(
|
|
[sys.executable, "-c", "import time; time.sleep(30)"],
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
|
|
**subprocess_new_group_kwargs(),
|
|
)
|
|
killed: list = []
|
|
try:
|
|
monkeypatch.setattr(workspace_executor, "_process_command_sha256", lambda _pid: "")
|
|
workspace_executor._register_process(
|
|
data, {"record_type": "foreground", "executor_type": "local", "host_pid": child.pid},
|
|
)
|
|
monkeypatch.setattr(workspace_executor, "_kill_host_pid", lambda pid: killed.append(int(pid)))
|
|
assert [r["id"] for r in workspace_executor.kill_all_foreground(data, wait=False)]
|
|
assert killed == [child.pid]
|
|
finally:
|
|
child.kill()
|
|
child.wait(timeout=10)
|
|
|
|
|
|
def test_executor_cleanup_ignores_pidless_docker_service_records(tmp_path, monkeypatch):
|
|
import ouroboros.workspace_executor as workspace_executor
|
|
|
|
data = tmp_path / "data"
|
|
state_dir = data / "state" / "workspace_executor_processes"
|
|
state_dir.mkdir(parents=True)
|
|
(state_dir / "service-docker.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"id": "service-docker",
|
|
"schema_version": 1,
|
|
"owner": "ouroboros_workspace_executor",
|
|
"record_type": "service",
|
|
"service_id": "task:svc",
|
|
"task_id": "task",
|
|
"name": "svc",
|
|
"executor_type": "docker_exec",
|
|
"executor_id": "docker",
|
|
"container_name": "bench",
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
monkeypatch.setattr(
|
|
workspace_executor.subprocess,
|
|
"run",
|
|
lambda *a, **k: (_ for _ in ()).throw(AssertionError("pidless docker service record should be ignored")),
|
|
)
|
|
|
|
assert workspace_executor.kill_all_services(data, wait=False) == []
|