Testing & CI
133 test modules, a synthetic golden corpus in the repository, and one very large corpus that is not — which is why the suite has to run in both worlds.
The suites
| Directory | Modules | Covers |
|---|---|---|
tests/tuning/ |
85 | the engine: extractors, maps, decoders, recipes, apply, parity |
tests/cli/ |
19 | each command's surface and exit codes |
tests/tui/ |
15 | each screen: keys, layout, messages, the file picker |
tests/api/ |
8 | registry, dispatch, error codes, GIL release, transport |
tests/checksum/ |
1 | scheme detection |
tests/convert/ |
1 | HEX / S-Record normalising |
tests/recipes/ |
1 | recipe documents |
tests/corpus/ |
1 | the synthetic golden corpus |
Run them with:
uv run pytest tests/ -q # everything
uv run pytest tests/tui -q # one area
uv run pytest tests/tuning/manufacturers/test_edc17_extractor.py -v
The corpora
Two committed kinds, and the distinction matters when you write a test.
| Corpus | Where | What it is |
|---|---|---|
| Synthetic | tests/corpus/ — committed |
generated by openremap synth: deterministic, no ECU data, safe to publish. It carries a manifest.json of what each file plants, so a test can assert against known truth |
| Fixture recipes | _dev/contrib/ |
an 89-file mimic corpus the contribution wizard checks candidate markers against |
Dump-based tests skip cleanly when no local dump corpus is present — the normal state in CI and on a fresh clone. That rule is enforced by a conftest helper; a test that reaches for a dump without the guard fails everywhere CI runs, and that has happened (it is how the guard became a rule).
Parity goldens
The 0.9.0 apply engine replaced a flag-driven implementation, and the replacement
was accepted only after it produced byte-identical output on 47 real stock/tuned
pairs. Those pairs are pinned in tests/tuning/parity_goldens.json, so the claim
survives the deletion of the old code:
- the golden file records what the old engine wrote;
- the test replays the new ladder against the same inputs;
- a difference is a failure, not a tolerance.
This is the pattern the project prefers over "trust the new code": record the old behaviour, delete the old code, keep the record.
Beyond parity, the tuning suite covers:
- extractor tests — each family's
can_handle()andextract()against its fixtures, including the negative case (a different family must not claim it); - doc-path guards —
tests/test_doc_paths.pyresolves backticked repository paths in the internal docs against the committed tree, so a spec cannot reference a file that does not exist; - GIL release —
tests/api/test_gil_release.pyasserts that a heavy native call does not hold the interpreter lock, as a property rather than a timing; - width-sensitive help — CLI help tests pin the terminal width, because a narrow renderer truncates long flag names and the assertion becomes a test of the CI runner's console.
Continuous integration
.github/workflows/ci.yml — two jobs:
| Job | Runs on | What it runs |
|---|---|---|
| test | ubuntu-latest, matrix Python 3.10, 3.11, 3.12, 3.13, 3.14 |
installs the Rust toolchain and uv, then uv run pytest tests/ -x |
| portability | ${{ matrix.os }} — Windows and macOS |
the TUI and console tests |
The portability job is advisory (continue-on-error) on purpose, and the reason
is documented in the workflow: the suite was written on Linux, wheels were built for
Windows and macOS, but the tests had never run there — so "the TUI works on
Windows" was an assumption. Its first run caught two real defects (a drive-root path
rewrite that mangled UNC paths, and a test that only passed because the corpus was
present locally), plus the console-encoding bug that made redirected output crash on
the first em dash. The flag is meant to come off once the job is green twice.
.github/workflows/release.yml — three stages:
| Stage | What it does |
|---|---|
| test | the suite must pass before anything is built |
| build | wheels for Linux (glibc), Linux (musl), macOS and Windows — one wheel each, thanks to abi3 |
| publish | uploads to PyPI |
The musl build exists because a musllinux wheel published under a glibc tag is rejected on upload; the platform tags are part of the release, not an afterthought.
What the suite does not test
- Real flashing. Nothing here writes to a vehicle, so no test can.
- Checksum correction. There is no code to correct a checksum, so there is nothing to test — the guarantee is the absence of a function, not a test.
- Bit-exact behaviour of third-party decoders. capstone and the oracles are trusted as they are; what is tested is the cascade around them.