← Files NGS Analysis WorkbenchARCHIVED FILE
tests/test_ssh_transport.py
31.5 KB · Sep 30, 2026 · 23:20 UTC
from __future__ import annotations
import hashlib
import itertools
import json
import os
import subprocess
import sys
import tempfile
import threading
import unittest
from dataclasses import replace
from pathlib import Path
from unittest import mock
MCP_ROOT = Path(__file__).resolve().parents[1] / "mcp"
if str(MCP_ROOT) not in sys.path:
sys.path.insert(0, str(MCP_ROOT))
from daemon_test_case import DaemonTestCase # noqa: E402
from ngs_workbench_daemon import ( # noqa: E402
client,
inspection,
remote_controller,
remote_operations,
remote_probe,
runs,
server,
ssh,
)
from ngs_workbench_daemon.persistence import ( # noqa: E402
SqlAlchemyUnitOfWork,
default_database,
ensure_workspace_identity,
)
from ngs_workbench_daemon.protocol import ( # noqa: E402
ExecutionRequest,
TargetInspection,
canonical_plan_checksum,
)
from ngs_workbench_daemon.state import daemon_directory, daemon_token # noqa: E402
from ngs_workbench_execution_monitoring import DEFAULT_OBSERVER_REGISTRY # noqa: E402
from ngs_workbench_mcp import runs as mcp_runs # noqa: E402
from test_run_persistence import new_run # noqa: E402
class ControllerLogTests(DaemonTestCase):
def setUp(self) -> None:
super().setUp()
self.server = server.DaemonServer(daemon_token(daemon_directory(), create=True), "test")
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
self.thread.start()
self.addCleanup(self.server.server_close)
self.addCleanup(self.thread.join, 2)
self.addCleanup(self.server.shutdown)
endpoint = client.DaemonEndpoint("127.0.0.1", self.server.server_port, "test", "test")
self.enterContext(mock.patch.object(client, "ensure_daemon_running", return_value=endpoint))
self.enterContext(mock.patch.object(client, "healthy_daemon", return_value=endpoint))
self.access = {"alias": "approved-test-host"}
self.enterContext(mock.patch.object(ssh, "effective_access", return_value=self.access))
local_python = subprocess.run
local_process = subprocess.Popen
self.stream_transport = self.enterContext(
mock.patch.object(
ssh.subprocess,
"Popen",
side_effect=lambda argv, **options: local_process([sys.executable, "-"], **options),
)
)
# Run the actual uploaded program in a subprocess, substituting only SSH transport.
self.transport = self.enterContext(
mock.patch.object(
ssh.subprocess,
"run",
side_effect=lambda argv, **options: local_python([sys.executable, "-"], **options),
)
)
def _register(self, binding="nextflow", target="lab", status="completed", warnings=()):
workspace_id = ensure_workspace_identity(self.workspace)
candidate = new_run(self.workspace, workspace_id, f"{binding}-{target}-{status}", binding)
local = self.workspace / candidate.run_relative_path
remote = self.root / "remote" / candidate.external_run_id
log_name = f"logs/{binding}.log"
plan = {
"command_argv": ["nextflow", "-with-trace", str(remote / "workflow/trace.txt")],
"readiness": {},
"warnings": list(warnings),
"effects": {"run_dir": str(local), "launch_log": str(local / log_name)},
"request": {
"target": {"target_id": target},
"remote": {
"run_dir": str(remote),
"workspace_root": str(self.root),
"host_access": self.access,
},
},
}
checksum = canonical_plan_checksum(plan)
audit = self.workspace / candidate.approved_plan_relative_path
audit.parent.mkdir(parents=True)
audit.write_text(json.dumps({**plan, "plan_checksum": checksum}))
candidate = replace(
candidate,
plan_checksum=checksum,
request={
"daemon_instance_id": "test",
"target": {"target_id": target},
"plan_request": plan["request"],
},
launch_log_relative_path=f"{candidate.run_relative_path}/{log_name}",
)
with SqlAlchemyUnitOfWork(default_database(), immediate=True) as uow:
uow.registry.register_workspace(workspace_id, self.workspace)
record = uow.registry.allocate_approved_run(candidate)
record = uow.registry.transition_run(
record.id, "running", expected_revision=record.revision
)
if status == "canceled":
for state in ("cancel_requested", "canceling"):
record = uow.registry.transition_run(
record.id, state, expected_revision=record.revision
)
if status != "running":
record = uow.registry.transition_run(
record.id, status, expected_revision=record.revision
)
uow.commit()
log = (local if target == "local" else remote) / log_name
log.parent.mkdir(parents=True, exist_ok=True)
log.write_text("old output\n" * 2000 + "final controller output\n")
if status == "running":
self.server.runs._runs[record.id] = runs.OwnedRun(
request=ExecutionRequest(binding=binding, plan_checksum=checksum, plan=plan),
registry_run_id=record.id,
remote_identity={"pid": 123},
)
return record, log
def test_remote_detail_uses_one_shared_parser_and_list_reads_no_evidence(self) -> None:
fixtures = Path(__file__).parent / "fixtures/execution_observation"
manifest = json.loads((fixtures / "fixture_manifest.json").read_text())
for binding, target in itertools.product(("nextflow", "snakemake"), ("local", "lab")):
with self.subTest(binding=binding, target=target):
record, log = self._register(
binding=binding, target=target, status="running", warnings=["Plan warning"]
)
root = log.parent.parent
evidence = root / "workflow/trace.txt"
if binding == "snakemake":
evidence = root / "results/.snakemake/log/run.snakemake.log"
evidence.parent.mkdir(parents=True, exist_ok=True)
evidence.write_bytes((fixtures / manifest[binding]["fixture_path"]).read_bytes())
if binding == "snakemake":
older = evidence.with_name("older.snakemake.log")
older.write_text("old native log\n")
os.utime(older, ns=(1, 1))
observer = DEFAULT_OBSERVER_REGISTRY.observers[binding]
with evidence.open() as lines:
expected = observer.observe_lines(
lines, evidence_path=str(evidence), workflow_status="running"
).model_dump(mode="json")
with mock.patch.object(
observer, "observe_lines", wraps=observer.observe_lines
) as parse:
observation = mcp_runs.observe_registry_run(record.id)
self.assertEqual(observation["execution"], expected)
self.assertEqual(observation["warnings"], [])
self.assertEqual(observation["log_tail"], log.read_bytes()[-12_000:].decode())
self.assertEqual(
mcp_runs.get_registry_run(record.id)["warnings"], ["Plan warning"]
)
parse.assert_called_once()
parse.reset_mock()
self.stream_transport.reset_mock()
mcp_runs.list_registry_runs()
parse.assert_not_called()
self.stream_transport.assert_not_called()
log.unlink()
observation = mcp_runs.observe_registry_run(record.id)
self.assertEqual(len(observation["warnings"]), 1)
self.assertEqual(observation["execution"], expected)
if target == "lab":
self.assertFalse((Path(record.run_dir) / "results").exists())
self.assertFalse(Path(record.launch_log_path).exists())
def test_run_detail_never_observes_the_daemon_or_ssh(self) -> None:
for status in ("running", "completed"):
with self.subTest(status=status):
record, _ = self._register(status=status)
with (
mock.patch.object(
mcp_runs.daemon_client,
"observe",
side_effect=AssertionError("detail observed lifecycle"),
),
mock.patch.object(
mcp_runs.daemon_client,
"observe_execution",
side_effect=AssertionError("detail observed evidence"),
),
mock.patch.object(
mcp_runs.daemon_client,
"ensure_daemon_running",
side_effect=AssertionError("detail started daemon"),
),
):
detail = mcp_runs.get_registry_run(record.id)
self.assertEqual(detail["status"], status)
self.transport.assert_not_called()
def test_remote_completed_report_is_not_projected_or_read_over_ssh(self) -> None:
record, _ = self._register(status="completed")
report = mcp_runs.get_registry_run_report(record.id)
self.assertEqual(report["availability"], "remote_results_not_projected")
self.assertIsNone(report["report"])
self.transport.assert_not_called()
def test_remote_trace_counts_are_complete_and_independent_of_controller_log(self) -> None:
record, log = self._register(warnings=["Plan warning"])
trace = log.parent.parent / "workflow/trace.txt"
trace.parent.mkdir()
trace.write_text(
"task_id\tname\tstatus\n" + "".join(f"{index}\tqc\tCOMPLETED\n" for index in range(240))
)
log.unlink()
observation = mcp_runs.observe_registry_run(record.id)
self.assertEqual(len(observation["warnings"]), 1)
self.assertEqual(mcp_runs.get_registry_run(record.id)["warnings"], ["Plan warning"])
self.assertEqual(observation["execution"]["counts"]["completed"], 240)
self.assertEqual(len(observation["execution"]["attempts"]), 200)
self.assertTrue(observation["execution"]["attempts_truncated"])
trace.unlink()
log.write_text("controller still readable\n")
observation = mcp_runs.observe_registry_run(record.id)
self.assertEqual(observation["log_tail"], log.read_text())
self.assertEqual(len(observation["warnings"]), 1)
self.assertFalse(observation["execution"]["evidence"]["available"])
def test_large_remote_results_complete_without_copyback(self) -> None:
for binding in ("nextflow", "snakemake"):
with self.subTest(binding=binding):
record, log = self._register(binding=binding, status="running")
root = log.parent.parent
result = root / "results" / "large.bin"
result.parent.mkdir()
with result.open("wb") as handle:
handle.truncate(65 * 1024 * 1024)
evidence = root / (
"workflow/trace.txt"
if binding == "nextflow"
else "results/.snakemake/log/run.snakemake.log"
)
evidence.parent.mkdir(parents=True, exist_ok=True)
evidence.write_text(
"name\tstatus\nqc\tCOMPLETED\n"
if binding == "nextflow"
else "Finished jobid: 1 (Rule: qc)\n"
)
owned = self.server.runs._runs.pop(record.id)
with (
mock.patch.object(
ssh,
"status",
side_effect=[
ssh.TransportError("offline"),
{"running": False, "return_code": 0},
],
),
mock.patch.object(runs.time, "sleep"),
):
self.server.runs._finish_remote(owned)
detail = mcp_runs.get_registry_run(record.id)
observation = mcp_runs.observe_registry_run(record.id)
self.assertEqual(detail["status"], "completed")
self.assertEqual(detail["run_dir"], str(root))
self.assertEqual(observation["execution"]["evidence"]["path"], str(evidence))
self.assertEqual(observation["execution"]["counts"]["completed"], 1)
self.assertEqual(observation["log_tail"], log.read_bytes()[-12_000:].decode())
self.assertFalse((Path(record.run_dir) / "results").exists())
self.assertFalse(Path(record.launch_log_path).exists())
self.assertEqual(result.stat().st_size, 65 * 1024 * 1024)
local = Path(record.run_dir)
(local / "results").mkdir()
(local / "results/stale.txt").write_text("obsolete copied result")
historical = mcp_runs.get_registry_run(record.id)
self.assertNotIn("result", historical)
self.assertNotIn("artifacts", historical)
summary = next(
item
for item in mcp_runs.list_registry_runs()["runs"]
if item["registry_run_id"] == record.id
)
self.assertEqual(summary["run_dir"], str(root))
self.assertEqual(mcp_runs.cancel_registry_run(record.id)["run_dir"], str(root))
Path(record.approved_plan_path).unlink()
unavailable = mcp_runs.get_registry_run(record.id)
self.assertEqual(unavailable["run_dir"], str(root))
self.assertNotIn("config_hash", unavailable["target"])
self.assertTrue(unavailable["warnings"])
self.assertEqual(unavailable["status"], "completed")
def test_stream_validates_completion_and_reaps_on_abort(self) -> None:
record, log = self._register()
log.write_text("first line\n" + "remaining output\n" * 65_536)
remote = runs._approved_plan(record)["request"]["remote"]
original = self.stream_transport.side_effect
processes = []
def track(*args, **kwargs):
process = original(*args, **kwargs)
processes.append(process)
return process
with mock.patch.object(ssh.subprocess, "Popen", side_effect=track):
for read_all in (False, True):
with self.subTest(read_all=read_all):
with ssh.stream_file(remote, "logs/nextflow.log") as lines:
self.assertEqual(
lines.read() if read_all else next(lines),
log.read_text() if read_all else "first line\n",
)
self.assertEqual(processes[-1].returncode, 0)
for failure, read_all in (
("consumer_error", False),
("deadline", False),
("failed_exit", False),
("deadline", True),
("failed_exit", True),
):
program = (
"import json, sys, time\n"
"def execute(request, controller):\n"
" sys.stdout.write('first line\\n'); sys.stdout.flush()\n"
+ (" sys.exit(7)\n" if failure == "failed_exit" else " time.sleep(30)\n")
)
with (
self.subTest(failure=failure, read_all=read_all),
mock.patch.object(ssh, "_REMOTE_PROGRAM", program),
):
if failure == "consumer_error":
error = RuntimeError("parser stopped")
with self.assertRaises(RuntimeError) as raised:
with ssh.stream_file(remote, "logs/nextflow.log") as lines:
self.assertEqual(next(lines), "first line\n")
raise error
self.assertIs(raised.exception, error)
else:
with self.assertRaises(ssh.TransportError):
with ssh.stream_file(remote, "logs/nextflow.log", timeout=0.5) as lines:
if read_all:
lines.read()
else:
self.assertEqual(next(lines), "first line\n")
self.assertIsNotNone(processes[-1].poll())
def test_malformed_snakemake_evidence_preserves_controller_diagnostics(self) -> None:
for target in ("local", "lab"):
record, log = self._register(binding="snakemake", target=target)
log.write_bytes(b"non-UTF8 tool output: \xff\nuseful final diagnostic\n")
native = log.parent.parent / "results/.snakemake/log/run.snakemake.log"
native.parent.mkdir(parents=True)
for content in (b"\xff\n", b"9" * 5000 + b" of 1 steps done\n"):
with self.subTest(target=target, content=content[:10]):
native.write_bytes(content)
detail = mcp_runs.observe_registry_run(record.id)
self.assertEqual(detail["log_tail"], log.read_bytes().decode(errors="replace"))
self.assertEqual(len(detail["warnings"]), 1)
self.assertIn("Execution evidence unavailable", detail["warnings"][0])
self.assertFalse(detail["execution"]["evidence"]["available"])
def test_remote_only_controller_log_reaches_run_detail_without_copyback(self) -> None:
for binding in ("nextflow", "snakemake"):
for status in ("running", "completed", "failed", "canceled", "orphaned"):
with self.subTest(binding=binding, status=status):
record, log = self._register(binding=binding, status=status)
detail = mcp_runs.observe_registry_run(record.id)
self.assertEqual(detail["log_tail"], log.read_bytes()[-12_000:].decode())
self.assertEqual(
(detail["status"], detail["revision"]), (status, record.revision)
)
self.assertFalse(Path(record.launch_log_path).exists())
self.assertFalse(detail["execution"]["evidence"]["available"])
self.transport.reset_mock()
mcp_runs.list_registry_runs(statuses=["completed"])
self.transport.assert_not_called()
def test_local_controller_log_remains_readable_without_its_approved_plan(self) -> None:
record, log = self._register(target="local")
Path(record.approved_plan_path).unlink()
detail = mcp_runs.observe_registry_run(record.id)
self.assertEqual(detail["log_tail"], log.read_bytes()[-12_000:].decode())
self.transport.assert_not_called()
def test_evidence_errors_preserve_lifecycle_and_never_fall_back_to_stale_local_logs(
self,
) -> None:
record, log = self._register()
local = Path(record.launch_log_path)
local.parent.mkdir(parents=True)
local.write_text("stale collected log")
audit = Path(record.approved_plan_path)
original = audit.read_text()
for failure in ("checksum", "target_drift", "unreachable", "missing"):
with self.subTest(failure=failure):
audit.write_text(original)
self.transport.reset_mock()
with mock.patch.object(ssh, "effective_access", return_value=self.access) as access:
if failure == "checksum":
plan = json.loads(original)
plan["effects"]["launch_log"] += ".changed"
audit.write_text(json.dumps(plan))
elif failure == "target_drift":
access.return_value = {"alias": "different-host"}
elif failure == "unreachable":
access.side_effect = subprocess.TimeoutExpired("ssh -G", 5)
else:
log.unlink()
detail = mcp_runs.observe_registry_run(record.id)
self.assertIsNone(detail["log_tail"])
self.assertTrue(detail["warnings"])
self.assertEqual(
(detail["status"], detail["revision"]), ("completed", record.revision)
)
if failure != "missing":
self.transport.assert_not_called()
def test_blocked_evidence_does_not_block_cancellation(self) -> None:
record, _ = self._register(status="running")
entered, release = threading.Event(), threading.Event()
def blocked_tail(*args):
entered.set()
release.wait(5)
raise ssh.TransportError("unreachable")
with (
mock.patch.object(ssh, "tail_file", side_effect=blocked_tail),
mock.patch.object(ssh, "stop") as stop,
):
observer = threading.Thread(target=client.observe_execution, args=(record.id,))
observer.start()
try:
self.assertTrue(entered.wait(2))
canceled = client.request(
"/cancel",
{"registry_run_id": record.id},
timeout=1,
)
self.assertEqual(canceled["status"], "canceling")
stop.assert_called_once()
finally:
release.set()
observer.join(2)
self.assertEqual(runs._run_record(record.id).status, "canceling")
def test_cancel_completed_run_is_noop(self) -> None:
record, _ = self._register()
canceled = mcp_runs.cancel_registry_run(record.id)
self.assertEqual(
(canceled["status"], canceled["registry_run_id"]), ("completed", record.id)
)
self.transport.assert_not_called()
def test_tail_rejects_paths_outside_the_run_symlinks_and_special_files(self) -> None:
record, log = self._register()
remote = runs._approved_plan(record)["request"]["remote"]
root = Path(remote["run_dir"])
(root / "link").symlink_to(log)
os.mkfifo(root / "pipe")
for relative in ("../outside", str(self.root / "outside"), "link", "pipe", "logs"):
with self.subTest(relative=relative), self.assertRaises(ValueError):
ssh.tail_file(remote, relative)
with self.subTest(stream=relative), self.assertRaises(ValueError):
with ssh.stream_file(remote, relative) as lines:
lines.read()
for pattern in ("../*", "link", str(self.root / "*")):
with self.subTest(selection=pattern), self.assertRaises(ValueError):
ssh.select_file(remote, (pattern,))
def test_compute_client_cannot_read_workflow_evidence(self) -> None:
record, _ = self._register()
with self.assertRaises(client.DaemonError):
client.request("/run/evidence", {"registry_run_id": record.id}, role="compute")
self.transport.assert_not_called()
def test_evidence_lookup_requires_a_string_id_but_ignores_extra_fields(self) -> None:
record, log = self._register(target="local")
observation = client.request(
"/run/evidence", {"registry_run_id": record.id, "unused": True}
)
self.assertEqual(observation["log_tail"], log.read_bytes()[-12_000:].decode())
for payload in ({}, {"registry_run_id": []}):
with self.subTest(payload=payload), self.assertRaises(client.DaemonError):
client.request("/run/evidence", payload)
class SshInspectionTests(DaemonTestCase):
def test_ssh_inspection_uses_only_the_fixed_remote_program(self) -> None:
nextflow = self.workspace / "nextflow"
nextflow.write_text('#!/bin/sh\nprintf "nextflow %s\\n" "$NXF_OFFLINE"\n')
nextflow.chmod(0o755)
payload = {
"workspace_root": str(self.workspace),
"executor": "slurm",
"executable_paths": [sys.executable],
}
completed = subprocess.run(
[sys.executable, str(Path(remote_probe.__file__)), json.dumps(payload)],
capture_output=True,
text=True,
env={**os.environ, "PATH": str(self.workspace)},
check=True,
)
facts = json.loads(completed.stdout)
commands = {item["executable"]: item for item in facts["commands"]}
self.assertEqual(commands["nextflow"]["version"], "nextflow true")
self.assertEqual(commands[sys.executable]["state"], "ready")
self.assertEqual(facts["workspace"]["path"], str(self.workspace))
self.assertEqual(facts["scheduler"]["worker_readiness"], "unknown")
self.assertEqual(facts["scheduler"]["shared_filesystem"], "unknown")
target = self._configure_ssh_target()
request = TargetInspection(
target_id="lab", executable_paths=["/remote/bin/fastqc", "sbatch"]
)
forged = subprocess.CompletedProcess(
[], 0, json.dumps({"target_id": "forged", "config_hash": "forged"})
)
with mock.patch.object(inspection.subprocess, "run", return_value=forged):
observed = inspection.inspect_target(target, request)
self.assertEqual(
(observed["target_id"], observed["config_hash"]),
("lab", target["config_hash"]),
)
with (
mock.patch.object(server, "_list_targets", return_value={"targets": [target]}),
mock.patch.object(
inspection,
"effective_access",
return_value={**target["host_access"], "port": 2222},
),
mock.patch.object(inspection, "inspect_target") as inspect_changed,
):
with self.assertRaisesRegex(ValueError, "SSH access has changed"):
server._inspect_target(request)
inspect_changed.assert_not_called()
class SshTransportTests(unittest.TestCase):
def test_staged_inputs_are_verified(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
workspace = Path(temporary).resolve()
run_dir = workspace / "run"
(run_dir / "workflow").mkdir(parents=True)
(run_dir / "workflow" / "approved_plan.json").write_text("{}")
source = workspace / "input.txt"
content = b"approved workflow input\n"
source.write_bytes(content)
checksum = f"sha256:{hashlib.sha256(content).hexdigest()}"
remote = {
"run_dir": "/remote/run",
"workspace_root": "/remote",
"host_access": {"alias": "lab"},
"staged_files": [
{
"source": str(source),
"destination": "/remote/run/input.txt",
"bytes": len(content),
"sha256": checksum,
}
],
}
with mock.patch.object(ssh, "_invoke"), mock.patch.object(ssh, "_upload") as upload:
ssh.stage(
{"request": {"remote": remote}},
run_dir,
checkpoint=lambda: None,
transfer=lambda _process: None,
)
upload.assert_called_once()
def test_transfer_timeout_scales_with_approved_file_size(self) -> None:
access = {"alias": "lab"}
process = mock.MagicMock(returncode=0)
process.communicate.return_value = (None, "")
with (
mock.patch.object(ssh, "effective_access", return_value=access),
mock.patch.object(ssh.subprocess, "Popen", return_value=process),
):
ssh._upload(
{"host_access": access},
"source",
"destination",
expected_bytes=5 * 1024**3,
)
process.communicate.assert_called_once_with(timeout=120 + 5 * 1024)
def test_fixed_remote_program_verifies_staged_files(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
workspace = Path(temporary).resolve()
root = workspace / "run"
content = "approved input\n"
destination = str(root / "results" / "result.txt")
request = {
"workspace_root": str(workspace),
"run_dir": str(root),
"operation": "prepare",
"destinations": [],
"generated": [{"destination": destination, "content": content}],
}
prepared = subprocess.run(
[sys.executable, str(Path(remote_operations.__file__)), json.dumps(request)],
capture_output=True,
text=True,
check=True,
)
self.assertEqual(json.loads(prepared.stdout), {"ok": True})
self.assertEqual(root.stat().st_mode & 0o777, 0o700)
checksum = f"sha256:{hashlib.sha256(content.encode()).hexdigest()}"
files = [{"destination": destination, "bytes": len(content), "sha256": checksum}]
request.update(operation="verify", files=files)
self.assertEqual(remote_operations.execute(request, ""), {"ok": True})
Path(destination).write_text("changed", encoding="utf-8")
request.update(operation="verify", files=files)
with self.assertRaises(ValueError):
remote_operations.execute(request, "")
def test_remote_cancellation_retries_after_transport_failure(self) -> None:
remote: dict[str, object] = {}
identity = {"pid": 42}
owned = runs.OwnedRun(
request=ExecutionRequest(
binding="nextflow",
plan_checksum="sha256:" + "0" * 64,
plan={"request": {"remote": remote}},
),
registry_run_id="remote-run",
remote_identity=identity,
canceled=True,
)
with (
mock.patch.object(
ssh, "stop", side_effect=(ssh.TransportError("offline"), None)
) as stop,
mock.patch.object(ssh, "status", return_value={"running": False, "return_code": -15}),
mock.patch.object(runs, "_transition") as transition,
mock.patch.object(runs.time, "sleep"),
):
runs.RunManager._finish_remote(owned)
self.assertEqual(stop.call_count, 2)
self.assertTrue(owned.controller_signaled)
transition.assert_called_once_with("remote-run", "canceled", pid=42)
def test_controller_script_writes_its_actual_exit_receipt(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
request = {
"argv": [sys.executable, "-c", "print('completed')"],
"run_dir": str(root),
"log": str(root / "controller.log"),
"exit": str(root / "controller.exit"),
}
subprocess.run(
[sys.executable, str(Path(remote_controller.__file__)), json.dumps(request)],
check=True,
)
self.assertEqual((root / "controller.exit").read_text(), "0")
self.assertEqual((root / "controller.log").read_text().strip(), "completed")
SHA-256: 0004bc42163e4cd7da473c7838ead8297e67a2cb1fa7ee867872c4a3ff19e544