diff --git a/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs b/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs index 9a89c8e8f1..782414e16c 100644 --- a/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs +++ b/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs @@ -39,20 +39,11 @@ public Task> CollectTestsAsync(string testSessionId) #endif public async Task> CollectTestsAsync(string testSessionId, ITestExecutionFilter? filter) { - // Extract hints from filter for pre-filtering test sources by type var filterHints = MetadataFilterMatcher.ExtractFilterHints(filter); - - // Try two-phase discovery with single-pass filtering when all sources support descriptors. - // This avoids double-enumeration: previously ExpandSourcesForDependencies enumerated - // descriptors to find dependencies, then CollectTestsWithTwoPhaseDiscoveryAsync enumerated - // them again. Now we pass ALL sources and do type-level + descriptor-level filtering - // in a single pass, while indexing everything for dependency resolution. IEnumerable standardTestMetadatas; if (filterHints.HasHints && Sources.TestSources.All(static kvp => kvp.Value.All(static s => s is ITestDescriptorSource))) { - // Single-pass two-phase discovery: enumerate all descriptors once, - // apply type and descriptor filters during enumeration, expand dependencies from index standardTestMetadatas = CollectTestsWithTwoPhaseDiscovery( Sources.TestSources, testSessionId, @@ -60,8 +51,6 @@ public async Task> CollectTestsAsync(string testSessio } else { - // Fallback: Use traditional collection (for legacy sources or no filter hints) - // Apply type-level pre-filtering when hints are available IEnumerable>> testSourcesByType = Sources.TestSources; if (filterHints.HasHints) @@ -70,7 +59,7 @@ public async Task> CollectTestsAsync(string testSessio } var testSourcesList = testSourcesByType.SelectMany(kvp => kvp.Value).ToList(); - standardTestMetadatas = CollectTestsTraditional(testSourcesList, testSessionId); + standardTestMetadatas = CollectTests(testSourcesList, testSessionId); } // Dynamic tests are typically rare, collect sequentially @@ -220,24 +209,33 @@ private IEnumerable CollectTestsWithTwoPhaseDiscovery( return results; } - /// - /// Traditional collection: materialize all tests from sources. - /// Used when filter hints are not available or sources don't support ITestDescriptorSource. - /// - private IEnumerable CollectTestsTraditional( + private List CollectTests( List testSourcesList, string testSessionId) { - var results = new List(); - foreach (var testSource in testSourcesList) + var batches = new IReadOnlyList[testSourcesList.Count]; + + Parallel.For(0, testSourcesList.Count, i => + { + batches[i] = testSourcesList[i].GetTests(testSessionId); + }); + + var totalCount = 0; + for (var i = 0; i < batches.Length; i++) + { + totalCount += batches[i].Count; + } + + var combined = new List(totalCount); + for (var i = 0; i < batches.Length; i++) { - var tests = testSource.GetTests(testSessionId); - for (var i = 0; i < tests.Count; i++) + var batch = batches[i]; + for (var j = 0; j < batch.Count; j++) { - results.Add(tests[i]); + combined.Add(batch[j]); } } - return results; + return combined; } [RequiresUnreferencedCode("Dynamic test collection requires expression compilation and reflection")]