interface RunState

Mutable state for one test run, kept separate from the resolved settings on Config.

Organized by sharing lifetime, and that organization is the invariant to preserve:

  • Every field except group is shared by reference across all concurrent groups. Groups are spread off the parent with a shallow {...config}, which copies state by reference. So the counter, failure sets, coverage collector and reporters are one set of objects for the whole run — which is what makes TAP numbering globally sequential and the coverage report whole.
  • group is replaced per group. It is the only place a per-group slot may live; anything added elsewhere silently becomes shared.

The consequence for shared fields is that they must be mutated in place, never reassigned — see RunState.reset. Assigning a fresh object on one config detaches it from the others and splits the run's totals, which no type can catch. Fields of a shared object may be reassigned freely (results.coverage = new Map()); it is the object itself that must survive.

import * as RunState from './setup/run-state.ts';

const state = RunState.create();
state.results.counter.total; // 0 — one shared accumulator for the whole run

Properties

Whole-run accumulators, shared by reference across every concurrent group.

Active reporter instances for this run, built by Reporters.create in Config.setup. One set for the whole run, so a stateful reporter sees every group rather than one slice.

Where this run's text goes: the TAP document, every reporter line, every # diagnostic and every forwarded page log. processConsole for the CLI, silentConsole for a programmatic run that only wants the result value. Shared by reference across concurrent groups.

Named console, not output: config.output is the build DIRECTORY, and the two were one word apart on the same object graph.

Cancels this run when it fires, or absent when nothing can. Carried here rather than threaded alongside the config because it is per-run input like everything else in state — and it is what lets a verb reach it from the resolved config instead of re-reading the raw arguments.

Only carried: Config.setup never subscribes. Whoever wires a listener has to unwire it, and setup has no teardown, so a shared controller would accumulate one listener per run.

Non-null exactly when this run is executing inside the persistent daemon process — it is the daemon-mode flag as well as the handles. Daemon runs reuse the shared browser, suppress the per-connection TAP header, and leave that browser open at the end of the run.

groupCount: number

Number of concurrent groups in this run; 1 for watch and single-group runs.

How this run's files were split across those groups — the descriptive record RunResult reports. Assigned once per run alongside groupCount and shared by reference, so a group can read the whole split rather than only its own slice.

Empty until a split is computed, which is why groupCount stays the authority the runner branches on: page reuse is decided during daemon setup, before any grouping exists. Watch never splits at all, so it leaves this empty and the result names its one group from the files that rerun ran.

aborters: Set<() => void>

One callback per live server that tells its connected pages to drop the rest of the QUnit queue — what qq, session.abort() and an aborted signal all go through.

A set rather than a slot because a concurrent run may have one server per group, and aborting half a run is not aborting it. Shared by reference across groups, so a group registering its own server marks it for everyone. Entries for closed servers are harmless: publishing to a server with no clients reaches nobody.

File-watcher build bookkeeping. Only meaningful in watch mode, where there is one group.

HTML fixtures and their referenced assets, resolved once by buildCachedContent before any group config is spread off. Frozen from that point on, so all groups share one copy.

RunState for this group only. The group spread replaces this object (everything else in RunState is shared by reference), so it is the one place per-group slots may live.

Usage

import { type RunState } from "lib/types.ts";