Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
30f1531
First step
Youssef1313 Apr 1, 2026
4719e01
Finalize
Youssef1313 Apr 7, 2026
8e63e0d
Update Microsoft.TestPlatform.Filter.Source.csproj
Youssef1313 Apr 8, 2026
0374178
Update Microsoft.TestPlatform.Filter.Source.nuspec
Youssef1313 Apr 8, 2026
f1255ef
Update src/Microsoft.TestPlatform.Filter.Source/Microsoft.TestPlatfor…
Youssef1313 Apr 8, 2026
ac83d12
Update Microsoft.TestPlatform.Filter.Source.csproj
Youssef1313 Apr 8, 2026
64adbdb
Update Microsoft.TestPlatform.Filter.Source.csproj
Youssef1313 Apr 8, 2026
47a1132
Update verify-nupkgs.ps1
Youssef1313 Apr 8, 2026
916eaee
Update verify-nupkgs.ps1
Youssef1313 Apr 8, 2026
64f7ac8
Update Microsoft.TestPlatform.Filter.Source.nuspec
Youssef1313 Apr 8, 2026
1c44c61
Update verify-nupkgs.ps1
Youssef1313 Apr 8, 2026
6044993
Add test project for Microsoft.TestPlatform.Filter.Source source-only…
Copilot Apr 8, 2026
69888fa
Add test project to solution
Youssef1313 Apr 8, 2026
3df6e64
Try to simplify
Youssef1313 Apr 8, 2026
891d60b
Update Directory.Build.targets
Youssef1313 Apr 9, 2026
2ff0d7a
Update Microsoft.TestPlatform.Filter.Source.UnitTests.csproj
Youssef1313 Apr 9, 2026
c4d7641
Update Microsoft.TestPlatform.Filter.Source.UnitTests.csproj
Youssef1313 Apr 9, 2026
9c6130d
Fix build
Youssef1313 Apr 9, 2026
402ecd1
Use .g.cs in package
Youssef1313 Apr 9, 2026
e8df0f1
Update README.md
Youssef1313 Apr 9, 2026
52ced56
Address few comments
Youssef1313 Apr 9, 2026
42721e3
Add Filter.Source integration tests in Microsoft.TestPlatform.Library…
Copilot Apr 10, 2026
f08d6eb
chore: add #nullable enable to all Filter.Source C# files (#15659)
Copilot Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
<!-- Temporary workaround for Arcade issue in net9-preview5 -->
<_NetFrameworkHostedCompilersVersion Condition="'$(_NetFrameworkHostedCompilersVersion)' == ''">4.11.0-3.24280.3</_NetFrameworkHostedCompilersVersion>
</PropertyGroup>


<PropertyGroup>
<DefineConstants Condition="'$(IsFilterSourcePackage)' != 'true'">$(DefineConstants);IS_VSTEST_REPO</DefineConstants>
</PropertyGroup>

<Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
<!-- Override the version of imported package to avoid infinite restore loop in VisualStudio, https://github.com/dotnet/arcade/issues/16228 -->
<ItemGroup>
Expand All @@ -22,7 +26,7 @@

<!-- Test project settings -->
<Choose>
<When Condition="$(TestProject) == 'true'">
<When Condition="$(TestProject) == 'true' AND '$(ExcludeRepoTestProjectSettings)' != 'true'">
<PropertyGroup>
<!-- Suppress warnings about testhost being x64 (AMD64)/x86 when imported into AnyCPU (MSIL) test projects. -->
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
Expand Down
2 changes: 2 additions & 0 deletions TestPlatform.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
<Project Path="src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.csproj" />
<Project Path="src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj" />
<Project Path="src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj" />
<Project Path="src/Microsoft.TestPlatform.Filter.Source/Microsoft.TestPlatform.Filter.Source.csproj" />
<Project Path="src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj" />
<Project Path="src/Microsoft.TestPlatform.PlatformAbstractions/Microsoft.TestPlatform.PlatformAbstractions.csproj" />
<Project Path="src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.csproj" />
Expand Down Expand Up @@ -115,6 +116,7 @@
<Project Path="test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.UnitTests.csproj" />
<Project Path="test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj" />
<Project Path="test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Microsoft.TestPlatform.CrossPlatEngine.UnitTests.csproj" />
<Project Path="test/Microsoft.TestPlatform.Filter.Source.UnitTests/Microsoft.TestPlatform.Filter.Source.UnitTests.csproj" />
<Project Path="test/Microsoft.TestPlatform.ObjectModel.UnitTests/Microsoft.TestPlatform.ObjectModel.UnitTests.csproj" />
<Project Path="test/Microsoft.TestPlatform.Utilities.UnitTests/Microsoft.TestPlatform.Utilities.UnitTests.csproj" />
</Folder>
Expand Down
Empty file modified eng/common/tools.sh
100644 → 100755
Empty file.
1 change: 1 addition & 0 deletions eng/verify-nupkgs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function Verify-Nuget-Packages {
"Microsoft.TestPlatform.TestHost" = 64
"Microsoft.TestPlatform.TranslationLayer" = 175
"Microsoft.TestPlatform.Internal.Uwp" = 39
"Microsoft.TestPlatform.Filter.Source" = 13
}

