Skip to content

MetadataFilterMatcher.ExtractFilterHints misinterprets parenthesised path segments as literal method names #6026

Closed

Description

@Evangelink

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 methodName is set to the literal string "(MyTest1)".
  • AotTestDataCollector then calls filterHints.CouldDescriptorMatch(descriptor), which requires descriptor.MethodName == "(MyTest1)" — never true.
  • All descriptors are filtered out before MTP's TreeNodeFilter.MatchesFilter ever 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

I'm happy to send a PR if a maintainer can confirm the approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions