Skip to content

Commit

Permalink
Cleanup filter-related logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 committed Feb 4, 2025
1 parent 5906435 commit 37c7d91
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static void AddMSTest(this ITestApplicationBuilder testApplicationBuilder
testApplicationBuilder.AddRunSettingsService(extension);
testApplicationBuilder.AddTestCaseFilterService(extension);
testApplicationBuilder.AddTestRunParametersService(extension);
testApplicationBuilder.AddTreeNodeFilterService(extension);
#pragma warning disable TPEXP // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
testApplicationBuilder.AddMaximumFailedTestsService(extension);
#pragma warning restore TPEXP // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
using Microsoft.Testing.Extensions.VSTestBridge.CommandLine;
using Microsoft.Testing.Platform;
using Microsoft.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Requests;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;

namespace Microsoft.Testing.Extensions.VSTestBridge.ObjectModel;

internal abstract class ContextAdapterBase
{
protected ContextAdapterBase(ICommandLineOptions commandLineOptions)
protected ContextAdapterBase(ICommandLineOptions commandLineOptions, ITestExecutionFilter filter)
{
if (commandLineOptions.TryGetOptionArgumentList(
TestCaseFilterCommandLineOptionsProvider.TestCaseFilterOptionName,
Expand All @@ -21,16 +23,34 @@ protected ContextAdapterBase(ICommandLineOptions commandLineOptions)
{
FilterExpressionWrapper = new(filterExpressions[0]);
}

switch (filter)
{
case TestNodeUidListFilter uidListFilter:
FilterExpressionWrapper = new(CreateFilter(uidListFilter.TestNodeUids));
break;

case TreeNodeFilter treeNodeFilter:
TreeNodeFilter = treeNodeFilter;
break;
}
}

protected FilterExpressionWrapper? FilterExpressionWrapper { get; set; }

protected TreeNodeFilter? TreeNodeFilter { get; set; }

// NOTE: Implementation is borrowed from VSTest
// MSTest relies on this method existing and access it through reflection: https://github.com/microsoft/testfx/blob/main/src/Adapter/MSTest.TestAdapter/TestMethodFilter.cs#L115
public ITestCaseFilterExpression? GetTestCaseFilter(
IEnumerable<string>? supportedProperties,
Func<string, TestProperty?> propertyProvider)
{
if (TreeNodeFilter is not null)
{
return new TreeNodeFilterExpression(TreeNodeFilter, supportedProperties, propertyProvider);
}

if (FilterExpressionWrapper is null)
{
return null;
Expand Down Expand Up @@ -61,4 +81,59 @@ protected ContextAdapterBase(ICommandLineOptions commandLineOptions)

return adapterSpecificTestCaseFilter;
}

// We use heuristic to understand if the filter should be a TestCaseId or FullyQualifiedName.
// We know that in VSTest TestCaseId is a GUID and FullyQualifiedName is a string.
private static string CreateFilter(TestNodeUid[] testNodesUid)
{
StringBuilder filter = new();

for (int i = 0; i < testNodesUid.Length; i++)
{
if (Guid.TryParse(testNodesUid[i].Value, out Guid guid))
{
filter.Append("Id=");
filter.Append(guid.ToString());
}
else
{
TestNodeUid currentTestNodeUid = testNodesUid[i];
filter.Append("FullyQualifiedName=");
for (int k = 0; k < currentTestNodeUid.Value.Length; k++)
{
char currentChar = currentTestNodeUid.Value[k];
switch (currentChar)
{
case '\\':
case '(':
case ')':
case '&':
case '|':
case '=':
case '!':
case '~':
// If the symbol is not escaped, add an escape character.
if (i - 1 < 0 || currentTestNodeUid.Value[k - 1] != '\\')
{
filter.Append('\\');
}

filter.Append(currentChar);
break;

default:
filter.Append(currentChar);
break;
}
}
}

if (i != testNodesUid.Length - 1)
{
filter.Append('|');
}
}

return filter.ToString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Requests;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;

namespace Microsoft.Testing.Extensions.VSTestBridge.ObjectModel;
Expand All @@ -11,8 +12,8 @@ namespace Microsoft.Testing.Extensions.VSTestBridge.ObjectModel;
/// </summary>
internal sealed class DiscoveryContextAdapter : ContextAdapterBase, IDiscoveryContext
{
public DiscoveryContextAdapter(ICommandLineOptions commandLineOptions, IRunSettings? runSettings)
: base(commandLineOptions) => RunSettings = runSettings;
public DiscoveryContextAdapter(ICommandLineOptions commandLineOptions, IRunSettings? runSettings, ITestExecutionFilter filter)
: base(commandLineOptions, filter) => RunSettings = runSettings;

public IRunSettings? RunSettings { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using Microsoft.Testing.Platform;
using Microsoft.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Requests;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;

Expand All @@ -14,17 +13,15 @@ namespace Microsoft.Testing.Extensions.VSTestBridge.ObjectModel;
/// </summary>
internal sealed class RunContextAdapter : ContextAdapterBase, IRunContext
{
public RunContextAdapter(ICommandLineOptions commandLineOptions, IRunSettings runSettings, ITestExecutionFilter? filter)
: base(commandLineOptions)
public RunContextAdapter(ICommandLineOptions commandLineOptions, IRunSettings runSettings, ITestExecutionFilter filter)
: base(commandLineOptions, filter)
{
RoslynDebug.Assert(runSettings.SettingsXml is not null);

RunSettings = runSettings;

// Parse and take the results directory from the runsettings.
TestRunDirectory = XElement.Parse(runSettings.SettingsXml).Descendants("ResultsDirectory").SingleOrDefault()?.Value;

HandleFilter(filter);
}

// NOTE: Always false as it's TPv2 oriented and so not applicable to TA.
Expand Down Expand Up @@ -53,73 +50,4 @@ public RunContextAdapter(ICommandLineOptions commandLineOptions, IRunSettings ru

/// <inheritdoc />
public IRunSettings? RunSettings { get; }

private void HandleFilter(ITestExecutionFilter? filter)
{
// TODO: Handle TreeNodeFilter
switch (filter)
{
case TestNodeUidListFilter testNodeUidListFilter:
FilterExpressionWrapper = new(CreateFilter(testNodeUidListFilter.TestNodeUids));
break;

default:
break;
}
}

// We use heuristic to understand if the filter should be a TestCaseId or FullyQualifiedName.
// We know that in VSTest TestCaseId is a GUID and FullyQualifiedName is a string.
private static string CreateFilter(TestNodeUid[] testNodesUid)
{
StringBuilder filter = new();

for (int i = 0; i < testNodesUid.Length; i++)
{
if (Guid.TryParse(testNodesUid[i].Value, out Guid guid))
{
filter.Append("Id=");
filter.Append(guid.ToString());
}
else
{
TestNodeUid currentTestNodeUid = testNodesUid[i];
filter.Append("FullyQualifiedName=");
for (int k = 0; k < currentTestNodeUid.Value.Length; k++)
{
char currentChar = currentTestNodeUid.Value[k];
switch (currentChar)
{
case '\\':
case '(':
case ')':
case '&':
case '|':
case '=':
case '!':
case '~':
// If the symbol is not escaped, add an escape character.
if (i - 1 < 0 || currentTestNodeUid.Value[k - 1] != '\\')
{
filter.Append('\\');
}

filter.Append(currentChar);
break;

default:
filter.Append(currentChar);
break;
}
}
}

if (i != testNodesUid.Length - 1)
{
filter.Append('|');
}
}

return filter.ToString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform.Requests;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;

namespace Microsoft.Testing.Extensions.VSTestBridge.ObjectModel;

internal sealed class TreeNodeFilterExpression : ITestCaseFilterExpression
{
private TreeNodeFilter _treeNodeFilter;
private IEnumerable<string>? _supportedProperties;
private Func<string, TestProperty?> _propertyProvider;

public TreeNodeFilterExpression(TreeNodeFilter treeNodeFilter, IEnumerable<string>? supportedProperties, Func<string, TestProperty?> propertyProvider)
{
_treeNodeFilter = treeNodeFilter;
_supportedProperties = supportedProperties;
_propertyProvider = propertyProvider;
}

public string TestCaseFilterValue => _treeNodeFilter.Filter;

public bool MatchTestCase(TestCase testCase, Func<string, object?> propertyValueProvider)
=> true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static VSTestDiscoverTestExecutionRequest CreateRequest(

ICommandLineOptions commandLineOptions = serviceProvider.GetRequiredService<ICommandLineOptions>();
RunSettingsAdapter runSettings = new(commandLineOptions, fileSystem, configuration, clientInfo, loggerFactory, messageLogger);
DiscoveryContextAdapter discoveryContext = new(commandLineOptions, runSettings);
DiscoveryContextAdapter discoveryContext = new(commandLineOptions, runSettings, discoverTestExecutionRequest.Filter);

ITestApplicationModuleInfo testApplicationModuleInfo = serviceProvider.GetTestApplicationModuleInfo();
IMessageBus messageBus = serviceProvider.GetRequiredService<IMessageBus>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context)
try
{
DebugUtils.LaunchAttachDebugger();

Debugger.Launch();
Task convertedRequest = context.Request switch
{
VSTestDiscoverTestExecutionRequest discoverRequest =>
Expand Down

0 comments on commit 37c7d91

Please sign in to comment.