Skip to content

Respond to a red check ​

When a check goes red, there are two responses: fix the code, or record a deliberate omission here. A single testing-conventions.toml at your repo root (or per package, in a monorepo) tunes what the checks require — coverage floors and reason-required exemptions. Anything you don't set keeps its strict default; the configuration reference carries every key.

Relax a coverage floor ​

Every coverage check ships a strict 100% floor. Lower one under the language's table:

toml
# Drop the Python floor from the strict default 100 to 90:
[python]
coverage = { branch = true, fail_under = 90 }

[typescript]
coverage = { branches = 90 }   # lines / functions / statements stay at 100

[rust]
coverage = { regions = 90 }    # lines stays at 100

A [<language>].coverage table is a partial override — set only the fields you want to move and the rest keep their default. (A typo'd key is still rejected; only missing keys fall back.) See Reference — coverage keys for every key and default, and Why a 100% floor before you lower one.

Raise the one-function-per-file threshold ​

one-function-per-file runs for Python and TypeScript with no configuration; Rust opts in by naming a threshold. It allows one module-scope function per file whose body runs longer than max_lines, and the default is 1. A codebase whose natural grain is longer functions moves the line under the language's table:

toml
[python]
one_function_per_file = { max_lines = 5 }

[typescript]
one_function_per_file = { max_lines = 20 }

A function at or under the threshold is trivial and shares a file freely. Raising the number moves where "trivial" ends; the one-per-file ceiling above it holds at every setting. See One function per file for what counts as a function and as a line.

Exempt a file ​

Some files genuinely shouldn't be tested — a launcher shim, a process entry point, generated code. A blocking gate needs that escape hatch, but here it's explicit and reason-required, never a silent ignore — see Scoping and exemptions for the design.

Declaration-only modules need no exemption ​

A file with no function and no control flow anywhere in it has nothing a test can drive and is never flagged: an empty or comment-only file, a re-export barrel __init__.py / index.ts, a constants module, an Enum, a type-only module. A TypeScript declaration file (*.d.ts) is ignored too. The parser decides from the file's contents, never from its name — the moment a file gains a def, an arrow, a method, an if, or a ternary, it becomes a subject and needs either a colocated test or an exemption. What it enforces carries the node list per language.

Exempt a real file ​

Add a [[<language>.exempt]] entry naming the rules it lifts and why. Whole-file exemptions are for the presence and lint checks — a launcher shim or a process entry point with no colocated test:

toml
# A launcher shim with no unit test:
[[python.exempt]]
path = "mypkg/cli.py"
rules = ["colocated-test"]
reason = "thin launcher; logic lives in run(), tested in run_test.py"

# A process entry point, exempt from the colocated-test check:
[[typescript.exempt]]
path = "src/bin.ts"
rules = ["colocated-test"]
reason = "process entry; maps run() to an exit code, tested in run.test.ts"
  • path is relative to the scanned source, and must point to a file that exists — a stale entry is a hard error, so the list can't silently rot.
  • rules names the checks the entry lifts (colocated-test, one-function-per-file) or an individual lint rule (no-monkeypatch) — see the glossary for how this key spans both. For coverage / mutation, see the line-scoped form below — those are never whole-file.
  • reason is required; a reason-less entry is rejected when the config loads.

Because every exemption lives in this one file, names its rules, and carries a reason, the whole exemption surface is auditable in a single diff — unlike scattered ignore comments. See Reference — Exemptions for the exact schema.

Exempt specific lines (coverage / mutation) ​

coverage and mutation exemptions are never whole-file — they must carry a lines list naming the exact lines they lift:

toml
[[python.exempt]]
path = "mypkg/config/tomlcompat.py"
rules = ["coverage", "mutation"]
lines = [9, 10, "12-13"]   # single lines and inclusive "start-end" ranges
reason = "version-conditional tomllib/tomli import; one branch is dead on any single interpreter"

A determinism guard checks the list:

  • A listed line that isn't failing (it's covered, has a killed mutant, or carries no measured code) is a hard error.
  • A failing line that isn't listed fails the gate as normal.

So the list is exactly the failing lines. lines is required with coverage / mutation and rejected with any whole-file check, so the two never share an entry — a file exempt from both colocated-test and coverage is two entries.

For a Rust mutation survivor asserted by an integration or e2e test, name that test in a [[rust.exempt]] entry with rules = ["mutation"] and the survivor's exact line. The reason states which behavior the other tier verifies.

See also ​