From 0b5fc3e047fa94545d3aea04c99ca6f3b74b3093 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 28 May 2026 21:05:28 +0100 Subject: [PATCH 1/2] perf: use [GeneratedRegex] in MetadataFilterMatcher Replace runtime Regex.Replace calls in ExtractFilterHints and the metadata-match path with a source-generated PropertyFilterRegex on net7+, falling back to a cached compiled Regex on netstandard2.0. Avoids re-parsing the pattern per call and is Native AOT friendly. Closes #6036 --- TUnit.Engine/Services/MetadataFilterMatcher.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/TUnit.Engine/Services/MetadataFilterMatcher.cs b/TUnit.Engine/Services/MetadataFilterMatcher.cs index 28ee3729e7..451df8d915 100644 --- a/TUnit.Engine/Services/MetadataFilterMatcher.cs +++ b/TUnit.Engine/Services/MetadataFilterMatcher.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Text.RegularExpressions; #if NET8_0_OR_GREATER using System.Buffers; #endif @@ -117,7 +118,7 @@ public bool CouldMatch(string testClassName, string testMethodName) /// Implementation of metadata filter matching logic extracted from TestBuilder. /// Evaluates if test metadata could match an execution filter without building tests. /// -internal sealed class MetadataFilterMatcher : IMetadataFilterMatcher +internal sealed partial class MetadataFilterMatcher : IMetadataFilterMatcher { #pragma warning disable TPEXP private static readonly ConstructorInfo _treeNodeFilterConstructor = @@ -129,6 +130,16 @@ internal sealed class MetadataFilterMatcher : IMetadataFilterMatcher private static readonly ConcurrentDictionary _strippedFilterCache = new(); + // Matches property-bag filters like [key=value]; used to strip them before path parsing. +#if NET7_0_OR_GREATER + [GeneratedRegex(@"\[([^\]]*)\]")] + private static partial Regex PropertyFilterRegex(); +#else + private static readonly Regex _propertyFilterRegex = new(@"\[([^\]]*)\]", RegexOptions.Compiled); + + private static Regex PropertyFilterRegex() => _propertyFilterRegex; +#endif + /// /// Extract hints from a filter that can be used to pre-filter test sources by type. /// @@ -149,7 +160,7 @@ public static FilterHints ExtractFilterHints(ITestExecutionFilter? filter) // Strip property filters like [key=value] if (filterString.Contains('[')) { - filterString = System.Text.RegularExpressions.Regex.Replace(filterString, @"\[([^\]]*)\]", ""); + filterString = PropertyFilterRegex().Replace(filterString, ""); } // Parse path: /{assembly}/{namespace}/{className}/{methodName} @@ -407,7 +418,7 @@ private static bool CouldMatchTreeNodeFilter(TreeNodeFilter filter, TestMetadata if (filterString.Contains('[')) { var strippedFilterString = _strippedFilterCache.GetOrAdd(filterString, - static fs => System.Text.RegularExpressions.Regex.Replace(fs, @"\[([^\]]*)\]", "")); + static fs => PropertyFilterRegex().Replace(fs, "")); pathOnlyFilter = CreateTreeNodeFilterViaReflection(strippedFilterString); } else From 856ad920469ba06d866038c0eef54644a178b9a0 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 28 May 2026 21:36:31 +0100 Subject: [PATCH 2/2] perf: use NET8 guard and drop unused capture group in PropertyFilterRegex Address review: align #if with rest of file (net7 TFM never built) and remove the discarded capturing group to avoid per-match capture overhead. --- TUnit.Engine/Services/MetadataFilterMatcher.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TUnit.Engine/Services/MetadataFilterMatcher.cs b/TUnit.Engine/Services/MetadataFilterMatcher.cs index 451df8d915..9d22850128 100644 --- a/TUnit.Engine/Services/MetadataFilterMatcher.cs +++ b/TUnit.Engine/Services/MetadataFilterMatcher.cs @@ -131,11 +131,11 @@ internal sealed partial class MetadataFilterMatcher : IMetadataFilterMatcher private static readonly ConcurrentDictionary _strippedFilterCache = new(); // Matches property-bag filters like [key=value]; used to strip them before path parsing. -#if NET7_0_OR_GREATER - [GeneratedRegex(@"\[([^\]]*)\]")] +#if NET8_0_OR_GREATER + [GeneratedRegex(@"\[[^\]]*\]")] private static partial Regex PropertyFilterRegex(); #else - private static readonly Regex _propertyFilterRegex = new(@"\[([^\]]*)\]", RegexOptions.Compiled); + private static readonly Regex _propertyFilterRegex = new(@"\[[^\]]*\]", RegexOptions.Compiled); private static Regex PropertyFilterRegex() => _propertyFilterRegex; #endif