TestPrune.Core
5.0.0
See the version list below for details.
dotnet add package TestPrune.Core --version 5.0.0
NuGet\Install-Package TestPrune.Core -Version 5.0.0
<PackageReference Include="TestPrune.Core" Version="5.0.0" />
<PackageVersion Include="TestPrune.Core" Version="5.0.0" />
<PackageReference Include="TestPrune.Core" />
paket add TestPrune.Core --version 5.0.0
#r "nuget: TestPrune.Core, 5.0.0"
#:package TestPrune.Core@5.0.0
#addin nuget:?package=TestPrune.Core&version=5.0.0
#tool nuget:?package=TestPrune.Core&version=5.0.0
TestPrune
Run only the tests your change could have affected.
TestPrune analyzes your F# code to work out which functions depend on which, then uses that map to skip tests that couldn't have been touched by what you changed. The aim: when your suite takes minutes but you changed one function, you wait seconds.
Status: early alpha. This is a young project, substantially AI-written, and still finding its shape. Behavior and APIs shift between versions, so pin a version and expect surprises. Issues and PRs are very welcome.
Why?
When your test suite takes minutes but you only changed one function, running everything is wasteful. TestPrune builds a map of your code — which functions call which, which tests cover which code — and tries to pick just the tests that matter.
Change multiply? Ideally only the multiply tests run. Change a type
that three modules depend on? Those three modules' tests run. Add a new
file? Everything runs, just to be safe.
Quick example
Say you have a math library and some tests
(from examples/SampleSolution):
// src/SampleLib/Math.fs
module SampleLib.Math
let add x y = x + y
let multiply x y = x * y
// tests/SampleLib.Tests/MathTests.fs
[<Fact>]
let ``add returns sum`` () = Assert.Equal(5, add 2 3)
[<Fact>]
let ``multiply returns product`` () = Assert.Equal(12, multiply 3 4)
You change multiply. TestPrune works out that only
multiply returns product needs to run — and skips add returns sum.
Try the CLI
The quickest way to see it work is the test-prune CLI, a reference
implementation that wires the library up for you:
test-prune index # Build the dependency graph
test-prune run # Run only affected tests
test-prune status # Show what would run (dry-run)
test-prune dead-code # Find unreachable production code
It detects changes from your version control (jj or git), so run
index once, then run/status after each edit.
Global options: --repo <path> (repo root, default: auto-detect),
--parallelism <n> (max parallel analyses, default: processor count).
The CLI re-analyzes serially and isn't tuned for big codebases —
FSharp.Compiler.Service type-checking is slow. For real workflows,
embed TestPrune.Core in your build tooling, where you can cache and
parallelize. See the integration guide.
How it works
- Index — Parse every
.fsfile, record which functions/types exist and what they depend on. Store in SQLite. - Diff — Look at what files changed since last commit.
- Compare — Figure out which specific functions changed (added, removed, or modified).
- Walk — Follow the dependency graph from changed functions to find every test that transitively depends on them.
- Run — Execute only those tests.
If anything looks uncertain (new files, project-file changes), it falls back to running everything. Better to run too many tests than miss a broken one.
Declarative dependencies
For edges the analyzer can't see — reflection, DI-by-type, or non-F#
files like snapshots, migrations, or config —
TestPrune.Attributes
lets you declare them:
open TestPrune
[<DependsOn(typeof<PluginRegistry>)>] // reflection target
let registerPlugins () = ...
[<DependsOnFile("tests/snapshots/api.snap.json")>] // specific file
[<Fact>]
let ``api snapshot`` () = ...
[<DependsOnGlob("migrations/*.sql")>] // glob
type DbIntegrationTests() = ...
Glob dialect: ** crosses path segments, * stays within one, ? is
a single non-/ char. Paths are repo-relative and case-sensitive. The
attributes are metadata — no runtime behavior.
Packages
| Package | What it's for |
|---|---|
TestPrune.Core |
The library — use this in your build system or editor |
TestPrune.Attributes |
Consumer-side markers: [<DependsOn>], [<DependsOnFile>], [<DependsOnGlob>] |
TestPrune.Falco |
Extension for Falco web apps (route → test mapping) |
TestPrune.Analyzers |
Opt-in F# analyzer that flags anonymous records (invisible to impact analysis) |
TestPrune |
CLI tool (reference implementation) |
Going deeper
- Integration guide — embed
TestPrune.Core: indexing, two-level caching, finding affected tests, dead-code detection, extensions, the analyzer, and dependency-change fanout. - Full documentation
- API reference
Design choices
Static analysis, not coverage. TestPrune reads your code's AST instead of instrumenting test runs. So you don't need to run tests to build the graph, and there's no flaky-coverage problem. The tradeoff: it may run a few extra tests, but it aims never to miss a broken one.
Safe by default. When in doubt, run everything. A missed broken test is much worse than running a few unnecessary ones.
Single-file storage. The dependency graph is one .test-prune.db
file. No servers, no services. Rebuilds are atomic.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FSharp.Compiler.Service (>= 43.12.204)
- Microsoft.Data.Sqlite (>= 10.0.9)
- SQLitePCLRaw.lib.e_sqlite3 (>= 3.50.3)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on TestPrune.Core:
| Package | Downloads |
|---|---|
|
FsHotWatch.TestPrune
FsHotWatch plugin for TestPrune test impact analysis |
|
|
TestPrune.Falco
Falco route-based integration test filtering extension for TestPrune |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.0.0 | 0 | 7/14/2026 |
| 5.0.0 | 64 | 7/11/2026 |
| 4.3.0 | 200 | 6/16/2026 |
| 4.2.3 | 114 | 6/15/2026 |
| 4.2.2 | 126 | 6/12/2026 |
| 4.2.1 | 126 | 6/7/2026 |
| 4.2.0 | 104 | 6/5/2026 |
| 4.1.0 | 120 | 6/4/2026 |
| 4.0.3 | 116 | 6/2/2026 |
| 4.0.2 | 105 | 5/26/2026 |
| 4.0.1 | 117 | 5/4/2026 |
| 4.0.0 | 127 | 4/25/2026 |
| 3.0.2 | 137 | 4/22/2026 |
| 3.0.1 | 141 | 4/20/2026 |
| 3.0.0 | 112 | 4/20/2026 |
| 2.0.0 | 170 | 4/11/2026 |
| 1.0.1 | 163 | 4/7/2026 |
| 0.1.0-beta.1 | 154 | 4/2/2026 |
| 0.1.0-alpha.9 | 74 | 4/2/2026 |
| 0.1.0-alpha.8 | 73 | 4/1/2026 |