Skip to content
Merged
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
44 changes: 21 additions & 23 deletions TUnit.Engine/Building/Collectors/AotTestDataCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,18 @@ 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
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 +59,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 @@ -220,24 +209,33 @@ private IEnumerable<TestMetadata> CollectTestsWithTwoPhaseDiscovery(
return results;
}

/// <summary>
/// Traditional collection: materialize all tests from sources.
/// Used when filter hints are not available or sources don't support ITestDescriptorSource.
/// </summary>
private IEnumerable<TestMetadata> CollectTestsTraditional(
private List<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 =>
{
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<TestMetadata>(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")]
Expand Down
Loading