diff --git a/TUnit.Core/DynamicTestBuilderContext.cs b/TUnit.Core/DynamicTestBuilderContext.cs
index 25d5225fec..0c6c04bbbb 100644
--- a/TUnit.Core/DynamicTestBuilderContext.cs
+++ b/TUnit.Core/DynamicTestBuilderContext.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics.CodeAnalysis;
+
namespace TUnit.Core;
///
@@ -20,6 +22,7 @@ public DynamicTestBuilderContext(string filePath, int lineNumber)
public IReadOnlyList Tests => _tests.AsReadOnly();
+ [RequiresDynamicCode("Adding dynamic tests requires reflection which is not supported in native AOT scenarios.")]
public void AddTest(AbstractDynamicTest test)
{
// Set creator location if the test implements IDynamicTestCreatorLocation
@@ -28,7 +31,7 @@ public void AddTest(AbstractDynamicTest test)
testWithLocation.CreatorFilePath = FilePath;
testWithLocation.CreatorLineNumber = LineNumber;
}
-
+
_tests.Add(test);
}
}
diff --git a/TUnit.Core/Extensions/TestContextExtensions.cs b/TUnit.Core/Extensions/TestContextExtensions.cs
index ba50b41e97..ffa127dfa0 100644
--- a/TUnit.Core/Extensions/TestContextExtensions.cs
+++ b/TUnit.Core/Extensions/TestContextExtensions.cs
@@ -24,6 +24,7 @@ public static string GetClassTypeName(this TestContext context)
return $"{context.TestDetails.ClassType.Name}({string.Join(", ", context.TestDetails.TestClassArguments.Select(a => ArgumentFormatter.Format(a, context.ArgumentDisplayFormatters)))})";
}
+ [RequiresDynamicCode("Adding dynamic tests requires reflection which is not supported in native AOT scenarios.")]
public static async Task AddDynamicTest<[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.PublicConstructors
| DynamicallyAccessedMemberTypes.NonPublicConstructors
diff --git a/TUnit.Core/Interfaces/ITestRegistry.cs b/TUnit.Core/Interfaces/ITestRegistry.cs
index 51305ece09..f35120875e 100644
--- a/TUnit.Core/Interfaces/ITestRegistry.cs
+++ b/TUnit.Core/Interfaces/ITestRegistry.cs
@@ -14,6 +14,7 @@ public interface ITestRegistry
/// The current test context
/// The dynamic test instance to add
/// A task that completes when the test has been queued for execution
+ [RequiresDynamicCode("Adding dynamic tests requires runtime compilation and reflection which are not supported in native AOT scenarios.")]
Task AddDynamicTest<[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.PublicConstructors
| DynamicallyAccessedMemberTypes.NonPublicConstructors
@@ -21,6 +22,6 @@ public interface ITestRegistry
| DynamicallyAccessedMemberTypes.PublicMethods
| DynamicallyAccessedMemberTypes.NonPublicMethods
| DynamicallyAccessedMemberTypes.PublicFields
- | DynamicallyAccessedMemberTypes.NonPublicFields)] T>(TestContext context, DynamicTest dynamicTest)
+ | DynamicallyAccessedMemberTypes.NonPublicFields)] T>(TestContext context, DynamicTest dynamicTest)
where T : class;
}
\ No newline at end of file
diff --git a/TUnit.Core/PropertyInjection/Initialization/Strategies/ReflectionPropertyStrategy.cs b/TUnit.Core/PropertyInjection/Initialization/Strategies/ReflectionPropertyStrategy.cs
index f5028337d2..581bb2a816 100644
--- a/TUnit.Core/PropertyInjection/Initialization/Strategies/ReflectionPropertyStrategy.cs
+++ b/TUnit.Core/PropertyInjection/Initialization/Strategies/ReflectionPropertyStrategy.cs
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using TUnit.Core.DataSources;
using TUnit.Core.Initialization;
@@ -33,6 +34,13 @@ public bool CanHandle(PropertyInitializationContext context)
[UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "Reflection mode support")]
public async Task InitializePropertyAsync(PropertyInitializationContext context)
{
+#if NET
+ if (!RuntimeFeature.IsDynamicCodeSupported)
+ {
+ throw new Exception("Using TUnit Reflection mechanisms isn't supported in AOT mode");
+ }
+#endif
+
if (context.PropertyInfo == null || context.DataSource == null)
{
return;
@@ -58,4 +66,4 @@ public async Task InitializePropertyAsync(PropertyInitializationContext context)
PropertyTrackingService.AddToTestContext(context, resolvedValue);
}
-}
\ No newline at end of file
+}
diff --git a/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs b/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs
index 81c663447b..90aa7ad316 100644
--- a/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs
+++ b/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs
@@ -76,6 +76,8 @@ private async IAsyncEnumerable CollectDynamicTestsStreaming(
}
}
+ [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
+ Justification = "Dynamic tests are opt-in and users are warned via RequiresDynamicCode on the method they call")]
private async IAsyncEnumerable ConvertDynamicTestToMetadataStreaming(
AbstractDynamicTest abstractDynamicTest,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
@@ -92,6 +94,7 @@ private async IAsyncEnumerable ConvertDynamicTestToMetadataStreami
}
}
+ [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Dynamic tests require runtime compilation of lambda expressions and are not supported in native AOT scenarios.")]
private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDiscoveryResult result)
{
if (result.TestClassType == null || result.TestMethod == null)
@@ -144,6 +147,7 @@ private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDisco
});
}
+ [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Dynamic test instance creation requires Activator.CreateInstance and MakeGenericType which are not supported in native AOT scenarios.")]
[UnconditionalSuppressMessage("Trimming",
"IL2070:'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors' in call to 'System.Type.GetConstructors()'",
Justification = "AOT mode uses source-generated factories")]
@@ -187,6 +191,7 @@ private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDisco
};
}
+ [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Dynamic test invocation requires LambdaExpression.Compile() which is not supported in native AOT scenarios.")]
private static Func
- [UnconditionalSuppressMessage("Trimming", "IL2026:Using member 'TUnit.Engine.Discovery.ReflectionTestDataCollector.ReflectionTestDataCollector()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code", Justification = "Reflection mode is explicitly chosen and cannot support trimming")]
- [UnconditionalSuppressMessage("AOT", "IL3050:Using member 'TUnit.Engine.Discovery.ReflectionTestDataCollector.ReflectionTestDataCollector()' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling", Justification = "Reflection mode is explicitly chosen and cannot support AOT")]
public static ITestDataCollector Create(bool? useSourceGeneration = null, Assembly[]? assembliesToScan = null)
{
var isSourceGenerationEnabled = useSourceGeneration ?? SourceRegistrar.IsEnabled;
@@ -43,8 +41,6 @@ public static ITestDataCollector Create(bool? useSourceGeneration = null, Assemb
/// Attempts AOT mode first, falls back to reflection if no source-generated tests found.
/// This provides automatic mode selection for optimal performance and compatibility.
///
- [UnconditionalSuppressMessage("Trimming", "IL2026:Using member 'TUnit.Engine.Discovery.ReflectionTestDataCollector.ReflectionTestDataCollector()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code", Justification = "Reflection mode is a fallback and cannot support trimming")]
- [UnconditionalSuppressMessage("AOT", "IL3050:Using member 'TUnit.Engine.Discovery.ReflectionTestDataCollector.ReflectionTestDataCollector()' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling", Justification = "Reflection mode is a fallback and cannot support AOT")]
public static async Task CreateAutoDetectAsync(string testSessionId, Assembly[]? assembliesToScan = null)
{
// Try AOT mode first (check if any tests were registered)
diff --git a/TUnit.Engine/Discovery/ReflectionAttributeExtractor.cs b/TUnit.Engine/Discovery/ReflectionAttributeExtractor.cs
index 4c6e29d16b..01b78e1d4e 100644
--- a/TUnit.Engine/Discovery/ReflectionAttributeExtractor.cs
+++ b/TUnit.Engine/Discovery/ReflectionAttributeExtractor.cs
@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
+using System.Runtime.CompilerServices;
using TUnit.Core;
namespace TUnit.Engine.Discovery;
@@ -59,8 +60,15 @@ public override int GetHashCode()
///
public static T? GetAttribute(Type testClass, MethodInfo? testMethod = null) where T : Attribute
{
+#if NET
+ if (!RuntimeFeature.IsDynamicCodeSupported)
+ {
+ throw new Exception("Using TUnit Reflection mechanisms isn't supported in AOT mode");
+ }
+#endif
+
var cacheKey = new AttributeCacheKey(testClass, testMethod, typeof(T));
-
+
return (T?)_attributeCache.GetOrAdd(cacheKey, key =>
{
// Original lookup logic preserved
@@ -88,6 +96,13 @@ public override int GetHashCode()
///
public static IEnumerable GetAttributes(Type testClass, MethodInfo? testMethod = null) where T : Attribute
{
+#if NET
+ if (!RuntimeFeature.IsDynamicCodeSupported)
+ {
+ throw new Exception("Using TUnit Reflection mechanisms isn't supported in AOT mode");
+ }
+#endif
+
var attributes = new List();
attributes.AddRange(testClass.Assembly.GetCustomAttributes());
@@ -104,7 +119,7 @@ public static IEnumerable GetAttributes(Type testClass, MethodInfo? testMe
public static string[] ExtractCategories(Type testClass, MethodInfo testMethod)
{
var categories = new HashSet();
-
+
foreach (var attr in GetAttributes(testClass, testMethod))
{
categories.Add(attr.Category);
@@ -128,7 +143,7 @@ public static bool CanRunInParallel(Type testClass, MethodInfo testMethod)
public static TestDependency[] ExtractDependencies(Type testClass, MethodInfo testMethod)
{
var dependencies = new List();
-
+
foreach (var attr in GetAttributes(testClass, testMethod))
{
dependencies.Add(attr.ToTestDependency());
@@ -155,13 +170,13 @@ public static IDataSourceAttribute[] ExtractDataSources(ICustomAttributeProvider
public static Attribute[] GetAllAttributes(Type testClass, MethodInfo testMethod)
{
var attributes = new List();
-
+
// Add in reverse order of precedence so method attributes come first
// This ensures ScopedAttributeFilter will keep method-level attributes over class/assembly
attributes.AddRange(testMethod.GetCustomAttributes());
attributes.AddRange(testClass.GetCustomAttributes());
attributes.AddRange(testClass.Assembly.GetCustomAttributes());
-
+
return attributes.ToArray();
}
@@ -193,4 +208,4 @@ public static PropertyDataSource[] ExtractPropertyDataSources([DynamicallyAccess
return propertyDataSources.ToArray();
}
-}
\ No newline at end of file
+}
diff --git a/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs b/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs
index 2c485f2ee2..8b6974cd92 100644
--- a/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs
+++ b/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
+using System.Runtime.CompilerServices;
using TUnit.Core;
namespace TUnit.Engine.Discovery;
@@ -7,7 +8,13 @@ namespace TUnit.Engine.Discovery;
///
/// Handles generic type resolution and instantiation for reflection-based test discovery
///
-[RequiresUnreferencedCode("Reflection-based generic type resolution requires unreferenced code")]
+[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Reflection mode cannot support trimming")]
+[UnconditionalSuppressMessage("Trimming", "IL2055:Call to 'System.Type.MakeGenericType' can not be statically analyzed", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("Trimming", "IL2065:Value passed to implicit 'this' parameter of method can not be statically determined and may not meet 'DynamicallyAccessedMembersAttribute' requirements", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("Trimming", "IL2067:Target parameter does not satisfy annotation requirements", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("Trimming", "IL2070:Target method does not satisfy annotation requirements", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("Trimming", "IL2075:'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.Type.GetMethods(BindingFlags)'", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Reflection mode cannot support AOT")]
internal static class ReflectionGenericTypeResolver
{
///
@@ -15,6 +22,13 @@ internal static class ReflectionGenericTypeResolver
///
public static Type[]? DetermineGenericTypeArguments(Type genericTypeDefinition, object?[] dataRow)
{
+#if NET
+ if (!RuntimeFeature.IsDynamicCodeSupported)
+ {
+ throw new Exception("Using TUnit Reflection mechanisms isn't supported in AOT mode");
+ }
+#endif
+
var genericParameters = genericTypeDefinition.GetGenericArguments();
// If no data row or empty data, can't determine types
@@ -68,9 +82,6 @@ internal static class ReflectionGenericTypeResolver
///
/// Extracts generic type information including constraints
///
- [UnconditionalSuppressMessage("Trimming",
- "IL2065:Value passed to implicit 'this' parameter of method 'System.Type.GetInterfaces()' can not be statically determined and may not meet 'DynamicallyAccessedMembersAttribute' requirements",
- Justification = "Reflection mode requires dynamic access")]
public static GenericTypeInfo? ExtractGenericTypeInfo(Type testClass)
{
if (!testClass.IsGenericTypeDefinition)
@@ -106,9 +117,6 @@ internal static class ReflectionGenericTypeResolver
///
/// Extracts generic method information including parameter positions
///
- [UnconditionalSuppressMessage("Trimming",
- "IL2065:Value passed to implicit 'this' parameter of method 'System.Type.GetInterfaces()' can not be statically determined and may not meet 'DynamicallyAccessedMembersAttribute' requirements",
- Justification = "Reflection mode requires dynamic access")]
public static GenericMethodInfo? ExtractGenericMethodInfo(MethodInfo method)
{
if (!method.IsGenericMethodDefinition)
@@ -157,10 +165,6 @@ internal static class ReflectionGenericTypeResolver
///
/// Creates a concrete type from a generic type definition and validates the type arguments
///
- [UnconditionalSuppressMessage("Trimming", "IL2055:Call to 'System.Type.MakeGenericType' can not be statically analyzed",
- Justification = "Reflection mode requires dynamic access")]
- [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
- Justification = "Reflection mode cannot support AOT")]
public static Type CreateConcreteType(Type genericTypeDefinition, Type[] typeArguments)
{
var genericParams = genericTypeDefinition.GetGenericArguments();
@@ -174,4 +178,4 @@ public static Type CreateConcreteType(Type genericTypeDefinition, Type[] typeArg
return genericTypeDefinition.MakeGenericType(typeArguments);
}
-}
\ No newline at end of file
+}
diff --git a/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs b/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs
index a9781b19b2..7ac904fb0e 100644
--- a/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs
+++ b/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs
@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
+using System.Runtime.CompilerServices;
using TUnit.Core;
using TUnit.Core.Hooks;
using TUnit.Core.Interfaces;
@@ -10,8 +11,13 @@ namespace TUnit.Engine.Discovery;
///
/// Discovers hooks at runtime using reflection for VB.NET and other languages that don't support source generation.
///
-[RequiresUnreferencedCode("Reflection-based hook discovery requires unreferenced code")]
-[RequiresDynamicCode("Hook invocation may require dynamic code generation")]
+[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Reflection mode cannot support trimming")]
+[UnconditionalSuppressMessage("Trimming", "IL2055:Call to 'System.Type.MakeGenericType' can not be statically analyzed", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("Trimming", "IL2067:Target parameter does not satisfy annotation requirements", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("Trimming", "IL2070:Target method does not satisfy annotation requirements", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' requirements", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("Trimming", "IL2075:'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.Type.GetMethods(BindingFlags)'", Justification = "Reflection mode requires dynamic access")]
+[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Reflection mode cannot support AOT")]
internal sealed class ReflectionHookDiscoveryService
{
private static readonly ConcurrentDictionary _scannedAssemblies = new();
@@ -19,8 +25,15 @@ internal sealed class ReflectionHookDiscoveryService
public static void DiscoverHooks()
{
+#if NET
+ if (!RuntimeFeature.IsDynamicCodeSupported)
+ {
+ throw new Exception("Using TUnit Reflection mechanisms isn't supported in AOT mode");
+ }
+#endif
+
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
-
+
foreach (var assembly in assemblies)
{
if (ShouldScanAssembly(assembly))
@@ -44,7 +57,7 @@ private static bool ShouldScanAssembly(Assembly assembly)
}
// Skip system and framework assemblies
- if (name.StartsWith("System.") ||
+ if (name.StartsWith("System.") ||
name.StartsWith("Microsoft.") ||
name == "mscorlib" ||
name == "netstandard" ||
@@ -54,9 +67,9 @@ private static bool ShouldScanAssembly(Assembly assembly)
}
// Skip TUnit framework assemblies (but not test projects)
- if (name == "TUnit" ||
- name == "TUnit.Core" ||
- name == "TUnit.Engine" ||
+ if (name == "TUnit" ||
+ name == "TUnit.Core" ||
+ name == "TUnit.Engine" ||
name == "TUnit.Assertions")
{
return false;
@@ -92,7 +105,7 @@ private static void DiscoverHooksInAssembly(Assembly assembly)
try
{
var types = assembly.GetTypes();
-
+
foreach (var type in types)
{
DiscoverHooksInType(type, assembly);
@@ -107,7 +120,7 @@ private static void DiscoverHooksInAssembly(Assembly assembly)
private static void DiscoverHooksInType(Type type, Assembly assembly)
{
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
-
+
foreach (var method in methods)
{
// Check for Before attributes
@@ -144,7 +157,7 @@ private static void RegisterBeforeHook(Type type, MethodInfo method, BeforeAttri
{
var hookType = attr.HookType;
var order = attr.Order;
-
+
switch (hookType)
{
case HookType.Test:
@@ -206,7 +219,7 @@ private static void RegisterAfterHook(Type type, MethodInfo method, AfterAttribu
{
var hookType = attr.HookType;
var order = attr.Order;
-
+
switch (hookType)
{
case HookType.Test:
@@ -268,7 +281,7 @@ private static void RegisterBeforeEveryHook(Type type, MethodInfo method, Before
{
var hookType = attr.HookType;
var order = attr.Order;
-
+
switch (hookType)
{
case HookType.Test:
@@ -343,7 +356,7 @@ private static void RegisterAfterEveryHook(Type type, MethodInfo method, AfterEv
{
var hookType = attr.HookType;
var order = attr.Order;
-
+
switch (hookType)
{
case HookType.Test:
@@ -574,7 +587,7 @@ private static Func