|
| str | _outcome_for (str nodeid, dict rec, set[str] timed_out) |
| SessionOutcome | run_session (*, Sequence[str] pytest_args, Path root, int|None timeout, bool dry_run, _SignalState|None sig_state=None, bool install_signal_handlers=True, str|None violation_file=None, str violation_scope="") |
| dict | _build_report (*, str run_id, str status, int exit_code, str|None workspace_root, str|None workspace_mode, list[str] packages, str|None profile, str|None marker, int|None seed, list[str] nodeids, int|str|None workers_requested, int|None workers_actual, str|None dist_mode, int collected, list[dict] tests, list[dict] violations, list[dict] errors, float duration_seconds, dict|None scheduling=None) |
| dict[str, str] | _features_for (str|None dist_mode) |
| dict | serial_scheduling (str|None reason) |
| RunResult | _pre_execution_result (*, str run_id, float t0, list[dict] errors, StandInConfig|None cfg, WalkResult|None walk, str failed_step) |
| RunResult | run_standin (list[str] argv, Path|None cwd=None) |
| RunResult | _run (list[str] argv, Path base_cwd, str run_id, float t0) |
| list[dict] | _tests_from_units (Sequence[UnitOutcome] outcomes) |
| dict | _parallel_scheduling (*, Path manifest_path, Path root, WorkerPlan plan, Sequence[UnitOutcome] outcomes, Sequence[str] missing) |
The §20 stand-in orchestrator: serial in-process execution (Plan 184).
Purpose
-------
Drive one stand-in invocation end to end: argv parse → ADR-T-12
discovery → §10 undeclared-root scan → profile/marker/seed composition
→ serial IN-PROCESS ``pytest.main`` with a collector plugin → §20.2
process-level CWD/env restore checks → §11 report assembly → §4 exit
mapping. In-process execution is the pinned decision: only it lets the
post-run ``os.environ``/CWD diff observe anything.
Responsibilities
----------------
- ``run_standin`` — the runner API; returns report + exit code + the
API-level execution-order witness (the report's ``tests`` array is
nodeid-sorted by contract, so order lives here).
- ``_Collector`` — the pytest plugin: collection accounting, static
scan before execution, dry-run/violation short-circuit, per-test
watchdog, outcome aggregation, collection-error capture.
- ``_SignalState`` — the ADR-T-08 subset for a serial in-process run.
Signal-handling subset (ADR-T-08, stated per plan T3)
-----------------------------------------------------
First ``SIGINT``/``SIGTERM`` (plus ``SIGBREAK`` on Windows): there are
no worker processes to TERM and nothing to wait ten seconds for, so the
stand-in unwinds the in-process pytest session, writes the partial
report with ``status: "orchestrator_interrupted"`` and the
``ORCHESTRATOR_INTERRUPTED`` signal entry, and exits 3. A second signal
hard-exits 130 with no report (the report-less second-signal wording
carries a known erratum upstream, B_04 P2 — out of scope here).
Handlers install only when running on the main thread.
Per-test-timeout deviation (ADR-T-07/§16.1, stated per plan T3)
---------------------------------------------------------------
The spec pins ``pytest-timeout`` as the serial stand-in's mechanism,
but its only Windows-viable method (``thread``) terminates the ENTIRE
process on expiry — for an in-process stand-in that would kill the
orchestrator and make the §20 surviving-report contract (timeout
outcome + duration + exit 1) unsatisfiable. The stand-in therefore
implements the ADR's intent with a watchdog timer that soft-interrupts
the hung test via ``PyThreadState_SetAsyncExc`` (the run continues;
the nodeid reports ``outcome: "timeout"`` with its elapsed duration).
``pytest-timeout`` remains the installed, pinned dependency and the
v1.0 in-worker backstop. Residual: a test blocked inside a C call is
interrupted only when that call returns — the same class of residual
pytest-timeout's signal method carries.
Diagnostics
-----------
Domain: OC_TESTING
Levels:
L2 — lifecycle events
L3 — semantic details
L4 — deep tracing
Contracts
---------
- The restore checks consume :data:`TRACKED_ENV_PREFIXES` from its
single export (§20.2) — object identity, no copy.
- The runner restores its own CWD and the recursion-guard variable
before diffing, so only TEST leaks are visible.
- ``report["exit_code"]`` always equals the returned exit code.
Dependencies
------------
- pytest (in-process engine)
- oc_diagnostics (structured debug logging via _dbg)
- oc_chat.testing.cli (argv contract)
- oc_chat.testing.package_walk (discovery + root scan)
- oc_chat.testing.static_scan (static AST guard)
- oc_chat.testing.report (report constants + exit mapping)
- oc_chat.testing.env_inventory (tracked prefixes)
- oc_chat.testing.scheduler (the parallel path - worker plan, unit schedule, bounded child pool and crash reconciliation)
- _pytest.mark.expression (lazy; Expression - validates the composed marker expression before the run)
- pytest_randomly (not imported; the seed-pinning plugin loaded with -p randomly when --seed is given, disabled with -p no:randomly otherwise)
| SessionOutcome oc_chat.testing.runner.run_session |
( |
* | , |
|
|
Sequence[str] | pytest_args, |
|
|
Path | root, |
|
|
int | None | timeout, |
|
|
bool | dry_run, |
|
|
_SignalState | None | sig_state = None, |
|
|
bool | install_signal_handlers = True, |
|
|
str | None | violation_file = None, |
|
|
str | violation_scope = "" ) |
Run ONE guarded in-process pytest session (§20.2).
Everything the §20.2 process-level contract needs happens here and
only here: the recursion guard, the CWD move and its restoration,
the tracked-environment snapshot taken around the run, the static
gate, the per-test watchdog and the leak diff. The serial stand-in
calls it once; the scheduler calls it once per child, in that
child's own process, which is what makes the §20.2 findings
attributable to a group at all.
Parameters
----------
violation_file
The ``file`` field of any runtime finding; defaults to *root*.
violation_scope
Prefixed to a finding's message when non-empty, so a child's
leak names the worker and group that produced it.
Definition at line 356 of file runner.py.
| dict oc_chat.testing.runner.serial_scheduling |
( |
str | None | reason | ) |
|
The ``scheduling`` block of a run that distributed nothing.
*reason* records WHY, so a run that could have parallelised and did
not is never silent about it: ``workers_1``, ``dry_run``,
``no_manifest``, ``single_worker``, ``single_unit``,
``classifier_unavailable`` or ``worker_cap_unusable``.
Definition at line 581 of file runner.py.