Design Decisions (ADRs)¶
The pure-Rust engine has its own ADR series — ADR-E0xx ("E" = engine) — recorded in planning/current/pure-rust-test-engine/design/adr/. It is deliberately separate from the old ADR-001..011 series, which belonged to the superseded orchestrate-pytest design (Rust driving pytest + SQLite + coverage.py). Those are retired.
Each record follows Context → Decision → Consequences → Alternatives considered, plus a Revisit trigger. ADR-E014 (the default execution path) and ADR-E015 (the sub-interpreter tier) are implemented and measured; E001–E013 are accepted for the design phase and several are now built as well.
| ADR | Decision | One-line summary |
|---|---|---|
| E001 | Pure-Rust engine, no pytest at runtime | tiderace owns the framework; pytest is not a runtime dependency. |
| E002 | Execution substrate: subprocess + Python shim | A thin shim over a subprocess, not PyO3 embedding or subinterpreters. |
| E003 | Fork-from-warm-wellspring snapshot isolation | Import the project once; fork() per test gives each a pristine COW view — fixture scopes as memory snapshots. |
| E004 | Content-addressed result cache | A test's outcome is a pure function of its input closure (CacheKey); a TieredCache makes a CI result reusable on any machine. |
| E005 | Cargo workspace + trait-based DI seams | Each boundary (collector, scheduler, cache, transport, reporter) is a trait, so it is testable in isolation. |
| E006 | Coverage via sys.monitoring (PEP 669) | Per-test footprints captured in-process with sys.monitoring, not coverage.py; settrace fallback for older CPython. |
| E007 | Warm daemon as the primary host | Keep CPython warm so the project is imported once, not per run. |
| E008 | Fork-first cross-platform strategy | Fork on Unix; the cross-platform fallback lives behind the Worker trait. |
| E009 | Lazy assertion introspection | Introspect a failing assert on demand, not via import-time rewrite. |
| E010 | Locality scheduler (LPT + scope locality) | Keep a module's tests on one worker while LPT-balancing load across workers. |
| E011 | ShimTransport seam | One swappable boundary: PipeTransport (JSON frames) in production, InProcessTransport (FFI) experimental. |
| E012 | Native type-driven authoring | @provides/@cases/@uses resolve fixtures by type, so a suite can drop pytest entirely. |
| E013 | In-process / FFI isolation (②) | Embedded CPython + fork-from-embedded over the transport seam — a research path. |
| E014 | No-fork + restore isolation ladder (implemented + measured) | The default execution path: bare no-fork (pure) / no-fork + restore (mutating) / fork (opaque), sound by construction. |
| E015 | Conditional sub-interpreter tier (implemented + measured) | Detect which modules are sub-interpreter-safe, then run the safe subset across a parallel sub-interpreter pool (per-interpreter GIL, PEP 684) — no fork. The one parallel path Windows has. Opt-in. |
The four that matter most¶
- E003 — fork from a warm wellspring. Import the project once; isolate by forking COW children rather than re-importing per test. This is the substrate the ladder optimizes.
- E004 — content-addressed cache. Outcomes are content-addressed, turning the test suite into a build system: an unchanged test is free, and a result computed in CI is reusable anywhere with the same inputs. A
puritygate keeps nondeterministic tests out of the cache. - E006 —
sys.monitoringcoverage. Per-test executed-source footprints captured on the same in-process run — no coverage.py, no separate pass — feeding both impact analysis and the cache key. - E014 — the no-fork ladder. The engine's default today: most tests skip the fork entirely and run in-process (with snapshot/restore where they mutate state), falling back to fork only for opaque modules. Sound by construction, so it needs no flag and no learning pass.
For the full narrative with diagrams, see ARCHITECTURE.md.