Runs the Rust tests affected by your change, instead of all of them.
A pre-commit hook for Cargo projects. Give it a list of changed files; it works out which test modules can observe the change and runs only those.
It is a hook, not a hook runner. Use it under prek (recommended) or pre-commit.
cargo install --path smart-hooksAdd it to .pre-commit-config.yaml:
repos:
- repo: https://github.com/CaptainEmpower/smart-hooks
rev: v0.3.0
hooks:
- id: smart-test-selectorOr run it directly:
smart-hooks test src/parser.rs src/lexer.rsSee the plan without running anything:
smart-hooks test --dry-run src/parser.rs🔍 Analysing 1 changed file(s) for affected tests...
unit: parser
--json emits the same plan for scripting. Under --json, stdout carries
exactly one JSON document, written after execution so its status reflects
what actually happened:
{
"status": "planned",
"files_analysed": 1,
"test_plan": {
"unit_tests": ["parser"],
"integration_tests": false
}
}That is the whole CLI: smart-hooks test [--dry-run] [--json] [FILES]....
Selection is path-based, derived from each changed file's module path:
src/parser.rs→ theparserunit-test filtersrc/analysis/config.rs→analysis::configsrc/analysis/mod.rs→analysissrc/main.rs,src/lib.rs→ no module of their own- Files matching configured patterns (
/core/,/types.rs,/error.rs, …) additionally select the integration-test targets - Anything under a
testsdirectory selects nothing — it is test code, not crate source
Configure it with a TOML file matching TestSelectorConfig if the defaults do
not fit your layout.
Stated plainly, because a test selector that quietly skips the wrong test is worse than no selector:
- It does not read your dependency graph. Selection is path-based. If
src/parser.rschanges and a test insrc/eval.rscovers it, that test is not selected. Fixing this is the subject of ADR 0004: acargo metadatareverse-dependency closure, with a full-run fallback wherever the graph cannot be trusted. - It has no soundness harness. Nothing yet proves the selected set is a
superset of the tests that would fail under a full run. Until it does, treat
this as a fast pre-commit filter and keep a full
cargo testin CI. - Rust and Cargo only. Earlier versions advertised multi-language support; the analyzers existed but the CLI never reached them.
Verified on a two-module demo crate where the unrelated module holds a deliberately slow (1.5 s) test, warm target directory:
| Wall clock | |
|---|---|
cargo test (full suite) |
1.55 s |
smart-hooks test src/calculator.rs |
0.02 s |
And on this repository's own suite, warm, changing one file:
| Wall clock | |
|---|---|
cargo test --lib (full, 107 tests) |
~10 s |
cargo test --lib analysis::config after touching that file |
1.69 s |
Both numbers come from the commands shown; reproduce them before trusting them. The saving scales with how slow your unrelated tests are and shrinks as incremental compilation comes to dominate.
0 selection ran and every selected test passed · 1 a selected test failed ·
2 usage error.
The hook fails your commit when a test fails. That sounds obvious; a previous version printed the failure and exited 0.
The scope, and what was removed to reach it, are recorded in docs/adr:
| ADR | |
|---|---|
| 0001 | Consume prek rather than wrap it |
| 0002 | Narrow scope to Rust test-impact selection |
| 0003 | Delete uncompiled and orphaned code |
| 0004 | Derive the impact set from cargo metadata |
| 0005 | Strict assertions and verifiable claims |
cargo test # 104 tests
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all -- --checkMIT — see LICENSE.