TestPrune.Falco
3.1.3
dotnet add package TestPrune.Falco --version 3.1.3
NuGet\Install-Package TestPrune.Falco -Version 3.1.3
<PackageReference Include="TestPrune.Falco" Version="3.1.3" />
<PackageVersion Include="TestPrune.Falco" Version="3.1.3" />
<PackageReference Include="TestPrune.Falco" />
paket add TestPrune.Falco --version 3.1.3
#r "nuget: TestPrune.Falco, 3.1.3"
#:package TestPrune.Falco@3.1.3
#addin nuget:?package=TestPrune.Falco&version=3.1.3
#tool nuget:?package=TestPrune.Falco&version=3.1.3
TestPrune.Falco
When you change a route handler, only re-run the integration tests that hit that route.
This is an extension for TestPrune
that connects Falco URL routes to integration tests. If you change the
handler for /api/users/{id}, it finds the tests that make requests to
that URL and runs just those.
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.
Installation
dotnet add package TestPrune.Falco
How to use it
1. Store your route mappings during indexing
Routes are the one thing TestPrune cannot read out of your code — they
live in a route DU plus runtime wiring, not in the symbol graph — so you
seed them. RouteStore owns that table: it lives inside TestPrune's cache
database, but TestPrune.Core knows nothing about it (it just hands out a
connection, via toPluginStore).
Each entry is a RouteHandlerEntry; Rebuild clears and rewrites the
whole route table, so re-seed it on every indexing run. Set
HandlerFunction to the short Module.function serving the route so a
route's tests link only to that function (a one-function change to a
multi-route file selects only that route's tests); None falls back to a
whole-file match (every function in the file):
open TestPrune.Ports // toPluginStore
open TestPrune.Falco // RouteStore, RouteHandlerEntry
let routeStore = RouteStore(toPluginStore db)
routeStore.Rebuild [
{ UrlPattern = "/api/users/{id}"
HttpMethod = "GET"
HandlerSourceFile = "src/Web/Handlers/Users.fs"
HandlerFunction = Some "Users.get" }
{ UrlPattern = "/api/users/{id}"
HttpMethod = "PUT"
HandlerSourceFile = "src/Web/Handlers/Users.fs"
HandlerFunction = Some "Users.update" }
]
2. Create the extension and query affected tests
The extension reads routes through that same RouteStore:
open TestPrune.Ports // toSymbolStore
open TestPrune.Extensions // ITestPruneExtension
let extension =
FalcoRouteExtension(
integrationTestProject = "MyApp.IntegrationTests",
integrationTestDir = "tests/MyApp.IntegrationTests",
routeStore = routeStore
)
// Affected test classes, directly:
let affected = extension.FindAffectedTestClasses(changedFiles, repoRoot)
// -> [{ TestProject = "MyApp.IntegrationTests"; TestClass = "UsersTests" }]
To feed those couplings into TestPrune's dependency graph instead, use
the ITestPruneExtension interface, which returns edges to inject:
let edges =
(extension :> ITestPruneExtension)
.AnalyzeEdges (toSymbolStore db) changedFiles repoRoot
// -> Dependency list (test symbol -> handler symbol, kind SharedState)
How it works
- Checks if any changed file is a known handler (from the route table)
- Looks up which URL patterns that handler serves
- Scans your integration test
.fsfiles for those URLs (/api/users/{id}matches/api/users/123in your test code). A route with no literal path text of its own — the root route/, or a param-only route like/{lang}— is matched only where a quote opens it ("/","/?lang=en",'/'). Anywhere else/on its own is ordinary punctuation: matching it loosely made the root route match//, the F# comment token, and so select every file with a comment in it - Returns the test classes from files that reference affected routes
(
FindAffectedTestClasses), or those couplings as graph edges (AnalyzeEdges). A class or module counts as a test declaration only when its own span carries a test attribute ([<Fact>],[<Theory>], aFactAttributesubclass such as[<SkippableFact>], the NUnit/MSTest equivalents) or — for a class — aninheritclause, since xUnit runs test methods a base class declares. Fixtures,[<CollectionDefinition>]markers and plain helpers declare no tests and are never returned
If you also mark a composition root
TestPrune.Core lets you mark your route table with
[<TestPrune.CompositionRoot>] so that editing one handler no longer selects
every test whose fixture boots the app. That is only safe because this
extension attributes each route to its own tests directly — cutting the
composition edge leaves the route→test edge behind to select the right tests.
The limit to know: this extension matches tests that name a URL. A browser
test that reaches the route by clicking (page.ClickAsync "#stop-impersonating")
is not attributed, so a composition-root barrier would drop it. Don't mark a
composition root in a repo whose browser tests navigate by UI interaction.
Documentation
| 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.400)
- FSharp.Core (>= 10.1.400)
- Microsoft.Data.Sqlite (>= 10.0.11)
- SQLitePCLRaw.lib.e_sqlite3 (>= 3.53.3)
- TestPrune.Core (>= 7.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.1.3 | 124 | 8/19/2026 |
| 3.1.2 | 92 | 8/18/2026 |
| 3.1.1 | 92 | 8/17/2026 |
| 3.1.0 | 108 | 8/5/2026 |
| 3.0.2 | 110 | 7/25/2026 |
| 3.0.1 | 106 | 7/18/2026 |
| 3.0.0 | 128 | 7/14/2026 |
| 2.0.4 | 116 | 7/11/2026 |
| 2.0.3 | 117 | 6/25/2026 |
| 2.0.2 | 125 | 6/12/2026 |
| 2.0.1 | 130 | 4/20/2026 |
| 2.0.0 | 109 | 4/20/2026 |
| 1.0.2 | 123 | 4/11/2026 |
| 1.0.1 | 132 | 4/7/2026 |
| 0.1.0-beta.1 | 112 | 4/2/2026 |
| 0.1.0-alpha.9 | 77 | 4/2/2026 |
| 0.1.0-alpha.8 | 78 | 4/1/2026 |
| 0.1.0-alpha.7 | 78 | 3/31/2026 |
| 0.1.0-alpha.6 | 71 | 3/30/2026 |
| 0.1.0-alpha.5 | 78 | 3/27/2026 |