$packageDirectory = Resolve-Path "$PSScriptRoot/../artifacts/packages/$configuration"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
<FromP2P>true</FromP2P>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="$(RepoRoot)src/Microsoft.TestPlatform.Filter.Source/Condition.cs" />
<Compile Include="$(RepoRoot)src/Microsoft.TestPlatform.Filter.Source/FastFilter.cs" />
<Compile Include="$(RepoRoot)src/Microsoft.TestPlatform.Filter.Source/FilterExpression.cs" />
<Compile Include="$(RepoRoot)src/Microsoft.TestPlatform.Filter.Source/FilterExpressionWrapper.cs" />
<Compile Include="$(RepoRoot)src/Microsoft.TestPlatform.Filter.Source/TestCaseFilterExpression.cs" />
</ItemGroup>
<ItemGroup>
<Compile Update="NullableHelpers.cs">
<DesignTime>True</DesignTime>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
#if IS_VSTEST_REPO
using System.Diagnostics.CodeAnalysis;
#endif
using System.Linq;
using System.Text;

#if !IS_VSTEST_REPO
using Microsoft.CodeAnalysis;
#endif

#if IS_VSTEST_REPO
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
#endif

using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;

using CommonResources = Microsoft.VisualStudio.TestPlatform.Common.Resources.Resources;
#if IS_VSTEST_REPO
using static Microsoft.VisualStudio.TestPlatform.Common.Resources.Resources;
#endif

namespace Microsoft.VisualStudio.TestPlatform.Common.Filtering;

