2026-09-29 18:11:24 +02:00
|
|
|
"""A stub task module for the runner tests: records its contract, writes one artifact and ``summary.json``.
|
|
|
|
|
|
2026-09-29 19:04:52 +02:00
|
|
|
The tests copy it into a throwaway package (``AX_RUNNER_TASK_PACKAGE``) so the runner starts it the way it starts
|
|
|
|
|
a real task, ``python -m <package>.stub_task``. ``STUB_MODE`` picks the outcome (``done``, ``failed``,
|
|
|
|
|
``unfinished``, ``crash``, ``nosummary``, ``exit``, ``sleep``); ``STUB_COUNTER`` names a file that gets one line
|
|
|
|
|
per run.
|
2026-09-29 18:11:24 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import os
|
2026-09-29 19:04:52 +02:00
|
|
|
import signal
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
2026-09-29 18:11:24 +02:00
|
|
|
import urllib.request
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
2026-09-29 19:04:52 +02:00
|
|
|
def _get(url: str) -> tuple[int, str]:
|
|
|
|
|
with urllib.request.urlopen(url, timeout=5) as response:
|
|
|
|
|
return int(response.status), response.read().decode("utf-8")
|
|
|
|
|
|
|
|
|
|
|
2026-09-29 18:11:24 +02:00
|
|
|
def main(argv: list[str]) -> None:
|
|
|
|
|
out = Path(os.environ["OUSAST_OUTPUT_DIR"])
|
|
|
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
mode = os.environ.get("STUB_MODE", "done")
|
2026-09-29 19:04:52 +02:00
|
|
|
counter = os.environ.get("STUB_COUNTER")
|
|
|
|
|
if counter:
|
|
|
|
|
with open(counter, "a", encoding="utf-8") as handle:
|
|
|
|
|
handle.write(f"{os.getpid()}\n")
|
2026-09-29 18:11:24 +02:00
|
|
|
workspace = Path(os.environ["OUSAST_WORKSPACE_DIR"])
|
|
|
|
|
port = os.environ.get("AX_RUNNER_BOUND_PORT")
|
2026-09-29 19:04:52 +02:00
|
|
|
readyz = _get(f"http://127.0.0.1:{port}/readyz")[0] if port else None
|
|
|
|
|
metadata = os.environ.get("AX_METADATA_URL")
|
2026-09-29 18:11:24 +02:00
|
|
|
record = {
|
|
|
|
|
"argv": argv,
|
|
|
|
|
"env": {k: v for k, v in os.environ.items() if k.startswith("OUSAST_")},
|
|
|
|
|
"workspace_files": sorted(p.relative_to(workspace).as_posix() for p in workspace.rglob("*") if p.is_file()),
|
|
|
|
|
"readyz": readyz,
|
2026-09-29 19:04:52 +02:00
|
|
|
"cwd": os.getcwd(),
|
|
|
|
|
"pid": os.getpid(),
|
|
|
|
|
"pgid": os.getpgid(0),
|
|
|
|
|
"metadata_url": metadata,
|
|
|
|
|
"metadata_task": _get(f"{metadata}/metadata/v1alpha1/ax/task")[1] if metadata else None,
|
2026-09-29 18:11:24 +02:00
|
|
|
}
|
|
|
|
|
(out / "facts.json").write_text(json.dumps(record, indent=2, sort_keys=True), encoding="utf-8")
|
2026-09-29 19:04:52 +02:00
|
|
|
if mode == "sleep": # ignore SIGTERM so only the runner's SIGKILL ends the group; the grandchild inherits it
|
|
|
|
|
signal.signal(signal.SIGTERM, signal.SIG_IGN)
|
|
|
|
|
grandchild = subprocess.Popen(["sleep", "60"])
|
|
|
|
|
(out / "pids.json").write_text(json.dumps([os.getpid(), grandchild.pid]), encoding="utf-8")
|
|
|
|
|
time.sleep(60)
|
2026-09-29 18:11:24 +02:00
|
|
|
if mode == "crash":
|
|
|
|
|
raise RuntimeError("stub exploded")
|
|
|
|
|
if mode == "exit":
|
|
|
|
|
raise SystemExit(7)
|
|
|
|
|
if mode == "nosummary":
|
|
|
|
|
return
|
|
|
|
|
summary = {"status": mode, "units_done": 1, "units_total": 1, "usd": 0.0, "calls": 0, "usage": {}}
|
|
|
|
|
(out / "summary.json").write_text(json.dumps(summary), encoding="utf-8")
|
2026-09-29 19:04:52 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main(sys.argv[1:])
|