Orchestrata Ultimate › Tests
Tests — a guide to the orchestrata test suite
This page explains the orchestrata test suite for a person who has to run it, read its results, or decide whether a red run means anything.
The numbers below were measured on 2026-10-23 in wt-MAP-PRECOMMIT, at 8a2cb42ad on feat/maps-via-codetools, one commit ahead of master (a4cbc2bdf). Where a number comes from another checkout, the page says so.
The generated reference pages hold the per-module detail:
- test reference: every test module;
- run reports: the recorded runs;
- green ledger: which modules are proven green;
- durations: how long each module takes.
1. What the suite is
tests/ holds 2,094 Python files. 1,976 of them are test_*.py modules; the rest are conftest.py files (19 of them), fixtures, helpers and __init__.py. There are 50 top-level directories, and 66 test modules sit directly in tests/.
| Directory | Python files | test_*.py modules |
Area (from the directory and module names) |
|---|---|---|---|
scripts |
294 | 291 | The repo's own tooling: the runner, gates, AST pipeline, map generators, worktree and deploy scripts |
agents |
136 | 121 | Volks (the agents) and their runtime |
verification |
117 | 103 | Spec, traceability and conformance checkers |
knowledge_graph |
114 | 110 | The operational graph: sync, schema, queries |
session |
107 | 105 | The session controller, goals, handoff, the session CLI |
claude |
99 | 99 | Claude Code hooks, skills and tools shipped with the repo |
core |
95 | 82 | Shared core: threading, the test executor, the live observer, config |
generated_recovery |
88 | 88 | Generated (see below). Excluded from runs |
gateway, orchestrator, kernel, postman, … |
The other runtime subsystems |
The generated_* directories
Six directories hold tests that a generator wrote, not a person:
| Directory | Modules |
|---|---|
generated_recovery |
88 |
generated_core |
63 |
generated_agents |
50 |
generated_orchestrator |
40 |
generated_gateway |
37 |
generated_plugin |
13 |
The files are named test_<module>_generated.py. They come from the test generators in src/orchestrata_tools/test_generator/: scripts/gen_tests.py drives EnhancedTestGenerator, and there are Jinja, interface, branch and behavioural generators besides. The generators write interface, branch and behavioural tests against the modules they cover.
tests/generated_recovery and tests/_generated_broken are passed to pytest as --ignore in pyproject.toml, so a normal run never collects them. In September 2026 the generated suites were regenerated with backward-compatible signature pins (see blockers, history section).
Markers
pyproject.toml runs pytest with --strict-markers, so every marker must be declared. Declared markers include:
unit,integration,no_infra;requires_redis,requires_graph,requires_prolog;slow,known_slow,llm_integration,incus_live;not_parallelizable,isolation;- a set of
REQ_*traceability markers.
The default selection is not slow and not llm_integration and not incus_live. The runner overrides that so slow tests are included, and leaves llm_integration and incus_live as opt-in lanes.
2. How to run it
The runner: scripts/run_tests.py
All suite runs go through one entry point, scripts/run_tests.py. It is not plain pytest. It checks inputs, starts or verifies infrastructure, runs blocking static gates, reuses anything already proven green, then runs the rest in batches across worker processes and writes a machine-readable report.
The run pipeline, read from _main in scripts/run_tests.py through the AST toolkit. Stage numbers are the ones in the source comments.
Modes
From scripts/run_tests.py --help:
| You want to… | Command | Notes |
|---|---|---|
| Run named modules or directories | .venv/bin/python scripts/run_tests.py tests/scripts/test_worktrees.py tests/core |
Positional paths; the default is tests/. A path that does not exist aborts the run before any infra starts |
| Run what a change affects | .venv/bin/python scripts/run_tests.py --affected [REV] |
Resolves the changes since REV (default HEAD) through scripts/affected_tests.py. A subset run: non-authoritative, and the full suite is still owed |
| Proceed past a refusal | --affected-decision "<citation>" |
Records a human decision that lets a refused resolution run its bounded remainder. RUL-040: a refusal is never a silent full run |
| Include rehearsal tests | --include-rehearsal |
Real deploy, Incus or tofu surfaces. For a deliberate rehearsal, never for a gate (RUL-039) |
| Re-run even if proven | --force |
Ignores the proven-run reuse check |
| Machine output or deep failures | --json, --debug |
--debug captures DEBUG logs into each failing test's report |
| Tune parallelism | -j/--workers (8), -b/--batch-multiplier (6), -t/--timeout (420 s per batch), --backfill (0), --solo-retry-cap (25) |
Defaults shown in brackets |
| Skip stages | --skip-infra, --skip-services kernel,gateway,redis,graph, --skip-gates, --skip-freshness |
For debugging only. The flags take no value except --skip-services, and a stray value after one is read as a test path |
| Extra pytest arguments | -a/--pytest-arg (repeatable) |
Watching a run: pytest-live and observe-pytest
Never start a run and wait for its exit code (RUL-009). A six-minute run gives no signal until the end, and a hang gives none at all. Start the run in the background and watch it:
pytest-live tests/unit -q & # any project: uses ./.venv/bin/python, puts the observer plugin on PYTHONPATH
observe-pytest list # instant: running and finished sessions with pass/fail counts
observe-pytest attach latest # streams PASS/FAIL, exits when the session finishes
Both tools are deployed at /opt/orchestrata-tools/bin. /usr/local/bin/observe-pytest is a symlink to observe_pytest there, and pytest-live is on PATH too. pytest-live also pins OPENBLAS/OMP/MKL_NUM_THREADS=1, because free-threaded Python aborts when native maths thread pools race. See pytest-live observer and the observe-work skill.
In a Claude session the hooks enforce this:
- a foreground test run is denied;
- a full-suite run is denied when the affected-test oracle has bounded the change, and the hook prints the targeted command instead;
- multi-module pytest is rerouted to the threaded observed runner.
3. The rules
| Rule | What it means in practice |
|---|---|
| RUL-001: no xdist | Never pytest -n or --dist. xdist deadlocked this suite in about 78% of runs. Parallelism comes from the batched executor's worker processes, each with its own database slot |
| RUL-002: no mocks, monkeypatch or skips | Tests use the real test fleet. The test-purity gate and a stop-hook enforce it. When a test catches a real bug, fix the code, never the test |
| Test isolation | Every test gets an id and cleans up exactly what it created, in graphs, Redis and the home directory. See the test-isolation skill, test Memgraph isolation, and the teardown and Redis sections of running the test suite |
| The 300 s cap (RUL-038 amendment) | The test phase (preflight and gates excluded) has a hard cap, ORCH_TEST_CAP_SECONDS, default 300 s. The runner prints the selected count first, then a live run X / selected Y · left Z · failed F line. At the cap every child pytest kills itself, the passing modules go into the green ledger, and the run exits non-zero with CAPPED. A pytest session that is not run by a sanctioned runner and selects tests from more than one file is refused at collection. Mohamed kept the 300 s cap on 2026-10-22 ("keep 300s") |
| RUL-009: observe, never wait blind | Background plus observe-pytest, as above |
| RUL-032: a test runs once per code state | A module whose content key matches its last green run is not run again (section 4) |
| RUL-036: functional or integration | Integration means the test uses a fleet service or the network. Functional means fully in-process. The category is derived from fixtures |
| RUL-038: no containers in test runs | No test creates, copies, launches or inits a container or VM. Container code is exercised only by explicit rehearsals. The same page carries the 300 s cap above as an amendment |
| RUL-039: no rehearsals in the gate | Rehearsal-class modules exist but are excluded from gate runs |
| RUL-040: scope at symbol level | A change's blast radius is measured, and an escalation to the full suite is a bug, not an answer |
For agents: no test run without Mohamed's permission and an agreed time budget. Reading recorded logs and reports is not a run.
4. The environment and the gates
The test fleet
Tests run against the Incus project orchestrata-test on the isolated bridge 10.53.2.0/24. The fleet holds:
- the app container (gateway
:8765and kernel:50053); - a single Memgraph node (
10.53.2.10), plus a disposable HA main/replica pair the HA tests may mutate; - Redis (
10.53.2.11) and Prolog (10.53.2.12); - the
rh-*cluster.
The endpoint profile is env/test.env, with 71 keys and no secrets; secrets layer in from a gitignored file. tests/conftest.py hard-pins the suite to this env and refuses to run if the resolved infra is not on 10.53.2.x. The test env cannot reach prod (Incus ACL). See isolated test fleet.
Since 2026-10-21 the fleet's autostart is off (see the DeepChat review). After a reboot it must be started before integration tests can pass. The runner's infrastructure check (stage 1) reports what is down.
What the pre-push gate runs
scripts/hooks/pre-push, wired through core.hooksPath = scripts/hooks, runs on every push:
- It refuses to run the suite from a protected-branch worktree.
- It skips the suite if
reports/.ship-verifiedalready certifies this exact source id. - It fails closed if the base ref does not resolve.
- It computes the range from the commit being pushed:
BASE...<pushed sha>, three-dot, committed changes only. With an empty diff it exits 0. - It runs
.venv/bin/python scripts/run_tests.py --affected "$RANGE". - On a green run whose report names the pushed
HEAD, it writes the source id intoreports/.ship-verified, so a re-push of identical source skips the suite.
Affected-test selection on push, from scripts/hooks/pre-push and the runner's stages.
How affected_tests.py decides:
- It walks the serialized import closure, so a changed
src/file selects every transitive dependent test, not only its same-named one. - It maps changed
.plrule files through a rule-pattern table. - It always adds the
tests/infrastructuresmoke floor. - It names a whole-suite change only when the honest blast radius really is the whole suite: the
env/*.envendpoint contract, asrc/file outside the serialized closure, an unrecoverable deleted module, or a module-level change to the rootconftest.
Since GATE-AFFECTED (68024751d, 2026-10-21), a push of only inert files (docs, config, generated) skips infrastructure and gates entirely.
RUL-032: green-ledger reuse
The runner reuses proven work at two levels:
- Whole report. If
reports/run-tests-latest.jsonis green, was made at the samehead_commitanddirty_sig, and its scope covers this run, the report is reused. - Per module (the green ledger). Each module has a content key made of the module itself, its
srcimport closure, its conftest chain andpyproject.toml. If that key matches the module's last green run, the module is not re-run, whatever the git identity. Every module that goes green is recorded inreports/test-green-ledger.json. An interrupted run therefore resumes from the modules it already proved.
Today the ledger in wt-MAP-PRECOMMIT holds 9 modules. They come from runs at three commits: 7fa34f61d (2 modules), d4ae44819 (6) and a4cbc2bdf (1). See green ledger.
5. Durations and the slow tail
Most modules are fast, and a few dominate. The components worktree's module duration cache (wt-COMPONENTS/.cache/test_durations.json, last written 2026-10-21) gives:
| Measure | Value |
|---|---|
| Modules with a recorded duration | 1,962 |
| Sum of module durations | 10,124 s |
| Median module | 0.39 s |
| Modules at 60 s or more | 43 |
| Modules at 120 s or more | 12 |
| Share of total time in the slowest 50 modules | 4,715 s (47%) |
| Slowest modules (components cache) | Seconds |
|---|---|
tests/gateway/test_mcp_registration_installer.py |
252 |
tests/core/threading/test_test_executor.py |
191 |
tests/scripts/test_ast_live_sync.py |
183 |
tests/harness/test_cli.py |
163 |
tests/scripts/test_run_tests.py |
158 |
tests/scripts/test_gen_brief_pack.py |
158 |
tests/scripts/test_verify_package.py |
157 |
tests/scripts/test_validate_ci.py |
157 |
Why the tail matters. The executor packs modules into bins by their recorded durations (longest first). One slow module caps how fast a bin can finish, and so how fast the whole run can finish. On 2026-10-21 an affected run of 151 modules lost 137 fast modules this way: a slow Memgraph module in the same bin hit the 420 s batch timeout and took the bin down with it. The cache records contention-free durations, so the packer under-provisioned the bins.
How the runner reports slowness. Any test over 60 s in a run is listed as a defect candidate in the summary. A test marked known_slow has had its slowness reviewed, and is listed separately.
The measured floor. The full gate floors at about 250 s on the dev box. See suite parallelism floor and how the suite got 32% faster.
In this worktree the slowest recent single test is test_gen_codebase_map.py::TestDeterminism::test_two_builds_are_byte_identical: 117 s in one of its recorded runs, 19–32 s in the others (reports/test-durations.ndjson, 691 test records over 9 runs). See durations for the generated per-module table.
6. How to read a report
The canonical report is reports/run-tests-latest.json. The runner writes it atomically, so it is always valid JSON. A scoped run or an abort never overwrites a current full-scope record.
Here is today's report, field by field (read with jq):
| Field | Today's value | Meaning |
|---|---|---|
head_commit |
a4cbc2bdf… |
The commit the run tested |
dirty_sig |
5f1b24f6… |
A hash of uncommitted changes. Report reuse needs both identities to match |
scope |
7 paths | The tests asked for. "full" for a whole-suite run |
affected |
"subset run — non-authoritative, full suite still owed" | Says plainly that this run does not certify the whole suite |
affected_details.verdict |
bounded |
The selector's verdict |
test_totals |
134 passed, 0 failed, 0 errors, 0 skipped | Test counts, including reused modules' recorded counts |
passed / failed / total |
1 / 0 / 1 | Module counts for modules actually executed this run |
module_test_counts |
test_hooks_pre_commit.py: 12 |
Tests per executed module |
reused |
6 modules, 122 tests | Proven green earlier and not re-run (RUL-032) |
rerun, solo_retry |
0, null | Batch-failed modules re-run alone. A recovery is a load or order flake and is logged to .flake_recoveries.ndjson |
slow_tests, acknowledged_slow |
empty | Over-60 s candidates, and reviewed known_slow tests |
rehearsal_excluded |
empty | Modules dropped under RUL-039 |
time_seconds |
30.8 | Wall time of the run |
So this run executed 12 tests in one module, reused 122 tests from 6 modules, and passed. It is evidence for those 7 paths at a4cbc2bdf only.
Checks that tell you whether to trust a green report:
test_totals.passedis greater than 0. A run that executed nothing is not green.head_commitis the commit you care about, anddirty_sigmatches a clean tree if you need one.scopeis"full", or covers the paths you changed.failedandtest_totals.failed/errorsare 0, andrerun.still_failedis empty.- An
abortedkey means no test ran; its value names the stage that stopped the run.
Before stamping a source as verified, the pre-push hook applies check 1, the head_commit half of check 2, and the failure and error counts of check 4.