fix(engine): apply --treenode-filter to --list-tests - #6334
Conversation
Discovery requests (--list-tests, IDE Test Explorer) flow through TestDiscoveryService.DiscoverTests with isForExecution=false, which skipped the filter entirely and listed every test. The isForExecution gate exists for side-effecting work (argument registration / fixture creation #6151, dependency closure expansion), but filtering itself is pure (MatchesTest only compares the test path/properties), so it was always safe to run during discovery. Now apply the filter during discovery when a real filter is present. Bare --list-tests and IDE discovery send null/NopFilter and keep listing everything (including [Explicit] tests, which FilterTests would otherwise strip). When a filter is present, reuse FilterTests so the listing mirrors what a run would execute. No fixture leaks: RegisterTestsAsync is still called with isForExecution=false. Adds ListTestsFilterTests covering reflection + source-generated modes.
Up to standards ✅🟢 Issues |
| Metric | Results |
|---|---|
| Complexity | 7 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewerTIP This summary will be updated as you push new changes.
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. Summary: The fix correctly widens filtering in
🤖 Generated with Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Problem
--list-testsignored--treenode-filterand listed every test in the assembly. Users expectapp --list-tests --treenode-filter Xto list only the tests filterXselects — the same set a real run would execute.Root cause
MTP routes
--list-teststo aDiscoverTestExecutionRequest, which flows throughTestDiscoveryService.DiscoverTestswithisForExecution: false. The filter was applied only whenisForExecutionwas true:isForExecutionlegitimately gates side-effecting work — argument registration / fixture creation (#6151) and dependency-closure expansion. But filtering itself is pure (MatchesTestonly compares the test's path/properties against the filter), so running it during discovery was always safe; it just was never wired up. Filtering got conflated with the side-effect gate.Fix
Apply the filter during discovery too, but only when a real filter is present:
--list-tests, IDE Test Explorer) →null/NopFilter→ returns all tests unchanged, including[Explicit]ones (whichFilterTestswould otherwise strip). No behavior change.FilterTests, so the listing mirrors exactly what a run with that filter would execute.RegisterTestsAsyncis still called withisForExecution: false, soRegisterTestArgumentsAsyncstays skipped.isForExecution.Engine-runtime only; works identically in reflection and source-generated modes since both flow through
TestDiscoveryService. No snapshot/source-gen output changes.Tests
Adds
ListTestsFilterTests(reflection + source-generated modes):--list-testsstill lists everything (guards the no-filter path).Manually verified against
TUnit.TestProject:[Category=Pass]filterPass*, 0Fail*Pass1+Fail1present