diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.DataRow.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.DataRow.cs new file mode 100644 index 0000000000..7635f0a0d8 --- /dev/null +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.DataRow.cs @@ -0,0 +1,107 @@ +// 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.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using UTF = Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; + +/// +/// Handles folded / DynamicData-style () data-driven tests. +/// +internal sealed partial class TestMethodRunner +{ + private async Task TryExecuteFoldedDataDrivenTestsAsync(List results) + { + bool hasTestDataSource = false; + var outerContext = (TestContextImplementation)_testContext.Context; + + foreach (Attribute attribute in PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(_testMethodInfo.MethodInfo)) + { + if (attribute is not UTF.ITestDataSource testDataSource) + { + continue; + } + + hasTestDataSource = true; + if (testDataSource is ITestDataSourceIgnoreCapability { IgnoreMessage: { } ignoreMessage }) + { + results.Add(TestResult.CreateIgnoredResult(ignoreMessage)); + continue; + } + + // This code is to execute tests. To discover the tests code is in AssemblyEnumerator.TryUnfoldITestDataSource. + // Any change made here should be reflected in AssemblyEnumerator.TryUnfoldITestDataSource as well. + bool dataSourceHasData = false; + foreach (object?[] data in testDataSource.GetData(_testMethodInfo.MethodInfo)) + { + dataSourceHasData = true; + + // Create a fresh TestContextImplementation per iteration so the folded path is + // structurally equivalent to the unfolded path (where each row gets its own + // context). This isolates per-row state (captured output, diagnostic messages, + // result files, outcome, exception, property bag mutations, ...) so a leak in + // any current or future TestContextImplementation field cannot accumulate across + // rows. See https://github.com/microsoft/testfx/issues/7933. + TestContextImplementation iterationContext = outerContext.CloneForDataDrivenIteration(); + try + { + TestResult[] testResults = await ExecuteTestWithDataSourceAsync(iterationContext, testDataSource, data, actualDataAlreadyHandledDuringDiscovery: false).ConfigureAwait(false); + + results.AddRange(testResults); + } + finally + { + _testMethodInfo.SetArguments(null); + iterationContext.Dispose(); + } + } + + if (!dataSourceHasData) + { + if (!MSTestSettings.CurrentSettings.ConsiderEmptyDataSourceAsInconclusive) + { + throw testDataSource.GetExceptionForEmptyDataSource(_testMethodInfo.MethodInfo); + } + + var inconclusiveResult = new TestResult + { + Outcome = UnitTestOutcome.Inconclusive, + }; + results.Add(inconclusiveResult); + } + } + + return hasTestDataSource; + } + + private async Task ExecuteTestWithDataRowAsync(ITestContext executionContext, object dataRow, int rowIndex) + { + string displayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, _test.DisplayName, rowIndex); + Stopwatch? stopwatch = null; + + TestResult[]? testResults; + try + { + stopwatch = Stopwatch.StartNew(); + executionContext.SetDataRow(dataRow); + testResults = await ExecuteTestAsync(executionContext, _testMethodInfo).ConfigureAwait(false); + } + finally + { + stopwatch?.Stop(); + executionContext.SetDataRow(null); + } + + foreach (TestResult testResult in testResults) + { + testResult.DisplayName = displayName; + testResult.Duration = stopwatch.Elapsed; + } + + return testResults; + } +} diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.DataSource.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.DataSource.cs new file mode 100644 index 0000000000..2c71dc5035 --- /dev/null +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.DataSource.cs @@ -0,0 +1,177 @@ +// 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.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.Internal; + +using UTF = Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; + +/// +/// Handles -attributed data-driven tests. +/// +internal sealed partial class TestMethodRunner +{ + private async Task TryExecuteDataSourceBasedTestsAsync(List results) + { + // Iterate cached attributes directly to preserve the previous semantics: + // execute only when there is exactly one DataSourceAttribute. + bool hasSingleDataSource = false; + foreach (Attribute attribute in PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(_testMethodInfo.MethodInfo)) + { + if (attribute is not DataSourceAttribute) + { + continue; + } + + if (hasSingleDataSource) + { + return false; + } + + hasSingleDataSource = true; + } + + if (!hasSingleDataSource) + { + return false; + } + + await ExecuteTestFromDataSourceAttributeAsync(results).ConfigureAwait(false); + return true; + } + + private async Task ExecuteTestFromDataSourceAttributeAsync(List results) + { + var watch = Stopwatch.StartNew(); + + try + { + IEnumerable? dataRows = PlatformServiceProvider.Instance.TestDataSource.GetData(_testMethodInfo, _testContext); + if (dataRows == null) + { + var inconclusiveResult = new TestResult + { + Outcome = UnitTestOutcome.Inconclusive, + Duration = watch.Elapsed, + }; + results.Add(inconclusiveResult); + return; + } + + try + { + int rowIndex = 0; + var outerContext = (TestContextImplementation)_testContext.Context; + + foreach (object dataRow in dataRows) + { + // Create a fresh TestContextImplementation per row for the same structural + // reason as in TryExecuteFoldedDataDrivenTestsAsync — each row should + // start with no accumulated per-test state. + TestContextImplementation iterationContext = outerContext.CloneForDataDrivenIteration(); + try + { + TestResult[] testResults = await ExecuteTestWithDataRowAsync(iterationContext, dataRow, rowIndex++).ConfigureAwait(false); + results.AddRange(testResults); + } + finally + { + iterationContext.Dispose(); + } + } + } + finally + { + _testContext.SetDataConnection(null); + _testContext.SetDataRow(null); + } + } + catch (Exception ex) + { + var failedResult = new TestResult + { + Outcome = UnitTestOutcome.Error, + TestFailureException = ex, + Duration = watch.Elapsed, + }; + results.Add(failedResult); + } + } + + private async Task ExecuteTestWithDataSourceAsync(ITestContext executionContext, UTF.ITestDataSource? testDataSource, object?[]? data, bool actualDataAlreadyHandledDuringDiscovery) + { + string? displayName = StringEx.IsNullOrWhiteSpace(_test.DisplayName) + ? _test.Name + : _test.DisplayName; + + string? displayNameFromTestDataRow = null; + string? ignoreFromTestDataRow = null; + if (!actualDataAlreadyHandledDuringDiscovery && data is not null && + TestDataSourceHelpers.TryHandleITestDataRow(data, _testMethodInfo.ParameterTypes, out data, out ignoreFromTestDataRow, out displayNameFromTestDataRow)) + { + // Handled already. + } + else if (!actualDataAlreadyHandledDuringDiscovery && TestDataSourceHelpers.IsDataConsideredSingleArgumentValue(data, _testMethodInfo.ParameterTypes)) + { + // SPECIAL CASE: + // This condition is a duplicate of the condition in GetInvokeResultAsync. + // + // The known scenario we know of that shows importance of that check is if we have DynamicData using this member + // + // public static IEnumerable GetData() + // { + // yield return new object[] { ("Hello", "World") }; + // } + // + // If the test method has a single parameter which is 'object[]', then we should pass the tuple array as is. + // Note that normally, the array in this code path represents the arguments of the test method. + // However, GetInvokeResultAsync uses the above check to mean "the whole array is the single argument to the test method" + } + else if (!actualDataAlreadyHandledDuringDiscovery && data?.Length == 1 && TestDataSourceHelpers.TryHandleTupleDataSource(data[0], _testMethodInfo.ParameterTypes, out object?[] tupleExpandedToArray)) + { + data = tupleExpandedToArray; + } + + // PERF: Extract ReflectionTestMethodInfo to avoid allocating it twice when testDataSource is not null + // and both GetDisplayName and ComputeDefaultDisplayName need to be consulted. + if (displayNameFromTestDataRow is null && testDataSource is not null) + { + ReflectionTestMethodInfo reflectionMethodInfo = _cachedReflectionMethodInfo ??= new ReflectionTestMethodInfo(_testMethodInfo.MethodInfo, _test.DisplayName); + displayName = testDataSource.GetDisplayName(reflectionMethodInfo, data) + ?? TestDataSourceUtilities.ComputeDefaultDisplayName(reflectionMethodInfo, data) + ?? displayName; + } + else + { + displayName = displayNameFromTestDataRow ?? displayName; + } + + var stopwatch = Stopwatch.StartNew(); + _testMethodInfo.SetArguments(data); + executionContext.SetTestData(data); + executionContext.SetDisplayName(displayName); + + TestResult[] testResults = ignoreFromTestDataRow is not null + ? [TestResult.CreateIgnoredResult(ignoreFromTestDataRow)] + : await ExecuteTestAsync(executionContext, _testMethodInfo).ConfigureAwait(false); + + stopwatch.Stop(); + + foreach (TestResult testResult in testResults) + { + if (testResult.Duration == TimeSpan.Zero) + { + testResult.Duration = stopwatch.Elapsed; + } + + testResult.DisplayName = displayName; + } + + return testResults; + } +} diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.Execution.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.Execution.cs new file mode 100644 index 0000000000..3289508e08 --- /dev/null +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.Execution.cs @@ -0,0 +1,89 @@ +// 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.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; + +/// +/// Core single-test execution logic. +/// +internal sealed partial class TestMethodRunner +{ + private async Task ExecuteTestAsync(ITestContext executionContext, TestMethodInfo testMethodInfo) + { + try + { + ExecutionContext? capturedContext = testMethodInfo.Parent.ExecutionContext + ?? testMethodInfo.Parent.Parent.ExecutionContext; + + // Fast path: when no ExecutionContext was captured (the common case), + // ExecutionContextHelpers.RunOnContext would simply call the action inline. + // Skip the TaskCompletionSource bridge, async-lambda closure, and Action + // delegate allocations entirely. + if (capturedContext is null) + { + using (TestContextImplementation.SetCurrentTestContext(executionContext as TestContext)) + { + testMethodInfo.TestContext = executionContext; + return await _testMethodInfo.Executor.ExecuteAsync(testMethodInfo).ConfigureAwait(false); + } + } + + var tcs = new TaskCompletionSource(); + +#pragma warning disable VSTHRD101 // Avoid unsupported async delegates + ExecutionContextHelpers.RunOnContext( + capturedContext, + async () => + { + try + { + using (TestContextImplementation.SetCurrentTestContext(executionContext as TestContext)) + { + testMethodInfo.TestContext = executionContext; + tcs.SetResult(await _testMethodInfo.Executor.ExecuteAsync(testMethodInfo).ConfigureAwait(false)); + } + } + catch (Exception e) + { + tcs.SetException(e); + } + }); +#pragma warning restore VSTHRD101 // Avoid unsupported async delegates + return await tcs.Task.ConfigureAwait(false); + } + catch (Exception ex) + { + return + [ + new TestResult + { + TestFailureException = new InvalidOperationException( + string.Format( + CultureInfo.CurrentCulture, + Resource.UTA_ExecuteThrewException, + _testMethodInfo.Executor.GetType().FullName, + ex.ToString()), + ex), + }, + ]; + } + } + + /// + /// Updates each given result with new execution and parent execution identifiers. + /// + /// Results. + private static void UpdateResultsWithParentInfo(List results) + { + foreach (TestResult result in results) + { + result.ExecutionId = Guid.NewGuid(); + result.ParentExecId = Guid.NewGuid(); + } + } +} diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs index 849b6ba9b1..aba0af1dab 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs @@ -4,7 +4,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -18,7 +17,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; /// This class is responsible to running tests and converting framework TestResults to adapter TestResults. /// [StackTraceHidden] -internal sealed class TestMethodRunner +internal sealed partial class TestMethodRunner { /// /// Test context which needs to be passed to the various methods of the test. @@ -242,317 +241,6 @@ private bool IsDataDrivenTest() return false; } - private async Task TryExecuteDataSourceBasedTestsAsync(List results) - { - // Iterate cached attributes directly to preserve the previous semantics: - // execute only when there is exactly one DataSourceAttribute. - bool hasSingleDataSource = false; - foreach (Attribute attribute in PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(_testMethodInfo.MethodInfo)) - { - if (attribute is not DataSourceAttribute) - { - continue; - } - - if (hasSingleDataSource) - { - return false; - } - - hasSingleDataSource = true; - } - - if (!hasSingleDataSource) - { - return false; - } - - await ExecuteTestFromDataSourceAttributeAsync(results).ConfigureAwait(false); - return true; - } - - private async Task TryExecuteFoldedDataDrivenTestsAsync(List results) - { - bool hasTestDataSource = false; - var outerContext = (TestContextImplementation)_testContext.Context; - - foreach (Attribute attribute in PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(_testMethodInfo.MethodInfo)) - { - if (attribute is not UTF.ITestDataSource testDataSource) - { - continue; - } - - hasTestDataSource = true; - if (testDataSource is ITestDataSourceIgnoreCapability { IgnoreMessage: { } ignoreMessage }) - { - results.Add(TestResult.CreateIgnoredResult(ignoreMessage)); - continue; - } - - // This code is to execute tests. To discover the tests code is in AssemblyEnumerator.TryUnfoldITestDataSource. - // Any change made here should be reflected in AssemblyEnumerator.TryUnfoldITestDataSource as well. - bool dataSourceHasData = false; - foreach (object?[] data in testDataSource.GetData(_testMethodInfo.MethodInfo)) - { - dataSourceHasData = true; - - // Create a fresh TestContextImplementation per iteration so the folded path is - // structurally equivalent to the unfolded path (where each row gets its own - // context). This isolates per-row state (captured output, diagnostic messages, - // result files, outcome, exception, property bag mutations, ...) so a leak in - // any current or future TestContextImplementation field cannot accumulate across - // rows. See https://github.com/microsoft/testfx/issues/7933. - TestContextImplementation iterationContext = outerContext.CloneForDataDrivenIteration(); - try - { - TestResult[] testResults = await ExecuteTestWithDataSourceAsync(iterationContext, testDataSource, data, actualDataAlreadyHandledDuringDiscovery: false).ConfigureAwait(false); - - results.AddRange(testResults); - } - finally - { - _testMethodInfo.SetArguments(null); - iterationContext.Dispose(); - } - } - - if (!dataSourceHasData) - { - if (!MSTestSettings.CurrentSettings.ConsiderEmptyDataSourceAsInconclusive) - { - throw testDataSource.GetExceptionForEmptyDataSource(_testMethodInfo.MethodInfo); - } - - var inconclusiveResult = new TestResult - { - Outcome = UnitTestOutcome.Inconclusive, - }; - results.Add(inconclusiveResult); - } - } - - return hasTestDataSource; - } - - private async Task ExecuteTestFromDataSourceAttributeAsync(List results) - { - var watch = Stopwatch.StartNew(); - - try - { - IEnumerable? dataRows = PlatformServiceProvider.Instance.TestDataSource.GetData(_testMethodInfo, _testContext); - if (dataRows == null) - { - var inconclusiveResult = new TestResult - { - Outcome = UnitTestOutcome.Inconclusive, - Duration = watch.Elapsed, - }; - results.Add(inconclusiveResult); - return; - } - - try - { - int rowIndex = 0; - var outerContext = (TestContextImplementation)_testContext.Context; - - foreach (object dataRow in dataRows) - { - // Create a fresh TestContextImplementation per row for the same structural - // reason as in TryExecuteFoldedDataDrivenTestsAsync — each row should - // start with no accumulated per-test state. - TestContextImplementation iterationContext = outerContext.CloneForDataDrivenIteration(); - try - { - TestResult[] testResults = await ExecuteTestWithDataRowAsync(iterationContext, dataRow, rowIndex++).ConfigureAwait(false); - results.AddRange(testResults); - } - finally - { - iterationContext.Dispose(); - } - } - } - finally - { - _testContext.SetDataConnection(null); - _testContext.SetDataRow(null); - } - } - catch (Exception ex) - { - var failedResult = new TestResult - { - Outcome = UnitTestOutcome.Error, - TestFailureException = ex, - Duration = watch.Elapsed, - }; - results.Add(failedResult); - } - } - - private async Task ExecuteTestWithDataSourceAsync(ITestContext executionContext, UTF.ITestDataSource? testDataSource, object?[]? data, bool actualDataAlreadyHandledDuringDiscovery) - { - string? displayName = StringEx.IsNullOrWhiteSpace(_test.DisplayName) - ? _test.Name - : _test.DisplayName; - - string? displayNameFromTestDataRow = null; - string? ignoreFromTestDataRow = null; - if (!actualDataAlreadyHandledDuringDiscovery && data is not null && - TestDataSourceHelpers.TryHandleITestDataRow(data, _testMethodInfo.ParameterTypes, out data, out ignoreFromTestDataRow, out displayNameFromTestDataRow)) - { - // Handled already. - } - else if (!actualDataAlreadyHandledDuringDiscovery && TestDataSourceHelpers.IsDataConsideredSingleArgumentValue(data, _testMethodInfo.ParameterTypes)) - { - // SPECIAL CASE: - // This condition is a duplicate of the condition in GetInvokeResultAsync. - // - // The known scenario we know of that shows importance of that check is if we have DynamicData using this member - // - // public static IEnumerable GetData() - // { - // yield return new object[] { ("Hello", "World") }; - // } - // - // If the test method has a single parameter which is 'object[]', then we should pass the tuple array as is. - // Note that normally, the array in this code path represents the arguments of the test method. - // However, GetInvokeResultAsync uses the above check to mean "the whole array is the single argument to the test method" - } - else if (!actualDataAlreadyHandledDuringDiscovery && data?.Length == 1 && TestDataSourceHelpers.TryHandleTupleDataSource(data[0], _testMethodInfo.ParameterTypes, out object?[] tupleExpandedToArray)) - { - data = tupleExpandedToArray; - } - - // PERF: Extract ReflectionTestMethodInfo to avoid allocating it twice when testDataSource is not null - // and both GetDisplayName and ComputeDefaultDisplayName need to be consulted. - if (displayNameFromTestDataRow is null && testDataSource is not null) - { - ReflectionTestMethodInfo reflectionMethodInfo = _cachedReflectionMethodInfo ??= new ReflectionTestMethodInfo(_testMethodInfo.MethodInfo, _test.DisplayName); - displayName = testDataSource.GetDisplayName(reflectionMethodInfo, data) - ?? TestDataSourceUtilities.ComputeDefaultDisplayName(reflectionMethodInfo, data) - ?? displayName; - } - else - { - displayName = displayNameFromTestDataRow ?? displayName; - } - - var stopwatch = Stopwatch.StartNew(); - _testMethodInfo.SetArguments(data); - executionContext.SetTestData(data); - executionContext.SetDisplayName(displayName); - - TestResult[] testResults = ignoreFromTestDataRow is not null - ? [TestResult.CreateIgnoredResult(ignoreFromTestDataRow)] - : await ExecuteTestAsync(executionContext, _testMethodInfo).ConfigureAwait(false); - - stopwatch.Stop(); - - foreach (TestResult testResult in testResults) - { - if (testResult.Duration == TimeSpan.Zero) - { - testResult.Duration = stopwatch.Elapsed; - } - - testResult.DisplayName = displayName; - } - - return testResults; - } - - private async Task ExecuteTestWithDataRowAsync(ITestContext executionContext, object dataRow, int rowIndex) - { - string displayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, _test.DisplayName, rowIndex); - Stopwatch? stopwatch = null; - - TestResult[]? testResults; - try - { - stopwatch = Stopwatch.StartNew(); - executionContext.SetDataRow(dataRow); - testResults = await ExecuteTestAsync(executionContext, _testMethodInfo).ConfigureAwait(false); - } - finally - { - stopwatch?.Stop(); - executionContext.SetDataRow(null); - } - - foreach (TestResult testResult in testResults) - { - testResult.DisplayName = displayName; - testResult.Duration = stopwatch.Elapsed; - } - - return testResults; - } - - private async Task ExecuteTestAsync(ITestContext executionContext, TestMethodInfo testMethodInfo) - { - try - { - ExecutionContext? capturedContext = testMethodInfo.Parent.ExecutionContext - ?? testMethodInfo.Parent.Parent.ExecutionContext; - - // Fast path: when no ExecutionContext was captured (the common case), - // ExecutionContextHelpers.RunOnContext would simply call the action inline. - // Skip the TaskCompletionSource bridge, async-lambda closure, and Action - // delegate allocations entirely. - if (capturedContext is null) - { - using (TestContextImplementation.SetCurrentTestContext(executionContext as TestContext)) - { - testMethodInfo.TestContext = executionContext; - return await _testMethodInfo.Executor.ExecuteAsync(testMethodInfo).ConfigureAwait(false); - } - } - - var tcs = new TaskCompletionSource(); - -#pragma warning disable VSTHRD101 // Avoid unsupported async delegates - ExecutionContextHelpers.RunOnContext( - capturedContext, - async () => - { - try - { - using (TestContextImplementation.SetCurrentTestContext(executionContext as TestContext)) - { - testMethodInfo.TestContext = executionContext; - tcs.SetResult(await _testMethodInfo.Executor.ExecuteAsync(testMethodInfo).ConfigureAwait(false)); - } - } - catch (Exception e) - { - tcs.SetException(e); - } - }); -#pragma warning restore VSTHRD101 // Avoid unsupported async delegates - return await tcs.Task.ConfigureAwait(false); - } - catch (Exception ex) - { - return - [ - new TestResult - { - TestFailureException = new InvalidOperationException( - string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_ExecuteThrewException, - _testMethodInfo.Executor.GetType().FullName, - ex.ToString()), - ex), - }, - ]; - } - } - /// /// Gets aggregate outcome. /// @@ -575,17 +263,4 @@ private static UnitTestOutcome GetAggregateOutcome(IReadOnlyList res return aggregateOutcome; } - - /// - /// Updates each given result with new execution and parent execution identifiers. - /// - /// Results. - private static void UpdateResultsWithParentInfo(List results) - { - foreach (TestResult result in results) - { - result.ExecutionId = Guid.NewGuid(); - result.ParentExecId = Guid.NewGuid(); - } - } }