tia — test impact analysis for Python
Run only the tests a change can affect. Run everything whenever that cannot be established.
Status: working, pre-release. Selection runs end to end. Safety validation at scale (defect injection across the corpus) is not finished, so the miss rate below is still
TBD— see Measured results and docs/SAFETY.md.
What it does
- Learning (once). Runs the suite under
coverage.pywith dynamic contexts and persists, for every source line, the set of tests that executed it. The map lives in.tia/map.db, keyed to the commit it was built at. - Selection (every change). Asks git which lines changed, classifies each changed path, looks the trustworthy ones up in the map, and hands pytest that subset. Everything else falls back to the full suite with a reason code.
The selection logic is meant to be read, not trusted: tia select --explain
prints the chain from changed line to covering test to reason code.
Install
Not published yet. From a clone:
pip install -e ".[dev,eval]"
Requires Python 3.11+.
Getting started
tia init --package yourpackage # write .tia.toml, create .tia/
tia build # run the suite once under instrumentation
tia select --explain # see what a change selects, and why
tia run # run just those tests
Usage
tia init write .tia.toml, create .tia/, add to .gitignore
tia build [--suite CMD] [--jobs N] run the instrumented suite, build the map
tia select [--base REV] [--format nodeids|json] [--explain]
tia run [--base REV] -- <pytest args>
tia status map freshness, commit, size, test count, age
tia explain PATH:LINE which tests cover this line, and why
pytest --tia [--tia-base=REV] plugin form, for CI
Exit codes: 0 success, 1 test failure, 2 usage error, 3 map unusable.
Safety
A tool that is fast but occasionally lets a defect through is worse than no tool. tia is conservative by construction: nine of the fourteen classifier rules deliberately give up the speed benefit and run everything. How often that happens is measured and published, not hidden.
Complete certainty is not attainable in a dynamically typed language and is not claimed. docs/SAFETY.md states exactly what is guaranteed, what is not, and how the residual risk is measured rather than asserted. Read it before using tia as a merge gate.
Measured results
Every number here was produced by the harness on this machine (Apple M4, 10
cores, 16 GB, Python 3.12.10). Anything not yet measured says TBD rather than
something plausible.
Corpus baselines — median of 5 runs after a warmup, from
eval/results/:
| repo | tests | -n auto |
serial |
|---|---|---|---|
| scrapy | 5000 | 49.85 s | 204.85 s |
| attrs | 1412 | 2.30 s | 3.93 s |
On attrs, one changed line (src/attr/_funcs.py, single line edited):
| Metric | Value |
|---|---|
| Map build | 1334 tests, 42 files, 508,908 rows, 13.3 MB, ~10 s |
| Instrumentation slowdown | 2.2× (3.93 s → 8.55 s serial) |
| Tests selected | 31 of 1334 |
| Selected run | 0.82 s against a 3.93 s serial baseline |
Safety validation. One defect injected at a time; the full suite must catch it or the mutant is excluded as equivalent; then the selection runs. If the selection passes where the full suite failed, that is a miss. attrs was run at 200 mutants (seed 2026), scrapy at 30 (seed 1234). Each pair shares one map and one set of mutation sites and differs only in whether the import closure is consulted.
| attrs, no graph | attrs, graph | scrapy, no graph | scrapy, graph | |
|---|---|---|---|---|
| Non-equivalent defects | 184 | 184 | 27 | 27 |
| Misses | 0 | 0 | 0 | 0 |
| Fallback frequency | 52.7% | 50.5% | 59.3% | 29.6% |
| Net time reduction | 57.0% | 58.4% | 40.4% | 65.2% |
| Selection precision, median | 62.5% | 55.2% | 25.0% | 9.8% |
| Selections holding every failing test | 85/87 | 87/91 | 11/11 | 14/19 |
Zero misses in 211 non-equivalent injected defects across two real codebases — after the import graph was caught letting one through at 200 mutants and repaired (D-0014).
The import graph pays off in proportion to suite duration, not test count. On scrapy, whose suite takes 52 s, it halves the fallback rate and lifts the saving from 40.4% to 65.2%. On attrs, whose suite takes 3.9 s, it is worth almost nothing — 58.4% against 57.0% — because pytest's own startup is the floor and there is nothing left to buy.
Precision is the honest cost of conservatism. A median of 55.2% on attrs and 9.8% on scrapy: the closure selects broadly, and many selected tests do not fail. That is what keeps the miss rate at zero. Note also that "no miss" is not complete recall — on scrapy, 14 of 19 selections contained every failing test; the others caught the defect with only some of them.
Results: safety_attrs_2026-10-02T161902Z_seed2026_nograph.json,
safety_attrs_2026-10-02T174240Z_seed2026_graph.json,
safety_scrapy_2026-10-02T191409Z_seed1234_nograph.json,
safety_scrapy_2026-10-02T182614Z_seed1234_graph.json.
Still unmeasured: fallback frequency over real historical commits, a third codebase, and whether 0.6 is the right closure limit. No published number moves without the JSON that produced it.
Repository
docs/SPEC.md— the specification this implementation follows.docs/DECISIONS.md— dated decision log with falsification conditions.eval/— the evaluation harness and its committed results.
Licence
MIT.
Metadata
Release files for tia-select 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tia_select-0.1.0.tar.gz | 215.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tia_select-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 251.3 kB
Release files / tia_select-0.1.0.tar.gz
| Download URL | tia_select-0.1.0.tar.gz |
|---|---|
| Size | 215.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f685edfef897a59b5f07d26784d777f9918dde996b48029912a99788777ac91
|
|
BLAKE2b-256 checksum How to use checksums |
b4ab4e42b8c1fa690f317bcf8d72c76c2051c534f525cd1e708da3838fac1ed2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / tia_select-0.1.0-py3-none-any.whl
| Download URL | tia_select-0.1.0-py3-none-any.whl |
|---|---|
| Size | 35.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cd8b8573642c46b73fb9e9951b7800835017a5656cd323870609082db0410ea1
|
|
BLAKE2b-256 checksum How to use checksums |
eaa929641517cc2cb5cacbe54f14297204e278941bd7eb591992f9543848406c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|