| """Pytest invocation inside the sandbox + JSON-report parsing. |
| |
| Kept separate from `runner.py` so the Docker orchestration and the |
| pytest-harness concerns don't tangle. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import json |
| from pathlib import Path |
| from typing import Any |
|
|
| from .resource_limits import REPORT_FILENAME, REPORT_PATH_IN_CONTAINER |
|
|
|
|
| def pytest_command() -> list[str]: |
| """Pytest argv used inside the sandbox container. |
| |
| `-p no:cacheprovider` stops pytest from writing `.pytest_cache/` into the |
| bind-mounted /work as the sandbox uid, which would be un-deletable by the |
| host on Linux (CI, CHTC). |
| """ |
| return [ |
| "pytest", |
| "-q", |
| "-p", |
| "no:cacheprovider", |
| "--json-report", |
| f"--json-report-file={REPORT_PATH_IN_CONTAINER}", |
| "test_solution.py", |
| ] |
|
|
|
|
| def parse_report(workdir: Path) -> tuple[int, int]: |
| """Read the JSON report from the host side of the bind mount. |
| |
| Returns (0, 0) if the report is missing or malformed — typical when the |
| container was killed before pytest could flush. |
| """ |
| report = workdir / REPORT_FILENAME |
| if not report.exists(): |
| return 0, 0 |
| try: |
| summary = json.loads(report.read_text()).get("summary", {}) |
| return int(summary.get("passed", 0)), int(summary.get("total", 0)) |
| except Exception: |
| return 0, 0 |
|
|
|
|
| def was_oom_killed(container: Any) -> bool: |
| """True if the kernel OOM-killed the container due to mem_limit.""" |
| try: |
| container.reload() |
| return bool(container.attrs.get("State", {}).get("OOMKilled", False)) |
| except Exception: |
| return False |
|
|