Repository navigation
Description
Summary
MetadataFilterMatcher.ExtractFilterHints silently drops valid --treenode-filter patterns because its IsWildcard helper does not recognise the grouping characters (, ), |, & as non-literal. As a result, any parenthesised expression in the last path segment is treated as a literal method name, which AotTestDataCollector then uses to reject every discovered test before MTP's actual TreeNodeFilter is consulted.
Reproduction
Minimal TUnit project (any recent version, reproduced on 1.12.65 and confirmed to still affect 1.45.29 / main):
public class MyTests
{
[Test] public async Task MyTest1() => await Assert.That(true).IsTrue();
[Test] public async Task MyTest2() => await Assert.That(true).IsTrue();
}dotnet run --treenode-filter "<pattern>":
| Pattern | Expected | Actual |
|---|---|---|
/*/*/*/MyTest1 |
1 | 1 ✅ |
/*/*/*/(MyTest1) |
1 | 0 ❌ |
/*/*/*/(MyTest1|MyTest2) |
2 | 0 ❌ |
/*/*/*/(MyTest1*) |
1 | 1 ✅ (workaround: the * makes IsWildcard true) |
Root cause
In TUnit.Engine/Services/MetadataFilterMatcher.cs (lines approx. 257-341 on main):
// Parse path: /{assembly}/{namespace}/{className}/{methodName}
var parts = filterString.Split('/');
...
if (parts.Length > 4 && !IsWildcard(parts[4]))
{
methodName = parts[4];
}
...
private static bool IsWildcard(string value)
=> string.IsNullOrEmpty(value)
|| value == "*"
|| value.Contains('*')
|| value.Contains('?');For /*/*/*/(MyTest1):
parts[4]is"(MyTest1)".IsWildcard("(MyTest1)")returns false (no*/?).- So
methodNameis set to the literal string"(MyTest1)". AotTestDataCollectorthen callsfilterHints.CouldDescriptorMatch(descriptor), which requiresdescriptor.MethodName == "(MyTest1)"— never true.- All descriptors are filtered out before MTP's
TreeNodeFilter.MatchesFilterever runs, so discovery returns zero.
Verification that MTP itself is correct
A reflection probe against Microsoft.Testing.Platform 2.0.2 (the version TUnit currently bundles) shows that TreeNodeFilter("/*/*/*/(MyTest1)").MatchesFilter("/YourTestProjectNameHere/YourTestProjectNameHere/MyTests/MyTest1", new PropertyBag()) returns True. The same holds for every failing pattern above. The testfx unit-test OrExpression_WorksForSinglePathSegmentInsideParentheses (microsoft/testfx#7415) further confirms this.
So the regression is entirely in TUnit's pre-filter, not in MTP.
Suggested fix
Make IsWildcard (or, better, ExtractFilterHints itself) recognise that any segment containing one of the TreeNodeFilter grouping/operator characters cannot safely be treated as a literal:
private static bool IsWildcard(string value)
=> string.IsNullOrEmpty(value)
|| value == "*"
|| value.AsSpan().IndexOfAny('*', '?', '(', ')', '|', '&', '!') >= 0;A more robust approach would be to only emit a hint when the segment is a plain identifier (^[A-Za-z_][A-Za-z0-9_]*$), and otherwise skip the hint and let MTP's TreeNodeFilter do the matching authoritatively.
Related
- testfx issue: --treenode-filter pattern matching is at least non obvious, potentially errored microsoft/testfx#7300
- Previously closed TUnit issue tracking the same symptoms: [Bug]: tunit filtering with conditional operators #4580
I'm happy to send a PR if a maintainer can confirm the approach.
Activity
fix: treenode filter pre-filter rejects parenthesised segments (#6026) (
docs: correct root cause of the OR-filter restriction to TUnit, not MTP
fix: gate OR filter translation on the resolved TUnit.Engine version
docs: correct root cause of the OR-filter restriction to TUnit, not MTP
fix: gate OR filter translation on the resolved TUnit.Engine version
docs: correct root cause of the OR-filter restriction to TUnit, not MTP
fix: gate OR filter translation on the resolved TUnit.Engine version
fix: complete TUnit Microsoft.Testing.Platform support (#1323)