Skip to content
Merged
Show file tree
Hide file tree
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
27 changes: 19 additions & 8 deletions TUnit.Core/AotCompatibility/GenericTestRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public static class GenericTestRegistry
private static readonly ConcurrentDictionary<GenericMethodKey, MethodInfo> _compiledMethods = new();
private static readonly ConcurrentDictionary<Type, HashSet<Type[]>> _registeredCombinations = new();
private static readonly ConcurrentDictionary<GenericMethodKey, Delegate> _directInvocationDelegates = new();
private static readonly object _combinationsLock = new();

/// <summary>
/// Registers a pre-compiled generic method instance.
Expand All @@ -22,9 +23,12 @@ public static void RegisterGenericMethod(Type declaringType, string methodName,
var key = new GenericMethodKey(declaringType, methodName, typeArguments);
_compiledMethods[key] = compiledMethod;

// Track registered combinations
// Track registered combinations - lock protects the inner HashSet from concurrent modification
var combinations = _registeredCombinations.GetOrAdd(declaringType, static _ => new HashSet<Type[]>(new TypeArrayComparer()));
combinations.Add(typeArguments);
lock (_combinationsLock)
{
combinations.Add(typeArguments);
}
}

/// <summary>
Expand Down Expand Up @@ -77,9 +81,16 @@ public static bool IsRegistered(Type declaringType, string methodName, Type[] ty
/// </summary>
public static IEnumerable<Type[]> GetRegisteredCombinations(Type declaringType)
{
return _registeredCombinations.TryGetValue(declaringType, out var combinations)
? combinations
: Array.Empty<Type[]>();
if (!_registeredCombinations.TryGetValue(declaringType, out var combinations))
{
return Array.Empty<Type[]>();
}

// Return a snapshot to avoid enumeration during concurrent modification
lock (_combinationsLock)
{
return combinations.ToArray();
}
}

/// <summary>
Expand All @@ -88,17 +99,17 @@ public static IEnumerable<Type[]> GetRegisteredCombinations(Type declaringType)
public static void MarkAsAotCompatible(MethodInfo method)
{
// This information can be used by the analyzer to suppress warnings
AotCompatibleMethods.Add(method);
AotCompatibleMethods.TryAdd(method, 0);
}

private static readonly HashSet<MethodInfo> AotCompatibleMethods = [];
private static readonly ConcurrentDictionary<MethodInfo, byte> AotCompatibleMethods = new();

/// <summary>
/// Checks if a method has been marked as AOT-compatible.
/// </summary>
public static bool IsMarkedAotCompatible(MethodInfo method)
{
return AotCompatibleMethods.Contains(method);
return AotCompatibleMethods.ContainsKey(method);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion TUnit.Core/Helpers/DataSourceHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ public static bool IsTuple(object? obj)
return (true, createdInstance);
}

private static readonly Dictionary<Type, Func<MethodMetadata, string, Task<object>>> TypeCreators = new();
private static readonly ConcurrentDictionary<Type, Func<MethodMetadata, string, Task<object>>> TypeCreators = new();

/// <summary>
/// Registers a type creator function for types with init-only data source properties.
Expand Down
8 changes: 1 addition & 7 deletions TUnit.Engine/TUnitInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,7 @@ private void ParseParameters()
var key = split[0];
var value = split[1];

if (!TestContext.InternalParametersDictionary.TryGetValue(key, out var list))
{
list =
[
];
TestContext.InternalParametersDictionary[key] = list;
}
var list = TestContext.InternalParametersDictionary.GetOrAdd(key, static _ => []);
list.Add(value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,13 @@ namespace
public static readonly .DefaultExecutor Instance;
protected override . ExecuteAsync(<.> action) { }
}
public static class Defaults
{
public static readonly ForcefulExitTimeout;
public static readonly HookTimeout;
public static readonly ProcessExitHookDelay;
public static readonly TestTimeout;
}
public abstract class DependencyInjectionDataSourceAttribute<TScope> : .UntypedDataSourceGeneratorAttribute
{
protected DependencyInjectionDataSourceAttribute() { }
Expand Down Expand Up @@ -562,6 +569,7 @@ namespace
public void SetDisplayName(string displayName) { }
public void SetDisplayNameFormatter( formatterType) { }
public void SetPriority(. priority) { }
public void SetRetryBackoff(int backoffMs, double backoffMultiplier) { }
public void SetRetryLimit(int retryLimit) { }
public void SetRetryLimit(int retryCount, <.TestContext, , int, .<bool>> shouldRetry) { }
}
Expand Down Expand Up @@ -1138,7 +1146,10 @@ namespace
public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, ., .
{
public RetryAttribute(int times) { }
public int BackoffMs { get; set; }
public double BackoffMultiplier { get; set; }
public int Order { get; }
public []? RetryOnExceptionTypes { get; set; }
public ScopeType { get; }
public int Times { get; }
public . OnTestDiscovered(.DiscoveredTestContext context) { }
Expand Down Expand Up @@ -1407,6 +1418,8 @@ namespace
public [] MethodGenericArguments { get; set; }
public required .MethodMetadata MethodMetadata { get; set; }
public required string MethodName { get; init; }
public int RetryBackoffMs { get; set; }
public double RetryBackoffMultiplier { get; set; }
public int RetryLimit { get; set; }
public required ReturnType { get; set; }
public required object?[] TestClassArguments { get; set; }
Expand Down Expand Up @@ -2359,6 +2372,8 @@ namespace .Interfaces
}
public interface ITestConfiguration
{
int RetryBackoffMs { get; }
double RetryBackoffMultiplier { get; }
int RetryLimit { get; }
? Timeout { get; }
}
Expand Down
Loading