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
66 changes: 5 additions & 61 deletions TUnit.Engine/Discovery/ReflectionTestDataCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ internal sealed class ReflectionTestDataCollector : ITestDataCollector

public async Task<IEnumerable<TestMetadata>> CollectTestsAsync(string testSessionId)
{
// Disable assembly loading event handler to prevent recursive issues
// This was causing problems when assemblies were loaded during scanning

// Optimize: Pre-filter and allocate array instead of LINQ ToList()
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var assembliesList = new List<Assembly>(allAssemblies.Length);
foreach (var assembly in allAssemblies)
Expand All @@ -41,8 +37,6 @@ public async Task<IEnumerable<TestMetadata>> CollectTestsAsync(string testSessio
var assemblies = assembliesList;


// Use throttled parallel processing to prevent thread pool exhaustion
// Limit to 2x processor count to avoid overwhelming the thread pool
var maxConcurrency = Math.Min(assemblies.Count, Environment.ProcessorCount * 2);
var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
var tasks = new Task<List<TestMetadata>>[assemblies.Count];
Expand All @@ -57,7 +51,6 @@ public async Task<IEnumerable<TestMetadata>> CollectTestsAsync(string testSessio
await semaphore.WaitAsync().ConfigureAwait(false);
try
{
// Use lock-free ConcurrentDictionary for assembly tracking
if (!_scannedAssemblies.TryAdd(assembly, true))
{
return
Expand All @@ -67,9 +60,7 @@ public async Task<IEnumerable<TestMetadata>> CollectTestsAsync(string testSessio

try
{
// Now we can properly await the async method
var testsInAssembly = await DiscoverTestsInAssembly(assembly).ConfigureAwait(false);
return testsInAssembly;
return await DiscoverTestsInAssembly(assembly).ConfigureAwait(false);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -308,41 +299,11 @@ private static async Task<List<TestMetadata>> DiscoverTestsInAssembly(Assembly a
{
try
{
// In single file mode, GetExportedTypes might miss some types
// Use GetTypes() instead which gets all types including nested ones
return asm.GetTypes();
}
catch (ReflectionTypeLoadException rtle)
catch (ReflectionTypeLoadException reflectionTypeLoadException)
{
// Some types might fail to load, but we can still use the ones that loaded successfully
// Optimize: Manual filtering with ArrayPool for better memory efficiency
var loadedTypes = rtle.Types;
if (loadedTypes == null)
{
return [];
}

// Use ArrayPool for temporary storage to reduce allocations
var tempArray = ArrayPool<Type>.Shared.Rent(loadedTypes.Length);
try
{
var validCount = 0;
foreach (var type in loadedTypes)
{
if (type != null)
{
tempArray[validCount++] = type;
}
}

var result = new Type[validCount];
Array.Copy(tempArray, result, validCount);
return result;
}
finally
{
ArrayPool<Type>.Shared.Return(tempArray);
}
return reflectionTypeLoadException.Types.Where(x => x != null).ToArray()!;
}
catch (Exception)
{
Expand All @@ -359,13 +320,11 @@ private static async Task<List<TestMetadata>> DiscoverTestsInAssembly(Assembly a

foreach (var type in filteredTypes)
{
// Skip abstract types - they can't be instantiated
if (type.IsAbstract)
{
continue;
}

// Handle generic type definitions specially
if (type.IsGenericTypeDefinition)
{
var genericTests = await DiscoverGenericTests(type).ConfigureAwait(false);
Expand Down Expand Up @@ -396,8 +355,6 @@ private static async Task<List<TestMetadata>> DiscoverTestsInAssembly(Assembly a
}
else
{
// Only get declared methods
// Optimize: Manual filtering instead of LINQ Where().ToArray()
var declaredMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
var testMethodsList = new List<MethodInfo>(declaredMethods.Length);
foreach (var method in declaredMethods)
Expand All @@ -423,7 +380,6 @@ private static async Task<List<TestMetadata>> DiscoverTestsInAssembly(Assembly a
}
catch (Exception ex)
{
// Create a failed test metadata for this specific test
var failedTest = CreateFailedTestMetadata(type, method, ex);
discoveredTests.Add(failedTest);
}
Expand Down Expand Up @@ -863,10 +819,8 @@ private static int CalculateInheritanceDepth(Type testClass, MethodInfo testMeth

private static Task<TestMetadata> BuildTestMetadata(Type testClass, MethodInfo testMethod, object?[]? classData = null)
{
// Create a base ReflectionTestMetadata instance
var testName = GenerateTestName(testClass, testMethod);

// Calculate inheritance depth
var inheritanceDepth = CalculateInheritanceDepth(testClass, testMethod);

try
Expand Down Expand Up @@ -1061,26 +1015,16 @@ private static ParameterInfo[] GetParametersWithoutCancellationToken(MethodInfo
return parameters;
}





private static string? ExtractFilePath(MethodInfo method)
{
method.GetCustomAttribute<TestAttribute>();
// Reflection doesn't have access to file path from CallerFilePath
return null;
return method.GetCustomAttribute<TestAttribute>()?.File;
}

private static int? ExtractLineNumber(MethodInfo method)
{
method.GetCustomAttribute<TestAttribute>();
// Reflection doesn't have access to line number from CallerLineNumber
return null;
return method.GetCustomAttribute<TestAttribute>()?.Line;
}



private static TestMetadata CreateFailedTestMetadataForAssembly(Assembly assembly, Exception ex)
{
var testName = $"[ASSEMBLY SCAN FAILED] {assembly.GetName().Name}";
Expand Down
Loading