|
| Any | load_parallel_module (Path|None workspace_root=None) |
| Path | manifest_path_for (Path root, Sequence[str] testpaths, Path|None workspace_root=None) |
| tuple[dict[str, str], tuple[str,...]] | load_groups (Path manifest_path, Iterable[str] relpaths, Path|None workspace_root=None) |
| WorkerPlan | resolve_worker_plan (int|str requested, Path workspace_root, str package, *, int|None cpu_count=None) |
| dict | clamp_error (WorkerPlan plan) |
| tuple[ScheduleUnit,...] | build_schedule (Mapping[str, str] groups, Mapping[str, Sequence[str]] nodeids_by_file, *, Iterable[str] whole_file=(), int|None max_free_unit_tests=None) |
| None | child_entry (ChildRequest request, str result_path) |
| Any | _spawn (ScheduleUnit unit, *, Path root, Sequence[str] pytest_args, int|None timeout, Path result_path) |
| UnitOutcome | _read_result (ScheduleUnit unit, Any process, Path result_path, float started, float finished) |
| tuple[list[UnitOutcome], bool] | run_units (Sequence[ScheduleUnit] units, *, int workers, Path root, Sequence[str] pytest_args, int|None timeout, float origin) |
| dict | crash_violation (UnitOutcome outcome) |
| dict | crash_error (UnitOutcome outcome) |
| dict | unclassified_violation (Path root, Sequence[str] missing) |
The §20 stand-in's parallel scheduler (Plan 240; ADR-T-15 slice).
Purpose
-------
Turn ``--workers N | auto`` into real child processes without inventing
a second classifier. The plan-239 mechanism in ``oct.testing.parallel``
owns the resource vocabulary, the committed
``parallel_manifest.json``, the grouping and the worker cap; this
module imports all four and does exactly three things the pytest side
does not need: it resolves the requested worker count against the cap,
it turns the manifest's groups into scheduling units, and it runs those
units in a bounded pool of children, reconciling their results into the
single §11 report.
Responsibilities
----------------
- ``resolve_worker_plan`` — P2: ``auto`` = min(cap, cores); an explicit
count above the cap CLAMPS and carries the note that says so.
- ``build_schedule`` — P3 as Plan 286 amended it: a group is one unit;
a ``free`` file is cut, in collection order, into units of at most
``MAX_FREE_UNIT_TESTS`` tests, unless it carries the ``whole_file``
opt-out; units run largest first by collected count; unclassified
files share one serial unit. Deterministic for any input order.
- ``load_groups`` — the manifest read, delegated to plan 239.
- ``run_units`` — the bounded pool: one spawned child per unit, each
with its own §20.2 snapshot, reconciled fail-closed.
- ``child_entry`` — the child-side entry point (module level so the
``spawn`` start method can import it).
Diagnostics
-----------
Domain: OC_TESTING
Levels:
L2 — lifecycle events (pool start/stop, unit completion)
L3 — semantic details (resolved counts, unit shapes)
L4 — deep tracing
Contracts
---------
- This module NEVER classifies a test file. Every group it uses comes
from the committed manifest through ``oct.testing.parallel``; a file
with no entry is :data:`GROUP_UNCLASSIFIED` and is reported, never
guessed at (P1).
- A child that dies without writing its result is a CRASH: its nodeids
are reported failed, never passed and never dropped (P3, spec-10
§8.4 worker-crash semantics).
- The start method is always ``spawn``, so a child inherits the
parent's ``sys.path`` (the F17 shadow guard) and nothing else of the
parent's in-process state.
- Ordering of everything this module returns is derived from the
schedule, not from completion order.
Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oct.testing.parallel (lazy; Plan 239: manifest, groups, worker cap)
- oc_chat.testing.report (violation/error constructors)
- oc_chat.testing.runner (lazy; the in-process session, imported in the child to keep the module graph acyclic)
| tuple[ScheduleUnit, ...] oc_chat.testing.scheduler.build_schedule |
( |
Mapping[str, str] | groups, |
|
|
Mapping[str, Sequence[str]] | nodeids_by_file, |
|
|
* | , |
|
|
Iterable[str] | whole_file = (), |
|
|
int | None | max_free_unit_tests = None ) |
Turn the plan-239 grouping into scheduling units (P3, Plan 286).
A group is the unit of scheduling: every file of a group runs in
ONE child, because that is what the group means, and the
unclassified set shares one serial unit because fail-closed means
serial.
A ``free`` file shares no resource with anything, so its tests may
run anywhere — including in different children (Plan 286 P2). Its
nodeids are therefore cut, in collection order, into units of at
most *max_free_unit_tests* tests, so that one file with hundreds of
tests no longer holds a child for its whole length. A file listed
in *whole_file* is the opt-out: it stays ONE unit however many
tests it has.
Units are scheduled largest first by collected count (longest
processing time first — the classic bound for a bounded pool) and
the result is deterministic for any input order.
Parameters
----------
groups
Root-relative path -> plan-239 group name.
nodeids_by_file
Root-relative path -> the parent's selection, in collection
order.
whole_file
Root-relative paths whose tests must stay in one unit: the
files the parent's collect pass saw carrying
:data:`WHOLE_FILE_MARKER`.
max_free_unit_tests
Override for :data:`MAX_FREE_UNIT_TESTS`; values below 1 are
read as 1, so a caller can never ask for an empty unit.
Definition at line 330 of file scheduler.py.