#if !IS_VSTEST_REPO
[Embedded]
#endif
internal enum Operation
{
Equal,
Expand All @@ -28,6 +44,9 @@ internal enum Operation
/// Precedence(And) > Precedence(Or)
/// Precedence of OpenBrace and CloseBrace operators is not used, instead parsing code takes care of same.
/// </summary>
#if !IS_VSTEST_REPO
[Embedded]
#endif
internal enum Operator
{
None,
Expand All @@ -40,6 +59,9 @@ internal enum Operator
/// <summary>
/// Represents a condition in filter expression.
/// </summary>
#if !IS_VSTEST_REPO
[Embedded]
#endif
internal sealed class Condition
{
/// <summary>
Expand All @@ -52,6 +74,14 @@ internal sealed class Condition
/// </summary>
public const Operation DefaultOperation = Operation.Contains;

#if !IS_VSTEST_REPO
private const string TestCaseFilterFormatException = "Incorrect format for TestCaseFilter {0}. Specify the correct format and try again. Note that the incorrect format can lead to no test getting executed.";

private const string InvalidCondition = "Error: Invalid Condition '{0}'";

private const string InvalidOperator = "Error: Invalid operator '{0}'";
#endif

internal Condition(string name, Operation operation, string value)
{
Name = name;
Expand Down Expand Up @@ -97,8 +127,10 @@ private bool EvaluateContainsOperation(string[]? multiValue)
{
foreach (string propertyValue in multiValue)
{
#if IS_VSTEST_REPO
TPDebug.Assert(null != propertyValue, "PropertyValue can not be null.");
if (propertyValue.IndexOf(Value, StringComparison.OrdinalIgnoreCase) != -1)
#endif
if (propertyValue!.IndexOf(Value, StringComparison.OrdinalIgnoreCase) != -1)
{
return true;
}
Expand All @@ -113,7 +145,9 @@ private bool EvaluateContainsOperation(string[]? multiValue)
/// </summary>
internal bool Evaluate(Func<string, object?> propertyValueProvider)
{
#if IS_VSTEST_REPO
ValidateArg.NotNull(propertyValueProvider, nameof(propertyValueProvider));
#endif
var multiValue = GetPropertyValue(propertyValueProvider);
var result = Operation switch
{
Expand All @@ -136,17 +170,21 @@ internal bool Evaluate(Func<string, object?> propertyValueProvider)
/// </summary>
internal static Condition Parse(string? conditionString)
{
#if IS_VSTEST_REPO
if (conditionString.IsNullOrWhiteSpace())
#else
if (string.IsNullOrWhiteSpace(conditionString))
#endif
{
ThrownFormatExceptionForInvalidCondition(conditionString);
}

var parts = TokenizeFilterConditionString(conditionString).ToArray();
var parts = TokenizeFilterConditionString(conditionString!).ToArray();
if (parts.Length == 1)
{
// If only parameter values is passed, create condition with default property name,
// default operation and given condition string as parameter value.
return new Condition(DefaultPropertyName, DefaultOperation, FilterHelper.Unescape(conditionString.Trim()));
return new Condition(DefaultPropertyName, DefaultOperation, FilterHelper.Unescape(conditionString!.Trim()));
}

if (parts.Length != 3)
Expand All @@ -156,7 +194,11 @@ internal static Condition Parse(string? conditionString)

for (int index = 0; index < 3; index++)
{
#if IS_VSTEST_REPO
if (parts[index].IsNullOrWhiteSpace())
#else
if (string.IsNullOrWhiteSpace(parts[index]))
#endif
{
ThrownFormatExceptionForInvalidCondition(conditionString);
}
Expand All @@ -168,33 +210,42 @@ internal static Condition Parse(string? conditionString)
return condition;
}

#if IS_VSTEST_REPO
[DoesNotReturn]
#endif
private static void ThrownFormatExceptionForInvalidCondition(string? conditionString)
{
throw new FormatException(string.Format(CultureInfo.CurrentCulture, CommonResources.TestCaseFilterFormatException,
string.Format(CultureInfo.CurrentCulture, CommonResources.InvalidCondition, conditionString)));
throw new FormatException(string.Format(CultureInfo.CurrentCulture, TestCaseFilterFormatException,
string.Format(CultureInfo.CurrentCulture, InvalidCondition, conditionString)));
}

/// <summary>
/// Check if condition validates any property in properties.
/// </summary>
#if IS_VSTEST_REPO
internal bool ValidForProperties(IEnumerable<string> properties, Func<string, TestProperty?>? propertyProvider)
#else
internal bool ValidForProperties(IEnumerable<string> properties)
#endif
{
bool valid = false;

if (properties.Contains(Name, StringComparer.OrdinalIgnoreCase))
{
valid = true;

#if IS_VSTEST_REPO
// Check if operation ~ (Contains) is on property of type string.
if (Operation == Operation.Contains)
{
valid = ValidForContainsOperation(propertyProvider);
}
#endif
}
return valid;
}

#if IS_VSTEST_REPO
private bool ValidForContainsOperation(Func<string, TestProperty?>? propertyProvider)
{
bool valid = true;
Expand All @@ -215,6 +266,7 @@ private bool ValidForContainsOperation(Func<string, TestProperty?>? propertyProv
}
return valid;
}
#endif

/// <summary>
/// Return Operation corresponding to the operationString
Expand All @@ -227,7 +279,7 @@ private static Operation GetOperator(string operationString)
"!=" => Operation.NotEqual,
"~" => Operation.Contains,
"!~" => Operation.NotContains,
_ => throw new FormatException(string.Format(CultureInfo.CurrentCulture, CommonResources.TestCaseFilterFormatException, string.Format(CultureInfo.CurrentCulture, CommonResources.InvalidOperator, operationString))),
_ => throw new FormatException(string.Format(CultureInfo.CurrentCulture, TestCaseFilterFormatException, string.Format(CultureInfo.CurrentCulture, InvalidOperator, operationString))),
};
}

Expand Down
8 changes: 8 additions & 0 deletions src/Microsoft.TestPlatform.Filter.Source/EmbeddedAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// <auto-generated/>
#nullable enable
namespace Microsoft.CodeAnalysis
{
internal sealed partial class EmbeddedAttribute : global::System.Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,59 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System;
using System.Collections.Generic;
#if IS_VSTEST_REPO
using System.Collections.Immutable;
using System.Globalization;
#endif
using System.Linq;
using System.Text.RegularExpressions;

#if !IS_VSTEST_REPO
using Microsoft.CodeAnalysis;
#endif

#if IS_VSTEST_REPO
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
#endif

namespace Microsoft.VisualStudio.TestPlatform.Common.Filtering;

#if !IS_VSTEST_REPO
[Embedded]
#endif
internal sealed class FastFilter
{
#if IS_VSTEST_REPO
internal FastFilter(ImmutableDictionary<string, ISet<string>> filterProperties, Operation filterOperation, Operator filterOperator)
#else
internal FastFilter(Dictionary<string, ISet<string>> filterProperties, Operation filterOperation, Operator filterOperator)
#endif
{
#if IS_VSTEST_REPO
ValidateArg.NotNullOrEmpty(filterProperties, nameof(filterProperties));
#endif

FilterProperties = filterProperties;

IsFilteredOutWhenMatched =
(filterOperation != Operation.Equal || filterOperator != Operator.Or && filterOperator != Operator.None)
&& (filterOperation == Operation.NotEqual && (filterOperator == Operator.And || filterOperator == Operator.None)
? true
: throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Resources.FastFilterException)));
#if IS_VSTEST_REPO
: throw new ArgumentException(Resources.Resources.FastFilterException));
#else
: throw new ArgumentException("An error occurred while creating Fast filter."));
#endif
}

#if IS_VSTEST_REPO
internal ImmutableDictionary<string, ISet<string>> FilterProperties { get; }
#else
internal Dictionary<string, ISet<string>> FilterProperties { get; }
#endif

internal bool IsFilteredOutWhenMatched { get; }

Expand All @@ -49,7 +75,9 @@ internal FastFilter(ImmutableDictionary<string, ISet<string>> filterProperties,

internal bool Evaluate(Func<string, object?> propertyValueProvider)
{
#if IS_VSTEST_REPO
ValidateArg.NotNull(propertyValueProvider, nameof(propertyValueProvider));
#endif

bool matched = false;
foreach (var name in FilterProperties.Keys)
Expand Down Expand Up @@ -86,20 +114,22 @@ internal bool Evaluate(Func<string, object?> propertyValueProvider)
/// <returns>For matching, returns the result of matching, null if no match found. For replacement, returns the result of replacement.</returns>
private string? ApplyRegex(string value)
{
#if IS_VSTEST_REPO
TPDebug.Assert(PropertyValueRegex != null);
#endif

string? result = null;
if (PropertyValueRegexReplacement == null)
{
var match = PropertyValueRegex.Match(value);
var match = PropertyValueRegex!.Match(value);
if (match.Success)
{
result = match.Value;
}
}
else
{
result = PropertyValueRegex.Replace(value, PropertyValueRegexReplacement);
result = PropertyValueRegex!.Replace(value, PropertyValueRegexReplacement);
}
return result;
}
Expand Down Expand Up @@ -134,7 +164,12 @@ internal sealed class Builder

private bool _conditionEncountered;
private Operation _fastFilterOperation;

#if IS_VSTEST_REPO
private readonly ImmutableDictionary<string, ImmutableHashSet<string>.Builder>.Builder _filterDictionaryBuilder = ImmutableDictionary.CreateBuilder<string, ImmutableHashSet<string>.Builder>(StringComparer.OrdinalIgnoreCase);
#else
private readonly Dictionary<string, ISet<string>> _filterDictionaryBuilder = new Dictionary<string, ISet<string>>(StringComparer.OrdinalIgnoreCase);
#endif

private bool _containsValidFilter = true;

Expand Down Expand Up @@ -201,7 +236,11 @@ private void AddProperty(string name, string value)
{
if (!_filterDictionaryBuilder.TryGetValue(name, out var values))
{
#if IS_VSTEST_REPO
values = ImmutableHashSet.CreateBuilder<string>(StringComparer.OrdinalIgnoreCase);
#else
values = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
#endif
_filterDictionaryBuilder.Add(name, values);
}

Expand All @@ -212,7 +251,11 @@ private void AddProperty(string name, string value)
{
return ContainsValidFilter
? new FastFilter(
#if IS_VSTEST_REPO
_filterDictionaryBuilder.ToImmutableDictionary(kvp => kvp.Key, kvp => (ISet<string>)_filterDictionaryBuilder[kvp.Key].ToImmutable()),
#else
_filterDictionaryBuilder,
#endif
_fastFilterOperation,
_fastFilterOperator)
: null;
Expand Down
Loading
Loading