Skip to content
Merged
Changes from 2 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
39 changes: 18 additions & 21 deletions TUnit.Engine/Building/Collectors/AotTestDataCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,19 @@ public Task<IEnumerable<TestMetadata>> CollectTestsAsync(string testSessionId)
#endif
public async Task<IEnumerable<TestMetadata>> 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<TestMetadata> 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
// Filtered: enumerate descriptors, apply filters, expand dependencies, materialize matches
standardTestMetadatas = CollectTestsWithTwoPhaseDiscovery(
Sources.TestSources,
testSessionId,
filterHints);
}
else
{
// Fallback: Use traditional collection (for legacy sources or no filter hints)
// Apply type-level pre-filtering when hints are available
IEnumerable<KeyValuePair<Type, ConcurrentQueue<ITestSource>>> testSourcesByType = Sources.TestSources;

if (filterHints.HasHints)
Expand All @@ -70,7 +60,7 @@ public async Task<IEnumerable<TestMetadata>> 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
Expand Down Expand Up @@ -221,23 +211,30 @@ private IEnumerable<TestMetadata> CollectTestsWithTwoPhaseDiscovery(
}

/// <summary>
/// Traditional collection: materialize all tests from sources.
/// Used when filter hints are not available or sources don't support ITestDescriptorSource.
/// Materializes TestMetadata from every source in parallel.
/// Each source's GetTests is independent and safe to call concurrently.
/// </summary>
private IEnumerable<TestMetadata> CollectTestsTraditional(
private IEnumerable<TestMetadata> CollectTests(
List<ITestSource> testSourcesList,
string testSessionId)
{
var results = new List<TestMetadata>();
foreach (var testSource in testSourcesList)
var batches = new IReadOnlyList<TestMetadata>[testSourcesList.Count];

Parallel.For(0, testSourcesList.Count, i =>
{
var tests = testSource.GetTests(testSessionId);
for (var i = 0; i < tests.Count; i++)
batches[i] = testSourcesList[i].GetTests(testSessionId);
});

var combined = new List<TestMetadata>();
for (var i = 0; i < batches.Length; 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")]
Expand Down
Loading