diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs index 9f96f02485..96b3545d62 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs @@ -108,12 +108,47 @@ internal async Task RunTestMethodAsync() DebugEx.Assert(_test != null, "Test should not be null."); DebugEx.Assert(_testMethodInfo.MethodInfo != null, "Test method should not be null."); - List results = []; if (_testMethodInfo.Executor == null) { throw ApplicationStateGuard.Unreachable(); } + // Fast path for non-data-driven tests (the common case). + // A single attribute scan replaces the two scans done by TryExecuteDataSourceBasedTestsAsync and + // TryExecuteFoldedDataDrivenTestsAsync, and avoids allocating a List that would + // immediately be spread back into a TestResult[]. + if (_test.DataType != DynamicDataType.ITestDataSource && !IsDataDrivenTest()) + { + _testContext.SetDisplayName(_test.DisplayName); + TestResult[] testResults = await ExecuteTestAsync(_testContext, _testMethodInfo).ConfigureAwait(false); + + foreach (TestResult testResult in testResults) + { + if (StringEx.IsNullOrWhiteSpace(testResult.DisplayName)) + { + testResult.DisplayName = _test.DisplayName; + } + } + + UnitTestOutcome fastPathOutcome = GetAggregateOutcome(testResults); + _testContext.SetOutcome(fastPathOutcome); + + // Set a result in case no result is present, preserving the safeguard from the slow path + // (ExecuteAsync dereferences result[0] in its finally block). + return testResults.Length == 0 + ? + [ + new TestResult + { + Outcome = fastPathOutcome, + TestFailureException = new TestFailedException(UnitTestOutcome.Error, Resource.UTA_NoTestResult), + }, + ] + : testResults; + } + + // Slow path for data-driven tests. + List results = []; bool isDataDriven = false; if (_test.DataType == DynamicDataType.ITestDataSource) { @@ -137,6 +172,9 @@ internal async Task RunTestMethodAsync() } else { + // IsDataDrivenTest() returned true but neither Try...Async method handled the test + // (e.g., multiple DataSourceAttributes → TryExecuteDataSourceBasedTestsAsync returns false). + // Execute as a normal non-data-driven test, preserving existing behavior. _testContext.SetDisplayName(_test.DisplayName); TestResult[] testResults = await ExecuteTestAsync(_testContext, _testMethodInfo).ConfigureAwait(false); @@ -176,6 +214,26 @@ internal async Task RunTestMethodAsync() return [.. results]; } + /// + /// Returns if this test method has a or + /// implements , indicating it requires the data-driven execution path. + /// A single attribute-cache pass checks for both, replacing the two separate scans that would otherwise + /// be performed by and + /// . + /// + private bool IsDataDrivenTest() + { + foreach (Attribute attribute in PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(_testMethodInfo.MethodInfo)) + { + if (attribute is DataSourceAttribute or UTF.ITestDataSource) + { + return true; + } + } + + return false; + } + private async Task TryExecuteDataSourceBasedTestsAsync(List results) { // Iterate cached attributes directly to preserve the previous semantics: @@ -476,7 +534,7 @@ private async Task ExecuteTestAsync(ITestContext executionContext, /// /// Results. /// Aggregate outcome. - private static UnitTestOutcome GetAggregateOutcome(List results) + private static UnitTestOutcome GetAggregateOutcome(IReadOnlyList results) { // In case results are not present, set outcome as unknown. if (results.Count == 0)