Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,20 @@ public bool Matches(UnitTestElement testElement)
}
}
}

/// <summary>
/// Adapter-boundary <see cref="ITestElementFilterProvider"/> that closes over the VSTest discovery/run context
/// and produces the neutral <see cref="ITestElementFilter"/> via <see cref="TestMethodFilter"/>. Created at the
/// boundary and passed into the platform services engine/discoverer so the filter (and any parse-error report)
/// is built at the exact point the engine needs it, preserving the original per-source timing.
/// </summary>
internal sealed class TestElementFilterProvider : ITestElementFilterProvider
{
private readonly TestMethodFilter _testMethodFilter = new();
private readonly IDiscoveryContext? _context;

public TestElementFilterProvider(IDiscoveryContext? context) => _context = context;

public ITestElementFilter? GetTestElementFilter(IAdapterMessageLogger logger, out bool filterHasError)
=> _testMethodFilter.GetTestElementFilter(_context, logger, out filterHasError);
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ internal async Task DiscoverTestsAsync(IEnumerable<string> sources, IDiscoveryCo
IAdapterMessageLogger adapterLogger = logger.ToAdapterMessageLogger();
if (MSTestDiscovererHelpers.InitializeDiscovery(sources, discoveryContext, adapterLogger, configuration, _testSourceHandler))
{
new UnitTestDiscoverer(_testSourceHandler).DiscoverTests(sources, adapterLogger, discoverySink.ToUnitTestElementSink(), discoveryContext, isMTP);
new UnitTestDiscoverer(_testSourceHandler).DiscoverTests(sources, adapterLogger, discoverySink.ToUnitTestElementSink(), discoveryContext, new TestElementFilterProvider(discoveryContext), isMTP);
}
}
finally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ internal async Task RunTestsAsync(IEnumerable<TestCase>? tests, IRunContext? run
// execution engine reports results through this neutral recorder and never constructs VSTest results.
ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings);

await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(testElements, runContext, frameworkHandle, testResultRecorder, testRunToken).ConfigureAwait(false)).ConfigureAwait(false);
await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(testElements, runContext, frameworkHandle, testResultRecorder, new TestElementFilterProvider(runContext), testRunToken).ConfigureAwait(false)).ConfigureAwait(false);
}
finally
{
Expand Down Expand Up @@ -214,7 +214,7 @@ internal async Task RunTestsAsync(IEnumerable<string>? sources, IRunContext? run
// execution engine reports results through this neutral recorder and never constructs VSTest results.
ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings);

await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(sources, runContext, frameworkHandle, testResultRecorder, testSourceHandler, isMTP, testRunToken).ConfigureAwait(false)).ConfigureAwait(false);
await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(sources, runContext, frameworkHandle, testResultRecorder, new TestElementFilterProvider(runContext), testSourceHandler, isMTP, testRunToken).ConfigureAwait(false)).ConfigureAwait(false);
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
[SuppressMessage("Performance", "CA1852: Seal internal types", Justification = "Overrides required for testability")]
internal class UnitTestDiscoverer
{
private readonly TestMethodFilter _testMethodFilter;

internal UnitTestDiscoverer(ITestSourceHandler testSourceHandler)
{
_testMethodFilter = new TestMethodFilter();
_testSource = testSourceHandler;
}
internal UnitTestDiscoverer(ITestSourceHandler testSourceHandler) => _testSource = testSourceHandler;

/// <summary>
/// Discovers the tests available from the provided sources.
Expand All @@ -27,17 +21,19 @@ internal UnitTestDiscoverer(ITestSourceHandler testSourceHandler)
/// <param name="logger"> The logger. </param>
/// <param name="discoverySink"> The discovery Sink. </param>
/// <param name="discoveryContext"> The discovery context. </param>
/// <param name="filterProvider">Provider for the test filter, or <see langword="null"/> for no filter.</param>
/// <param name="isMTP">Flag set to true when the platform running discovery is MTP.</param>
internal void DiscoverTests(
IEnumerable<string> sources,
IAdapterMessageLogger logger,
IUnitTestElementSink discoverySink,
IDiscoveryContext discoveryContext,
ITestElementFilterProvider? filterProvider,
bool isMTP)
{
foreach (string source in sources)
{
DiscoverTestsInSource(source, logger, discoverySink, discoveryContext, isMTP);
DiscoverTestsInSource(source, logger, discoverySink, discoveryContext, filterProvider, isMTP);
}
}

Expand All @@ -48,12 +44,14 @@ internal void DiscoverTests(
/// <param name="logger"> The logger. </param>
/// <param name="discoverySink"> The discovery Sink. </param>
/// <param name="discoveryContext"> The discovery context. </param>
/// <param name="filterProvider">Provider for the test filter, or <see langword="null"/> for no filter.</param>
/// <param name="isMTP">Flag set to true when the platform running discovery is MTP.</param>
internal virtual void DiscoverTestsInSource(
string source,
IAdapterMessageLogger logger,
IUnitTestElementSink discoverySink,
IDiscoveryContext? discoveryContext,
ITestElementFilterProvider? filterProvider,
bool isMTP)
{
ICollection<UnitTestElement>? testElements = AssemblyEnumeratorWrapper.GetTests(source, discoveryContext?.RunSettings?.SettingsXml, _testSource, isMTP, out List<string> warnings);
Expand Down Expand Up @@ -103,15 +101,16 @@ internal virtual void DiscoverTestsInSource(
source);
}

SendTestCases(testElements, discoverySink, discoveryContext, logger);
SendTestCases(testElements, discoverySink, filterProvider, logger);
}

private readonly ITestSourceHandler _testSource;

internal void SendTestCases(IEnumerable<UnitTestElement> testElements, IUnitTestElementSink discoverySink, IDiscoveryContext? discoveryContext, IAdapterMessageLogger logger)
internal static void SendTestCases(IEnumerable<UnitTestElement> testElements, IUnitTestElementSink discoverySink, ITestElementFilterProvider? filterProvider, IAdapterMessageLogger logger)
{
// Get filter and skip discovery in case filter expression has parsing error.
ITestElementFilter? filter = _testMethodFilter.GetTestElementFilter(discoveryContext, logger, out bool filterHasError);
bool filterHasError = false;
ITestElementFilter? filter = filterProvider?.GetTestElementFilter(logger, out filterHasError);
if (filterHasError)
{
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ internal partial class TestExecutionManager
/// <param name="runContext">The run context.</param>
/// <param name="frameworkHandle">Handle to record test start/end/results.</param>
/// <param name="testResultRecorder">Recorder used to report test results back to the host.</param>
/// <param name="filterProvider">Provider for the test filter, or <see langword="null"/> for no filter.</param>
/// <param name="isDeploymentDone">Indicates if deployment is done.</param>
internal virtual async Task ExecuteTestsAsync(IEnumerable<UnitTestElement> tests, IRunContext? runContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, bool isDeploymentDone)
internal virtual async Task ExecuteTestsAsync(IEnumerable<UnitTestElement> tests, IRunContext? runContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, bool isDeploymentDone)
{
_testResultRecorder = testResultRecorder;
_testElementFilterProvider = filterProvider;

InitializeRandomTestOrder(frameworkHandle.ToAdapterMessageLogger());

Expand Down Expand Up @@ -74,7 +76,8 @@ private async Task ExecuteTestsInSourceAsync(IEnumerable<UnitTestElement> tests,
}

// Default test set is filtered tests based on user provided filter criteria
ITestElementFilter? filter = _testMethodFilter.GetTestElementFilter(runContext, adapterMessageLogger, out bool filterHasError);
bool filterHasError = false;
ITestElementFilter? filter = _testElementFilterProvider?.GetTestElementFilter(adapterMessageLogger, out filterHasError);
if (filterHasError)
{
// Bail out without processing everything else below.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public TestExecutionManager()

internal TestExecutionManager(Func<Func<Task>, Task>? taskFactory = null)
{
_testMethodFilter = new TestMethodFilter();
_sessionParameters = new Dictionary<string, object>();
_taskFactory = taskFactory ?? DefaultFactoryAsync;
}
Expand All @@ -74,9 +73,11 @@ private static Task DefaultFactoryAsync(Func<Task> taskGetter)
}

/// <summary>
/// Gets or sets method filter for filtering tests.
/// Provider for the test filter, supplied at the adapter boundary and set once per run in
/// <see cref="ExecuteTestsAsync"/> so the filter is built at the same point (and with the same per-source
/// timing) as before.
/// </summary>
private readonly TestMethodFilter _testMethodFilter;
private ITestElementFilterProvider? _testElementFilterProvider;

#if !WINDOWS_UWP && !WIN_UI
/// <summary>
Expand All @@ -92,8 +93,9 @@ private static Task DefaultFactoryAsync(Func<Task> taskGetter)
/// <param name="runContext">Context to use when executing the tests.</param>
/// <param name="frameworkHandle">Handle to the framework to record results and to do framework operations.</param>
/// <param name="testResultRecorder">Recorder used to report test results back to the host.</param>
/// <param name="filterProvider">Provider for the test filter, or <see langword="null"/> for no filter.</param>
/// <param name="runCancellationToken">Test run cancellation token.</param>
internal async Task RunTestsAsync(IEnumerable<UnitTestElement> tests, IRunContext? runContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, TestRunCancellationToken runCancellationToken)
internal async Task RunTestsAsync(IEnumerable<UnitTestElement> tests, IRunContext? runContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, TestRunCancellationToken runCancellationToken)
{
DebugEx.Assert(tests != null, "tests");
DebugEx.Assert(runContext != null, "runContext");
Expand All @@ -113,7 +115,7 @@ internal async Task RunTestsAsync(IEnumerable<UnitTestElement> tests, IRunContex
CacheSessionParameters(runContext?.RunSettings?.SettingsXml, frameworkHandle.ToAdapterMessageLogger());

// Execute the tests
await ExecuteTestsAsync(tests, runContext, frameworkHandle, testResultRecorder, isDeploymentDone).ConfigureAwait(false);
await ExecuteTestsAsync(tests, runContext, frameworkHandle, testResultRecorder, filterProvider, isDeploymentDone).ConfigureAwait(false);

#if !WINDOWS_UWP && !WIN_UI
if (!_hasAnyTestFailed)
Expand All @@ -123,7 +125,7 @@ internal async Task RunTestsAsync(IEnumerable<UnitTestElement> tests, IRunContex
#endif
}

internal async Task RunTestsAsync(IEnumerable<string> sources, IRunContext? runContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, ITestSourceHandler testSourceHandler, bool isMTP, TestRunCancellationToken cancellationToken)
internal async Task RunTestsAsync(IEnumerable<string> sources, IRunContext? runContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, ITestSourceHandler testSourceHandler, bool isMTP, TestRunCancellationToken cancellationToken)
{
_testRunCancellationToken = cancellationToken;
PlatformServiceProvider.Instance.TestRunCancellationToken = _testRunCancellationToken;
Expand All @@ -140,7 +142,7 @@ internal async Task RunTestsAsync(IEnumerable<string> sources, IRunContext? runC
_testRunCancellationToken?.ThrowIfCancellationRequested();

// discover the tests
GetUnitTestDiscoverer(testSourceHandler).DiscoverTestsInSource(source, logger, discoverySink, runContext, isMTP);
GetUnitTestDiscoverer(testSourceHandler).DiscoverTestsInSource(source, logger, discoverySink, runContext, filterProvider, isMTP);
tests.AddRange(discoverySink.TestElements);

// Clear discoverSinksTests so that it just stores test for one source at one point of time
Expand All @@ -157,7 +159,7 @@ internal async Task RunTestsAsync(IEnumerable<string> sources, IRunContext? runC
CacheSessionParameters(runContext?.RunSettings?.SettingsXml, frameworkHandle.ToAdapterMessageLogger());

// Run tests.
await ExecuteTestsAsync(tests, runContext, frameworkHandle, testResultRecorder, isDeploymentDone).ConfigureAwait(false);
await ExecuteTestsAsync(tests, runContext, frameworkHandle, testResultRecorder, filterProvider, isDeploymentDone).ConfigureAwait(false);

#if !WINDOWS_UWP && !WIN_UI
if (!_hasAnyTestFailed)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;

/// <summary>
/// Platform-agnostic factory that produces the <see cref="ITestElementFilter"/> in effect for the current
/// run or discovery.
/// </summary>
/// <remarks>
/// This abstraction lets the platform services discovery and execution pipelines obtain the active filter
/// without taking a dependency on a specific test platform's filter object model (for example the VSTest
/// <c>ITestCaseFilterExpression</c> parsed from an <c>IRunContext</c> / <c>IDiscoveryContext</c>). The concrete
/// provider is created at the adapter boundary, closing over the host filter context; the engine invokes it at
/// the exact point it needs the filter so parse-error reporting keeps the same timing and per-source semantics.
/// </remarks>
internal interface ITestElementFilterProvider
{
/// <summary>
/// Builds the <see cref="ITestElementFilter"/> for the current run/discovery.
/// </summary>
/// <param name="logger">Logger used to report a filter parsing error.</param>
/// <param name="filterHasError">
/// Set to <see langword="true"/> when the filter is unsupported or failed to parse; in that case the caller
/// should report no tests for the affected source.
/// </param>
/// <returns>
/// The filter to apply, or <see langword="null"/> when no filter applies (every element is included).
/// </returns>
ITestElementFilter? GetTestElementFilter(IAdapterMessageLogger logger, out bool filterHasError);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal static ImmutableArray<TestCase> DiscoverTests(string assemblyPath, stri
string runSettingsXml = GetRunSettingsXml(string.Empty);
var context = new InternalDiscoveryContext(runSettingsXml, testCaseFilter);

unitTestDiscoverer.DiscoverTestsInSource(assemblyPath, logger.ToAdapterMessageLogger(), sink.ToUnitTestElementSink(), context, false);
unitTestDiscoverer.DiscoverTestsInSource(assemblyPath, logger.ToAdapterMessageLogger(), sink.ToUnitTestElementSink(), context, new TestElementFilterProvider(context), false);

return sink.DiscoveredTests;
}
Expand All @@ -41,7 +41,7 @@ internal static async Task<ImmutableArray<TestResult>> RunTestsAsync(IEnumerable
var frameworkHandle = new InternalFrameworkHandle();

ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings);
await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), null, frameworkHandle, testResultRecorder, false);
await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), null, frameworkHandle, testResultRecorder, filterProvider: null, false);
return frameworkHandle.GetFlattenedTestResults();
}

Expand All @@ -54,7 +54,7 @@ internal static async Task<ImmutableArray<TestResult>> RunTestsAsync(IEnumerable
var runContext = new InternalRunContext(runSettingsXml, testCaseFilter);

ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings);
await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), runContext, frameworkHandle, testResultRecorder, false);
await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), runContext, frameworkHandle, testResultRecorder, new TestElementFilterProvider(runContext), false);
return frameworkHandle.GetFlattenedTestResults();
}

Expand Down
Loading
Loading