diff --git a/TUnit.Assertions.Tests/TUnit.Assertions.Tests.csproj b/TUnit.Assertions.Tests/TUnit.Assertions.Tests.csproj index 8811738112..da77d1b8fb 100644 --- a/TUnit.Assertions.Tests/TUnit.Assertions.Tests.csproj +++ b/TUnit.Assertions.Tests/TUnit.Assertions.Tests.csproj @@ -14,6 +14,10 @@ + + + + \ No newline at end of file diff --git a/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet10_0.verified.txt b/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet10_0.verified.txt index 94f9d7a233..e869041ae4 100644 --- a/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet10_0.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet10_0.verified.txt @@ -1085,3 +1085,171 @@ internal static class STAThreadTests_STA_WithExceptionHandling_ModuleInitializer global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithExceptionHandling_TestSource_GUID()); } } + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class STAThreadTests_STA_WithThrowsNothingAssertion_TestSource_GUID : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "STA_WithThrowsNothingAssertion", + TestClassType = typeof(global::TUnit.TestProject.STAThreadTests), + TestMethodName = "STA_WithThrowsNothingAssertion", + Dependencies = global::System.Array.Empty(), + AttributeFactory = () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.Executors.STAThreadExecutorAttribute(), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass), + new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows), + new global::TUnit.Core.RepeatAttribute(100), + new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = global::System.Array.Empty(), + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 194, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STA_WithThrowsNothingAssertion", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = global::System.Array.Empty(), + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.STAThreadTests", () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STAThreadTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = global::System.Array.Empty(), + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => new global::TUnit.TestProject.STAThreadTests(), + InvokeTypedTest = async (instance, args, cancellationToken) => + { + await instance.STA_WithThrowsNothingAssertion(); + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class STAThreadTests_STA_WithThrowsNothingAssertion_ModuleInitializer_GUID +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithThrowsNothingAssertion_TestSource_GUID()); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class STAThreadTests_STA_WithFuncAssertion_TestSource_GUID : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "STA_WithFuncAssertion", + TestClassType = typeof(global::TUnit.TestProject.STAThreadTests), + TestMethodName = "STA_WithFuncAssertion", + Dependencies = global::System.Array.Empty(), + AttributeFactory = () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.Executors.STAThreadExecutorAttribute(), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass), + new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows), + new global::TUnit.Core.RepeatAttribute(100), + new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = global::System.Array.Empty(), + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 211, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STA_WithFuncAssertion", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = global::System.Array.Empty(), + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.STAThreadTests", () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STAThreadTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = global::System.Array.Empty(), + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => new global::TUnit.TestProject.STAThreadTests(), + InvokeTypedTest = async (instance, args, cancellationToken) => + { + await instance.STA_WithFuncAssertion(); + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class STAThreadTests_STA_WithFuncAssertion_ModuleInitializer_GUID +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithFuncAssertion_TestSource_GUID()); + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet8_0.verified.txt b/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet8_0.verified.txt index 94f9d7a233..e869041ae4 100644 --- a/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet8_0.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet8_0.verified.txt @@ -1085,3 +1085,171 @@ internal static class STAThreadTests_STA_WithExceptionHandling_ModuleInitializer global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithExceptionHandling_TestSource_GUID()); } } + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class STAThreadTests_STA_WithThrowsNothingAssertion_TestSource_GUID : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "STA_WithThrowsNothingAssertion", + TestClassType = typeof(global::TUnit.TestProject.STAThreadTests), + TestMethodName = "STA_WithThrowsNothingAssertion", + Dependencies = global::System.Array.Empty(), + AttributeFactory = () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.Executors.STAThreadExecutorAttribute(), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass), + new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows), + new global::TUnit.Core.RepeatAttribute(100), + new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = global::System.Array.Empty(), + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 194, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STA_WithThrowsNothingAssertion", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = global::System.Array.Empty(), + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.STAThreadTests", () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STAThreadTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = global::System.Array.Empty(), + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => new global::TUnit.TestProject.STAThreadTests(), + InvokeTypedTest = async (instance, args, cancellationToken) => + { + await instance.STA_WithThrowsNothingAssertion(); + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class STAThreadTests_STA_WithThrowsNothingAssertion_ModuleInitializer_GUID +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithThrowsNothingAssertion_TestSource_GUID()); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class STAThreadTests_STA_WithFuncAssertion_TestSource_GUID : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "STA_WithFuncAssertion", + TestClassType = typeof(global::TUnit.TestProject.STAThreadTests), + TestMethodName = "STA_WithFuncAssertion", + Dependencies = global::System.Array.Empty(), + AttributeFactory = () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.Executors.STAThreadExecutorAttribute(), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass), + new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows), + new global::TUnit.Core.RepeatAttribute(100), + new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = global::System.Array.Empty(), + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 211, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STA_WithFuncAssertion", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = global::System.Array.Empty(), + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.STAThreadTests", () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STAThreadTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = global::System.Array.Empty(), + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => new global::TUnit.TestProject.STAThreadTests(), + InvokeTypedTest = async (instance, args, cancellationToken) => + { + await instance.STA_WithFuncAssertion(); + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class STAThreadTests_STA_WithFuncAssertion_ModuleInitializer_GUID +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithFuncAssertion_TestSource_GUID()); + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet9_0.verified.txt b/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet9_0.verified.txt index 94f9d7a233..e869041ae4 100644 --- a/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet9_0.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.DotNet9_0.verified.txt @@ -1085,3 +1085,171 @@ internal static class STAThreadTests_STA_WithExceptionHandling_ModuleInitializer global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithExceptionHandling_TestSource_GUID()); } } + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class STAThreadTests_STA_WithThrowsNothingAssertion_TestSource_GUID : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "STA_WithThrowsNothingAssertion", + TestClassType = typeof(global::TUnit.TestProject.STAThreadTests), + TestMethodName = "STA_WithThrowsNothingAssertion", + Dependencies = global::System.Array.Empty(), + AttributeFactory = () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.Executors.STAThreadExecutorAttribute(), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass), + new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows), + new global::TUnit.Core.RepeatAttribute(100), + new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = global::System.Array.Empty(), + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 194, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STA_WithThrowsNothingAssertion", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = global::System.Array.Empty(), + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.STAThreadTests", () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STAThreadTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = global::System.Array.Empty(), + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => new global::TUnit.TestProject.STAThreadTests(), + InvokeTypedTest = async (instance, args, cancellationToken) => + { + await instance.STA_WithThrowsNothingAssertion(); + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class STAThreadTests_STA_WithThrowsNothingAssertion_ModuleInitializer_GUID +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithThrowsNothingAssertion_TestSource_GUID()); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class STAThreadTests_STA_WithFuncAssertion_TestSource_GUID : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "STA_WithFuncAssertion", + TestClassType = typeof(global::TUnit.TestProject.STAThreadTests), + TestMethodName = "STA_WithFuncAssertion", + Dependencies = global::System.Array.Empty(), + AttributeFactory = () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.Executors.STAThreadExecutorAttribute(), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass), + new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows), + new global::TUnit.Core.RepeatAttribute(100), + new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = global::System.Array.Empty(), + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 211, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STA_WithFuncAssertion", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = global::System.Array.Empty(), + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.STAThreadTests", () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STAThreadTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = global::System.Array.Empty(), + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => new global::TUnit.TestProject.STAThreadTests(), + InvokeTypedTest = async (instance, args, cancellationToken) => + { + await instance.STA_WithFuncAssertion(); + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class STAThreadTests_STA_WithFuncAssertion_ModuleInitializer_GUID +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithFuncAssertion_TestSource_GUID()); + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.Net4_7.verified.txt b/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.Net4_7.verified.txt index c2cd3f5248..a31a6cc141 100644 --- a/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.Net4_7.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/STAThreadTests.Test.Net4_7.verified.txt @@ -1085,3 +1085,171 @@ internal static class STAThreadTests_STA_WithExceptionHandling_ModuleInitializer global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithExceptionHandling_TestSource_GUID()); } } + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class STAThreadTests_STA_WithThrowsNothingAssertion_TestSource_GUID : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "STA_WithThrowsNothingAssertion", + TestClassType = typeof(global::TUnit.TestProject.STAThreadTests), + TestMethodName = "STA_WithThrowsNothingAssertion", + Dependencies = global::System.Array.Empty(), + AttributeFactory = () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.Executors.STAThreadExecutorAttribute(), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass), + new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows), + new global::TUnit.Core.RepeatAttribute(100), + new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = global::System.Array.Empty(), + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 194, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STA_WithThrowsNothingAssertion", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = global::System.Array.Empty(), + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.STAThreadTests", () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STAThreadTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = global::System.Array.Empty(), + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => new global::TUnit.TestProject.STAThreadTests(), + InvokeTypedTest = async (instance, args, cancellationToken) => + { + await instance.STA_WithThrowsNothingAssertion(); + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class STAThreadTests_STA_WithThrowsNothingAssertion_ModuleInitializer_GUID +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithThrowsNothingAssertion_TestSource_GUID()); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class STAThreadTests_STA_WithFuncAssertion_TestSource_GUID : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "STA_WithFuncAssertion", + TestClassType = typeof(global::TUnit.TestProject.STAThreadTests), + TestMethodName = "STA_WithFuncAssertion", + Dependencies = global::System.Array.Empty(), + AttributeFactory = () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.Executors.STAThreadExecutorAttribute(), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass), + new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows), + new global::TUnit.Core.RepeatAttribute(100), + new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = global::System.Array.Empty(), + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 211, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STA_WithFuncAssertion", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = global::System.Array.Empty(), + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.STAThreadTests", () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.STAThreadTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.STAThreadTests)), + Name = "STAThreadTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = global::System.Array.Empty(), + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => new global::TUnit.TestProject.STAThreadTests(), + InvokeTypedTest = async (instance, args, cancellationToken) => + { + await instance.STA_WithFuncAssertion(); + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class STAThreadTests_STA_WithFuncAssertion_ModuleInitializer_GUID +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.STAThreadTests), new STAThreadTests_STA_WithFuncAssertion_TestSource_GUID()); + } +} diff --git a/TUnit.Core/Attributes/Executors/CultureAttribute.cs b/TUnit.Core/Attributes/Executors/CultureAttribute.cs index 30e3706921..be5f45c35e 100644 --- a/TUnit.Core/Attributes/Executors/CultureAttribute.cs +++ b/TUnit.Core/Attributes/Executors/CultureAttribute.cs @@ -12,10 +12,7 @@ public CultureAttribute(string cultureName) : this(CultureInfo.GetCultureInfo(cu public int Order => 0; -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] -#endif - public ValueTask OnTestRegistered(TestRegisteredContext context) +public ValueTask OnTestRegistered(TestRegisteredContext context) { context.SetTestExecutor(new CultureExecutor(cultureInfo)); return default(ValueTask); diff --git a/TUnit.Core/Attributes/Executors/STAThreadExecutorAttribute.cs b/TUnit.Core/Attributes/Executors/STAThreadExecutorAttribute.cs index 52b43a538e..a5a9f3a3ed 100644 --- a/TUnit.Core/Attributes/Executors/STAThreadExecutorAttribute.cs +++ b/TUnit.Core/Attributes/Executors/STAThreadExecutorAttribute.cs @@ -9,10 +9,7 @@ public class STAThreadExecutorAttribute : TUnitAttribute, ITestRegisteredEventRe { public int Order => 0; -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] -#endif - public ValueTask OnTestRegistered(TestRegisteredContext context) +public ValueTask OnTestRegistered(TestRegisteredContext context) { var executor = new STAThreadExecutor(); context.SetTestExecutor(executor); diff --git a/TUnit.Core/Attributes/Executors/TestExecutorAttribute.cs b/TUnit.Core/Attributes/Executors/TestExecutorAttribute.cs index 9d32a2d863..368c79bce2 100644 --- a/TUnit.Core/Attributes/Executors/TestExecutorAttribute.cs +++ b/TUnit.Core/Attributes/Executors/TestExecutorAttribute.cs @@ -8,10 +8,7 @@ namespace TUnit.Core.Executors; { public int Order => 0; -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] -#endif - public ValueTask OnTestRegistered(TestRegisteredContext context) +public ValueTask OnTestRegistered(TestRegisteredContext context) { context.SetTestExecutor(new T()); return default(ValueTask); @@ -23,10 +20,7 @@ public sealed class TestExecutorAttribute([DynamicallyAccessedMembers(Dynamicall { public int Order => 0; -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] -#endif - public ValueTask OnTestRegistered(TestRegisteredContext context) +public ValueTask OnTestRegistered(TestRegisteredContext context) { context.SetTestExecutor((ITestExecutor) Activator.CreateInstance(type)!); return default(ValueTask); diff --git a/TUnit.Core/Attributes/IScopedAttribute.cs b/TUnit.Core/Attributes/IScopedAttribute.cs index 0e306e9d0b..4377f7f5c0 100644 --- a/TUnit.Core/Attributes/IScopedAttribute.cs +++ b/TUnit.Core/Attributes/IScopedAttribute.cs @@ -1,5 +1,6 @@ namespace TUnit.Core; -public interface IScopedAttribute; - -public interface IScopedAttribute : IScopedAttribute where T : Attribute; +public interface IScopedAttribute +{ + public Type ScopeType { get; } +} diff --git a/TUnit.Core/Attributes/ParallelLimiterAttribute.cs b/TUnit.Core/Attributes/ParallelLimiterAttribute.cs index 1b5cc88b84..5a3ae08814 100644 --- a/TUnit.Core/Attributes/ParallelLimiterAttribute.cs +++ b/TUnit.Core/Attributes/ParallelLimiterAttribute.cs @@ -6,7 +6,7 @@ namespace TUnit.Core; /// Limits the number of tests that can run in parallel for a test assembly, class, or method. /// /// -/// The type that implements and defines the maximum number +/// The type that implements and defines the maximum number /// of tests that can execute concurrently. /// /// @@ -15,7 +15,7 @@ namespace TUnit.Core; /// class, or method, it limits how many tests from that scope can run simultaneously. /// /// -/// The parallelism limit is defined by the property of the +/// The parallelism limit is defined by the property of the /// instance. This value is used to create a semaphore that /// controls concurrent test execution. /// @@ -31,14 +31,14 @@ namespace TUnit.Core; /// /// // Apply to an assembly to limit all tests /// [assembly: ParallelLimiter<DefaultParallelLimit>] -/// +/// /// // Apply to a class to limit tests in that class /// [ParallelLimiter<ParallelLimit3>] /// public class MyTestClass /// { /// // Tests in this class will run with a maximum of 3 in parallel /// } -/// +/// /// // Apply to a specific test method /// [Test] /// [ParallelLimiter<ParallelLimit1>] @@ -57,10 +57,7 @@ public sealed class ParallelLimiterAttribute : TUnitAttribute, I public int Order => 0; /// -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] -#endif - public ValueTask OnTestRegistered(TestRegisteredContext context) +public ValueTask OnTestRegistered(TestRegisteredContext context) { context.SetParallelLimiter(new TParallelLimit()); return default(ValueTask); diff --git a/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs b/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs index 0ff0cc87ff..8c3e58345d 100644 --- a/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs +++ b/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs @@ -27,10 +27,7 @@ public ArgumentsAttribute(params object?[]? values) await Task.CompletedTask; } -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] -#endif - public ValueTask OnTestRegistered(TestRegisteredContext context) +public ValueTask OnTestRegistered(TestRegisteredContext context) { if(!string.IsNullOrEmpty(Skip)) { @@ -55,10 +52,7 @@ public override async IAsyncEnumerable>> GetTypedDataRowsAsync(Data await default(ValueTask); } -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] -#endif - public ValueTask OnTestRegistered(TestRegisteredContext context) +public ValueTask OnTestRegistered(TestRegisteredContext context) { if(!string.IsNullOrEmpty(Skip)) { diff --git a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs index bc013e88a1..94570bcbb4 100644 --- a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs @@ -13,21 +13,21 @@ namespace TUnit.Core; /// /// /// The display name can include parameter placeholders in the format of "$parameterName" which will be -/// replaced with the actual parameter values during test execution. For test methods, method parameters +/// replaced with the actual parameter values during test execution. For test methods, method parameters /// will be used for substitution. For test classes, constructor parameters will be used for substitution. For example: /// /// [Test] /// [Arguments("John", 25)] /// [DisplayName("User $name is $age years old")] /// public void TestUser(string name, int age) { ... } -/// +/// /// [Arguments("TestData")] /// [DisplayName("Class with data: $data")] /// public class MyTestClass(string data) { ... } /// /// /// -/// When these tests run, the display names would appear as "User John is 25 years old" and +/// When these tests run, the display names would appear as "User John is 25 years old" and /// "Class with data: TestData" respectively. /// /// @@ -36,7 +36,7 @@ namespace TUnit.Core; /// For methods, method parameter names can be referenced. For classes, constructor parameter names can be referenced. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)] -public sealed class DisplayNameAttribute(string displayName) : DisplayNameFormatterAttribute, IScopedAttribute +public sealed class DisplayNameAttribute(string displayName) : DisplayNameFormatterAttribute, IScopedAttribute { /// protected override string FormatDisplayName(DiscoveredTestContext context) @@ -77,4 +77,6 @@ protected override string FormatDisplayName(DiscoveredTestContext context) return mutableDisplayName; } + + public Type ScopeType => typeof(DisplayNameAttribute); } diff --git a/TUnit.Core/Attributes/TestMetadata/ExecutionPriorityAttribute.cs b/TUnit.Core/Attributes/TestMetadata/ExecutionPriorityAttribute.cs index 0013b71cfb..cf6077d71a 100644 --- a/TUnit.Core/Attributes/TestMetadata/ExecutionPriorityAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/ExecutionPriorityAttribute.cs @@ -4,7 +4,7 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public class ExecutionPriorityAttribute : SingleTUnitAttribute, ITestDiscoveryEventReceiver, IScopedAttribute +public class ExecutionPriorityAttribute : SingleTUnitAttribute, ITestDiscoveryEventReceiver, IScopedAttribute { public Priority Priority { get; } public int Order => 0; @@ -19,4 +19,6 @@ public ValueTask OnTestDiscovered(DiscoveredTestContext context) context.SetPriority(Priority); return default(ValueTask); } + + public Type ScopeType => typeof(ExecutionPriorityAttribute); } diff --git a/TUnit.Core/Attributes/TestMetadata/NotInParallelAttribute.cs b/TUnit.Core/Attributes/TestMetadata/NotInParallelAttribute.cs index 7475abe747..aed90bacbd 100644 --- a/TUnit.Core/Attributes/TestMetadata/NotInParallelAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/NotInParallelAttribute.cs @@ -3,7 +3,7 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public class NotInParallelAttribute : SingleTUnitAttribute, ITestDiscoveryEventReceiver, IScopedAttribute +public class NotInParallelAttribute : SingleTUnitAttribute, ITestDiscoveryEventReceiver, IScopedAttribute { public string[] ConstraintKeys { get; } = []; @@ -39,4 +39,6 @@ public ValueTask OnTestDiscovered(DiscoveredTestContext context) }); return default(ValueTask); } + + public Type ScopeType => typeof(NotInParallelAttribute); } diff --git a/TUnit.Core/Attributes/TestMetadata/RepeatAttribute.cs b/TUnit.Core/Attributes/TestMetadata/RepeatAttribute.cs index 172ecd8740..783a4147df 100644 --- a/TUnit.Core/Attributes/TestMetadata/RepeatAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/RepeatAttribute.cs @@ -2,7 +2,7 @@ // Don't think there's a way to enable inheritance on this because the source generator needs to access the constructor argument [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public sealed class RepeatAttribute : TUnitAttribute, IScopedAttribute +public sealed class RepeatAttribute : TUnitAttribute, IScopedAttribute { public int Times { get; } @@ -15,4 +15,6 @@ public RepeatAttribute(int times) Times = times; } + + public Type ScopeType => typeof(RepeatAttribute); } diff --git a/TUnit.Core/Attributes/TestMetadata/RetryAttribute.cs b/TUnit.Core/Attributes/TestMetadata/RetryAttribute.cs index 14eeecdfc7..921e421550 100644 --- a/TUnit.Core/Attributes/TestMetadata/RetryAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/RetryAttribute.cs @@ -38,7 +38,7 @@ namespace TUnit.Core; /// /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public class RetryAttribute : TUnitAttribute, ITestDiscoveryEventReceiver, IScopedAttribute +public class RetryAttribute : TUnitAttribute, ITestDiscoveryEventReceiver, IScopedAttribute { /// public int Order => 0; @@ -97,4 +97,6 @@ public ValueTask OnTestDiscovered(DiscoveredTestContext context) context.SetRetryLimit(Times, ShouldRetry); return default(ValueTask); } + + public Type ScopeType => typeof(RetryAttribute); } diff --git a/TUnit.Core/Attributes/TestMetadata/SkipAttribute.cs b/TUnit.Core/Attributes/TestMetadata/SkipAttribute.cs index f57dff0f0d..0b5b7e6feb 100644 --- a/TUnit.Core/Attributes/TestMetadata/SkipAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/SkipAttribute.cs @@ -16,9 +16,6 @@ public SkipAttribute(string reason) public int Order => int.MinValue; /// -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] -#endif public async ValueTask OnTestRegistered(TestRegisteredContext context) { if (await ShouldSkip(context)) diff --git a/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs b/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs index 589ae05cce..2cde57aa09 100644 --- a/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs @@ -32,7 +32,7 @@ namespace TUnit.Core; /// /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public class TimeoutAttribute(int timeoutInMilliseconds) : TUnitAttribute, ITestDiscoveryEventReceiver, IHookRegisteredEventReceiver, IScopedAttribute +public class TimeoutAttribute(int timeoutInMilliseconds) : TUnitAttribute, ITestDiscoveryEventReceiver, IHookRegisteredEventReceiver, IScopedAttribute { /// public int Order => 0; @@ -56,4 +56,6 @@ public ValueTask OnHookRegistered(HookRegisteredContext context) context.Timeout = Timeout; return default(ValueTask); } + + public Type ScopeType => typeof(TimeoutAttribute); } diff --git a/TUnit.Core/Executors/DedicatedThreadExecutor.cs b/TUnit.Core/Executors/DedicatedThreadExecutor.cs index 9871446ada..fc1a7bb80e 100644 --- a/TUnit.Core/Executors/DedicatedThreadExecutor.cs +++ b/TUnit.Core/Executors/DedicatedThreadExecutor.cs @@ -87,7 +87,7 @@ private void ExecuteAsyncActionWithMessagePump(Func action, TaskCompl var spinWait = new SpinWait(); var lastTimeCheck = DateTime.UtcNow; const int TimeCheckIntervalMs = 100; - + while (!task.IsCompleted) { var hadWork = dedicatedContext.ProcessPendingWork(); @@ -148,8 +148,8 @@ private static void HandleTaskCompletion(Task task, TaskCompletionSource /// Called when a test is registered /// - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif ValueTask OnTestRegistered(TestRegisteredContext context); } diff --git a/TUnit.Core/PropertyInjection/PropertyInjectionCache.cs b/TUnit.Core/PropertyInjection/PropertyInjectionCache.cs index 161e647840..c40e1df908 100644 --- a/TUnit.Core/PropertyInjection/PropertyInjectionCache.cs +++ b/TUnit.Core/PropertyInjection/PropertyInjectionCache.cs @@ -25,9 +25,6 @@ internal static class PropertyInjectionCache /// The plan builder will use source-generated metadata if available, /// otherwise falls back to reflection-based discovery. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public static PropertyInjectionPlan GetOrCreatePlan(Type type) { return _injectionPlans.GetOrAdd(type, _ => PropertyInjectionPlanBuilder.Build(type)); @@ -36,9 +33,6 @@ public static PropertyInjectionPlan GetOrCreatePlan(Type type) /// /// Checks if a type has injectable properties using caching. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public static bool HasInjectableProperties(Type type) { return _shouldInjectCache.GetOrAdd(type, t => diff --git a/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs b/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs index 774d274549..5bc5e1962b 100644 --- a/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs +++ b/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs @@ -14,9 +14,6 @@ internal static class PropertyInjectionPlanBuilder /// Creates an injection plan for source-generated mode. /// Walks the inheritance chain to include all injectable properties from base classes. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("BaseType reflection required for inheritance support")] - #endif public static PropertyInjectionPlan BuildSourceGeneratedPlan(Type type) { var allProperties = new List(); @@ -63,7 +60,7 @@ public static PropertyInjectionPlan BuildReflectionPlan(Type type) { var propertyDataSourcePairs = new List<(PropertyInfo property, IDataSourceAttribute dataSource)>(); var processedProperties = new HashSet(); - + // Walk up the inheritance chain to find all properties with data source attributes var currentType = type; while (currentType != null && currentType != typeof(object)) @@ -71,7 +68,7 @@ public static PropertyInjectionPlan BuildReflectionPlan(Type type) var properties = currentType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) .Where(p => p.CanWrite || p.SetMethod?.IsPublic == false); // Include init-only properties - + foreach (var property in properties) { // Skip if we've already processed a property with this name (overridden in derived class) @@ -79,7 +76,7 @@ public static PropertyInjectionPlan BuildReflectionPlan(Type type) { continue; } - + // Check for data source attributes, including inherited attributes foreach (var attr in property.GetCustomAttributes(inherit: true)) { @@ -90,10 +87,10 @@ public static PropertyInjectionPlan BuildReflectionPlan(Type type) } } } - + currentType = currentType.BaseType; } - + return new PropertyInjectionPlan { Type = type, @@ -106,13 +103,11 @@ public static PropertyInjectionPlan BuildReflectionPlan(Type type) /// /// Builds an injection plan based on the current execution mode. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Property injection plan building requires reflection")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Source gen mode has its own path>")] public static PropertyInjectionPlan Build(Type type) { - return SourceRegistrar.IsEnabled - ? BuildSourceGeneratedPlan(type) + return SourceRegistrar.IsEnabled + ? BuildSourceGeneratedPlan(type) : BuildReflectionPlan(type); } } diff --git a/TUnit.Core/Tracking/ObjectTracker.cs b/TUnit.Core/Tracking/ObjectTracker.cs index 251a8fa4a7..8e702a6262 100644 --- a/TUnit.Core/Tracking/ObjectTracker.cs +++ b/TUnit.Core/Tracking/ObjectTracker.cs @@ -12,9 +12,6 @@ internal class ObjectTracker(TrackableObjectGraphProvider trackableObjectGraphPr { private static readonly ConcurrentDictionary _trackedObjects = new(); - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Trackable object discovery uses reflection for property injection")] - #endif public void TrackObjects(TestContext testContext) { var alreadyTracked = testContext.TrackedObjects.SelectMany(x => x.Value).ToHashSet(); diff --git a/TUnit.Core/Tracking/TrackableObjectGraphProvider.cs b/TUnit.Core/Tracking/TrackableObjectGraphProvider.cs index 9668371d53..6aefd20244 100644 --- a/TUnit.Core/Tracking/TrackableObjectGraphProvider.cs +++ b/TUnit.Core/Tracking/TrackableObjectGraphProvider.cs @@ -7,9 +7,6 @@ namespace TUnit.Core.Tracking; internal class TrackableObjectGraphProvider { - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Trackable object discovery uses reflection for property injection")] - #endif public ConcurrentDictionary> GetTrackableObjects(TestContext testContext) { var visitedObjects = testContext.TrackedObjects; @@ -67,9 +64,6 @@ public IEnumerable GetStaticPropertyTrackableObjects() } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Nested object tracking uses reflection for property discovery")] - #endif private void AddNestedTrackableObjects(object obj, ConcurrentDictionary> visitedObjects, int currentDepth) { var plan = PropertyInjectionCache.GetOrCreatePlan(obj.GetType()); diff --git a/TUnit.Engine/Building/Interfaces/ITestBuilder.cs b/TUnit.Engine/Building/Interfaces/ITestBuilder.cs index 7122df3f00..e2066eadff 100644 --- a/TUnit.Engine/Building/Interfaces/ITestBuilder.cs +++ b/TUnit.Engine/Building/Interfaces/ITestBuilder.cs @@ -23,10 +23,9 @@ internal interface ITestBuilder /// /// The test metadata with DataCombinationGenerator /// Collection of executable tests for all data combinations - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces")] - [RequiresDynamicCode("Hook registration may involve dynamic delegate creation")] - #endif +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode("Test building in reflection mode uses generic type resolution which requires unreferenced code")] +#endif Task> BuildTestsFromMetadataAsync(TestMetadata metadata); /// @@ -35,10 +34,9 @@ internal interface ITestBuilder /// The test metadata with DataCombinationGenerator /// Cancellation token /// Stream of executable tests for all data combinations - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces")] - [RequiresDynamicCode("Hook registration may involve dynamic delegate creation")] - #endif +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode("Test building in reflection mode uses generic type resolution which requires unreferenced code")] +#endif IAsyncEnumerable BuildTestsStreamingAsync( TestMetadata metadata, CancellationToken cancellationToken = default); diff --git a/TUnit.Engine/Building/TestBuilder.cs b/TUnit.Engine/Building/TestBuilder.cs index 7d465c299b..37b567c5ce 100644 --- a/TUnit.Engine/Building/TestBuilder.cs +++ b/TUnit.Engine/Building/TestBuilder.cs @@ -107,10 +107,9 @@ private async Task CreateInstance(TestMetadata metadata, Type[] resolved } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Hook discovery uses reflection on methods and attributes")] - [RequiresDynamicCode("Hook registration may involve dynamic delegate creation")] - #endif +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode("Test building in reflection mode uses generic type resolution which requires unreferenced code")] +#endif public async Task> BuildTestsFromMetadataAsync(TestMetadata metadata) { var tests = new List(); @@ -609,9 +608,6 @@ private static Type[] TryInferClassGenericsFromDataSources(TestMetadata metadata return resolvedTypes; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private async Task GetDataSourcesAsync(IDataSourceAttribute[] dataSources) { if (dataSources.Length == 0) @@ -632,9 +628,6 @@ private async Task GetDataSourcesAsync(IDataSourceAttrib /// Ensures a data source is initialized before use and returns data rows. /// This centralizes the initialization logic for all data source usage. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private async IAsyncEnumerable>> GetInitializedDataRowsAsync( IDataSourceAttribute dataSource, DataGeneratorMetadata dataGeneratorMetadata) @@ -722,9 +715,6 @@ public async Task BuildTestAsync(TestMetadata metadata, } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private async ValueTask CreateTestContextAsync(string testId, TestMetadata metadata, TestData testData, TestBuilderContext testBuilderContext) { // Use attributes from context if available, or create new ones @@ -766,10 +756,6 @@ private async ValueTask CreateTestContextAsync(string testId, TestM return context; } - - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task InvokeDiscoveryEventReceiversAsync(TestContext context) { var discoveredContext = new DiscoveredTestContext( @@ -781,17 +767,11 @@ private async Task InvokeDiscoveryEventReceiversAsync(TestContext context) } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task CreateFailedTestForDataGenerationError(TestMetadata metadata, Exception exception) { return await CreateFailedTestForDataGenerationError(metadata, exception, new TestDataCombination()); } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task CreateFailedTestForDataGenerationError(TestMetadata metadata, Exception exception, TestDataCombination combination) { var testId = TestIdentifierService.GenerateFailedTestId(metadata, combination); @@ -811,9 +791,6 @@ private async Task CreateFailedTestForDataGenerationErro }; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private async Task CreateFailedTestDetails(TestMetadata metadata, string testId) { return new TestDetails @@ -834,9 +811,6 @@ private async Task CreateFailedTestDetails(TestMetadata metadata, s }; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private async Task InitializeAttributesAsync(Attribute[] attributes) { // Initialize any attributes that need property injection or implement IAsyncInitializer @@ -871,18 +845,12 @@ private TestContext CreateFailedTestContext(TestMetadata metadata, TestDetails t - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task CreateFailedTestForInstanceDataSourceError(TestMetadata metadata, Exception exception) { var message = $"Failed to create instance for method data source expansion: {exception.Message}"; return await CreateFailedTestForDataGenerationError(metadata, exception); } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task CreateFailedTestForClassDataSourceCircularDependency(TestMetadata metadata) { var instanceClassDataSources = metadata.ClassDataSources @@ -1121,10 +1089,9 @@ internal class TestData public Type[] ResolvedMethodGenericArguments { get; set; } = Type.EmptyTypes; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - [RequiresDynamicCode("Hook registration may involve dynamic delegate creation")] - #endif +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode("Test building in reflection mode uses generic type resolution which requires unreferenced code")] +#endif public async IAsyncEnumerable BuildTestsStreamingAsync( TestMetadata metadata, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -1188,14 +1155,14 @@ public async IAsyncEnumerable BuildTestsStreamingAsync( var classDataLoopIndex = 0; await foreach (var classDataFactory in GetInitializedDataRowsAsync( - classDataSource, - DataGeneratorMetadataCreator.CreateDataGeneratorMetadata( - testMetadata: metadata, - testSessionId: _sessionId, - generatorType: DataGeneratorType.ClassParameters, - testClassInstance: null, - classInstanceArguments: null, - contextAccessor))) + classDataSource, + DataGeneratorMetadataCreator.CreateDataGeneratorMetadata( + testMetadata: metadata, + testSessionId: _sessionId, + generatorType: DataGeneratorType.ClassParameters, + testClassInstance: null, + classInstanceArguments: null, + contextAccessor)).WithCancellation(cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); classDataLoopIndex++; @@ -1260,9 +1227,9 @@ public async IAsyncEnumerable BuildTestsStreamingAsync( } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Generic type inference uses reflection on data sources and parameters")] - #endif +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode("Generic type resolution for instance creation uses reflection")] +#endif private Task CreateInstanceForMethodDataSources( TestMetadata metadata, int classDataAttributeIndex, int classDataLoopIndex, object?[] classData) { @@ -1305,10 +1272,9 @@ public async IAsyncEnumerable BuildTestsStreamingAsync( } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Hook discovery uses reflection on methods and attributes")] - [RequiresDynamicCode("Hook registration may involve dynamic delegate creation")] - #endif +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode("Generic type resolution for test building uses reflection")] +#endif private async Task BuildSingleTestAsync( TestMetadata metadata, Func> classDataFactory, diff --git a/TUnit.Engine/Building/TestBuilderPipeline.cs b/TUnit.Engine/Building/TestBuilderPipeline.cs index d51155be03..1ccb75146d 100644 --- a/TUnit.Engine/Building/TestBuilderPipeline.cs +++ b/TUnit.Engine/Building/TestBuilderPipeline.cs @@ -118,9 +118,6 @@ public async Task> BuildTestsFromMetadataAsy return testGroups.SelectMany(x => x); } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task GenerateDynamicTests(TestMetadata metadata) { // Get attributes first @@ -210,10 +207,9 @@ private async Task GenerateDynamicTests(TestMetadata m /// /// Build tests from a single metadata item, yielding them as they're created /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Hook discovery uses reflection on methods and attributes")] - [RequiresDynamicCode("Hook registration may involve dynamic delegate creation")] - #endif +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode("Test building in reflection mode uses generic type resolution which requires unreferenced code")] +#endif private async IAsyncEnumerable BuildTestsFromSingleMetadataAsync(TestMetadata metadata) { TestMetadata resolvedMetadata; @@ -453,9 +449,6 @@ private AbstractExecutableTest CreateFailedTestForGenericResolutionError(TestMet }; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task InvokeDiscoveryEventReceiversAsync(TestContext context) { var discoveredContext = new DiscoveredTestContext( diff --git a/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs b/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs index 02dd343654..884d87605e 100644 --- a/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs +++ b/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs @@ -2,6 +2,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using TUnit.Core; +using TUnit.Engine.Helpers; namespace TUnit.Engine.Discovery; @@ -110,7 +111,7 @@ internal static class ReflectionGenericTypeResolver { ParameterName = param.Name, BaseTypeConstraint = param.BaseType != typeof(object) ? param.BaseType : null, - InterfaceConstraints = param.GetInterfaces(), + InterfaceConstraints = AssemblyReferenceCache.GetInterfaces(param), HasDefaultConstructorConstraint = param.GenericParameterAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint), HasReferenceTypeConstraint = param.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint), HasValueTypeConstraint = param.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint), @@ -160,7 +161,7 @@ internal static class ReflectionGenericTypeResolver { ParameterName = param.Name, BaseTypeConstraint = param.BaseType != typeof(object) ? param.BaseType : null, - InterfaceConstraints = param.GetInterfaces(), + InterfaceConstraints = AssemblyReferenceCache.GetInterfaces(param), HasDefaultConstructorConstraint = param.GenericParameterAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint), HasReferenceTypeConstraint = param.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint), HasValueTypeConstraint = param.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint), diff --git a/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs b/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs index 04011559b3..7501cead8e 100644 --- a/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs +++ b/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs @@ -6,6 +6,7 @@ using TUnit.Core; using TUnit.Core.Hooks; using TUnit.Core.Interfaces; +using TUnit.Engine.Helpers; namespace TUnit.Engine.Discovery; @@ -70,10 +71,6 @@ private static void ClearSourceGeneratedHooks() /// Discovers and registers instance hooks for a specific closed generic type. /// This is needed because closed generic types are created at runtime and don't appear in assembly.GetTypes(). /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Hook discovery uses reflection on methods and attributes")] - [RequiresDynamicCode("Hook registration may involve dynamic delegate creation")] - #endif public static void DiscoverInstanceHooksForType(Type closedGenericType) { if (SourceRegistrar.IsEnabled) @@ -227,8 +224,7 @@ private static bool ShouldScanAssembly(Assembly assembly) return false; } - // Check if assembly references TUnit.Core - var referencedAssemblies = assembly.GetReferencedAssemblies(); + var referencedAssemblies = AssemblyReferenceCache.GetReferencedAssemblies(assembly); var referencesTUnit = false; foreach (var refAssembly in referencedAssemblies) { diff --git a/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs b/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs index 8f76d4c1ea..dd76cb83f8 100644 --- a/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs +++ b/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs @@ -8,6 +8,7 @@ using TUnit.Core.Helpers; using TUnit.Engine.Building; using TUnit.Engine.Building.Interfaces; +using TUnit.Engine.Helpers; namespace TUnit.Engine.Discovery; @@ -320,7 +321,7 @@ private static bool ShouldScanAssembly(Assembly assembly) // Don't return false here, continue with other checks } - var referencedAssemblies = assembly.GetReferencedAssemblies(); + var referencedAssemblies = AssemblyReferenceCache.GetReferencedAssemblies(assembly); var hasTUnitReference = false; foreach (var reference in referencedAssemblies) { @@ -1346,8 +1347,7 @@ private static void InferGenericTypeMapping(Type paramType, [DynamicallyAccessed } else { - // Check interfaces implemented by the argument type - foreach (var iface in argType.GetInterfaces()) + foreach (var iface in AssemblyReferenceCache.GetInterfaces(argType)) { if (iface.IsGenericType && iface.GetGenericTypeDefinition() == paramGenDef) { @@ -1368,7 +1368,7 @@ private static void InferGenericTypeMapping(Type paramType, [DynamicallyAccessed // Handle non-generic types that implement generic interfaces var paramGenDef = paramType.GetGenericTypeDefinition(); - foreach (var iface in argType.GetInterfaces()) + foreach (var iface in AssemblyReferenceCache.GetInterfaces(argType)) { if (iface.IsGenericType && iface.GetGenericTypeDefinition() == paramGenDef) { @@ -1418,8 +1418,7 @@ private static bool IsCovariantCompatible(Type paramType, [DynamicallyAccessedMe return false; } - // Get all interfaces from the argument type - var argInterfaces = argType.GetInterfaces(); + var argInterfaces = AssemblyReferenceCache.GetInterfaces(argType); if (argType.IsInterface) { argInterfaces = argInterfaces.Concat([argType]).ToArray(); diff --git a/TUnit.Engine/Framework/IRequestHandler.cs b/TUnit.Engine/Framework/IRequestHandler.cs index 0a6f8cf4c4..4704b0040a 100644 --- a/TUnit.Engine/Framework/IRequestHandler.cs +++ b/TUnit.Engine/Framework/IRequestHandler.cs @@ -9,7 +9,6 @@ namespace TUnit.Engine.Framework; internal interface IRequestHandler { #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] #endif Task HandleRequestAsync(TestExecutionRequest request, TUnitServiceProvider serviceProvider, ExecuteRequestContext context, ITestExecutionFilter? testExecutionFilter); diff --git a/TUnit.Engine/Framework/TUnitTestFramework.cs b/TUnit.Engine/Framework/TUnitTestFramework.cs index 81bc7fdfb8..95f02e927c 100644 --- a/TUnit.Engine/Framework/TUnitTestFramework.cs +++ b/TUnit.Engine/Framework/TUnitTestFramework.cs @@ -6,6 +6,7 @@ using Microsoft.Testing.Platform.Extensions.TestFramework; using Microsoft.Testing.Platform.Requests; using TUnit.Core; +using TUnit.Engine.Services; namespace TUnit.Engine.Framework; @@ -56,6 +57,12 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) serviceProvider.Initializer.Initialize(context); + // Initialize hook collection service to pre-compute global hooks + if (serviceProvider.HookCollectionService is HookCollectionService hookCollectionService) + { + await hookCollectionService.InitializeAsync(); + } + GlobalContext.Current = serviceProvider.ContextProvider.GlobalContext; GlobalContext.Current.GlobalLogger = serviceProvider.Logger; BeforeTestDiscoveryContext.Current = serviceProvider.ContextProvider.BeforeTestDiscoveryContext; diff --git a/TUnit.Engine/Framework/TestRequestHandler.cs b/TUnit.Engine/Framework/TestRequestHandler.cs index 5adc08ee29..15c75e9434 100644 --- a/TUnit.Engine/Framework/TestRequestHandler.cs +++ b/TUnit.Engine/Framework/TestRequestHandler.cs @@ -8,7 +8,6 @@ namespace TUnit.Engine.Framework; internal sealed class TestRequestHandler : IRequestHandler { #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] #endif public async Task HandleRequestAsync(TestExecutionRequest request, TUnitServiceProvider serviceProvider, ExecuteRequestContext context, ITestExecutionFilter? testExecutionFilter) @@ -32,7 +31,6 @@ public async Task HandleRequestAsync(TestExecutionRequest request, TUnitServiceP } #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] #endif private async Task HandleDiscoveryRequestAsync( @@ -60,7 +58,6 @@ private async Task HandleDiscoveryRequestAsync( } #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] #endif private async Task HandleRunRequestAsync( diff --git a/TUnit.Engine/Helpers/AssemblyReferenceCache.cs b/TUnit.Engine/Helpers/AssemblyReferenceCache.cs new file mode 100644 index 0000000000..b03b944abb --- /dev/null +++ b/TUnit.Engine/Helpers/AssemblyReferenceCache.cs @@ -0,0 +1,27 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace TUnit.Engine.Helpers; + +internal static class AssemblyReferenceCache +{ + private static readonly ConcurrentDictionary _assemblyCache = new(); + private static readonly ConcurrentDictionary _interfaceCache = new(); + +#if NET6_0_OR_GREATER + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Assembly.GetReferencedAssemblies is reflection-based but safe for checking references")] +#endif + public static AssemblyName[] GetReferencedAssemblies(Assembly assembly) + { + return _assemblyCache.GetOrAdd(assembly, a => a.GetReferencedAssemblies()); + } + +#if NET6_0_OR_GREATER + [UnconditionalSuppressMessage("Trimming", "IL2070", Justification = "Type.GetInterfaces is reflection-based but safe for type checking")] +#endif + public static Type[] GetInterfaces(Type type) + { + return _interfaceCache.GetOrAdd(type, t => t.GetInterfaces()); + } +} diff --git a/TUnit.Engine/Helpers/DisplayNameBuilder.cs b/TUnit.Engine/Helpers/DisplayNameBuilder.cs index 90b831754a..2aa1746616 100644 --- a/TUnit.Engine/Helpers/DisplayNameBuilder.cs +++ b/TUnit.Engine/Helpers/DisplayNameBuilder.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Text; using TUnit.Core; using TUnit.Core.Helpers; @@ -50,12 +51,16 @@ public static string FormatArguments(object?[] arguments) return string.Empty; } - var formattedArgs = new string[arguments.Length]; + var sb = new StringBuilder(); for (var i = 0; i < arguments.Length; i++) { - formattedArgs[i] = ArgumentFormatter.Format(arguments[i], []); + if (i > 0) + { + sb.Append(", "); + } + sb.Append(ArgumentFormatter.Format(arguments[i], [])); } - return string.Join(", ", formattedArgs); + return sb.ToString(); } /// @@ -68,12 +73,16 @@ public static string FormatArguments(object?[] arguments, List 0) + { + sb.Append(", "); + } + sb.Append(ArgumentFormatter.Format(arguments[i], formatters)); } - return string.Join(", ", formattedArgs); + return sb.ToString(); } /// diff --git a/TUnit.Engine/Interfaces/IHookCollectionService.cs b/TUnit.Engine/Interfaces/IHookCollectionService.cs index 589a9ceb54..3b9d4a96f8 100644 --- a/TUnit.Engine/Interfaces/IHookCollectionService.cs +++ b/TUnit.Engine/Interfaces/IHookCollectionService.cs @@ -5,21 +5,9 @@ namespace TUnit.Engine.Interfaces; internal interface IHookCollectionService { - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif ValueTask>> CollectBeforeTestHooksAsync(Type testClassType); - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif ValueTask>> CollectAfterTestHooksAsync(Type testClassType); - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif ValueTask>> CollectBeforeEveryTestHooksAsync(Type testClassType); - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif ValueTask>> CollectAfterEveryTestHooksAsync(Type testClassType); ValueTask>> CollectBeforeClassHooksAsync(Type testClassType); diff --git a/TUnit.Engine/Reporters/GitHubReporter.cs b/TUnit.Engine/Reporters/GitHubReporter.cs index a2725da186..69deaedac5 100644 --- a/TUnit.Engine/Reporters/GitHubReporter.cs +++ b/TUnit.Engine/Reporters/GitHubReporter.cs @@ -98,7 +98,14 @@ public Task AfterRunAsync(int exitCode, CancellationToken cancellation) ?.FrameworkDisplayName ?? RuntimeInformation.FrameworkDescription; - var last = _updates.ToDictionary(x => x.Key, x => x.Value.Last()); + var last = new Dictionary(_updates.Count); + foreach (var kvp in _updates) + { + if (kvp.Value.Count > 0) + { + last[kvp.Key] = kvp.Value[kvp.Value.Count - 1]; + } + } var passedCount = last.Count(x => x.Value.TestNode.Properties.AsEnumerable().Any(p => p is PassedTestNodeStateProperty)); diff --git a/TUnit.Engine/Scheduling/TestScheduler.cs b/TUnit.Engine/Scheduling/TestScheduler.cs index d22b0c3bfd..6a288f25b3 100644 --- a/TUnit.Engine/Scheduling/TestScheduler.cs +++ b/TUnit.Engine/Scheduling/TestScheduler.cs @@ -98,8 +98,8 @@ public async Task ScheduleAndExecuteAsync( } } - var executableTests = testList.Where(t => !testsInCircularDependencies.Contains(t)).ToList(); - if (executableTests.Count == 0) + var executableTests = testList.Where(t => !testsInCircularDependencies.Contains(t)).ToArray(); + if (executableTests.Length == 0) { await _logger.LogDebugAsync("No executable tests found after removing circular dependencies").ConfigureAwait(false); return true; diff --git a/TUnit.Engine/Services/DataSourceInitializer.cs b/TUnit.Engine/Services/DataSourceInitializer.cs index 06093c07b7..ac26d709a7 100644 --- a/TUnit.Engine/Services/DataSourceInitializer.cs +++ b/TUnit.Engine/Services/DataSourceInitializer.cs @@ -25,9 +25,6 @@ public void Initialize(PropertyInjectionService propertyInjectionService) /// Ensures a data source instance is fully initialized before use. /// This includes property injection and calling IAsyncInitializer if implemented. /// - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public async Task EnsureInitializedAsync( T dataSource, Dictionary? objectBag = null, @@ -62,9 +59,6 @@ public async Task EnsureInitializedAsync( /// /// Initializes a data source instance with the complete lifecycle. /// - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private async Task InitializeDataSourceAsync( object dataSource, Dictionary? objectBag, @@ -106,9 +100,6 @@ await _propertyInjectionService.InjectPropertiesIntoObjectAsync( /// Initializes all nested property-injected objects in depth-first order. /// This ensures that when the parent's IAsyncInitializer runs, all nested dependencies are already initialized. /// - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private async Task InitializeNestedObjectsAsync(object rootObject) { var objectsByDepth = new Dictionary>(capacity: 4); @@ -132,9 +123,6 @@ private async Task InitializeNestedObjectsAsync(object rootObject) /// /// Recursively collects all nested property-injected objects grouped by depth. /// - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private void CollectNestedObjects( object obj, Dictionary> objectsByDepth, diff --git a/TUnit.Engine/Services/EventReceiverOrchestrator.cs b/TUnit.Engine/Services/EventReceiverOrchestrator.cs index fc491fad84..68696795ce 100644 --- a/TUnit.Engine/Services/EventReceiverOrchestrator.cs +++ b/TUnit.Engine/Services/EventReceiverOrchestrator.cs @@ -84,9 +84,6 @@ obj is IFirstTestInAssemblyEventReceiver || // Fast-path checks with inlining [MethodImpl(MethodImplOptions.AggressiveInlining)] - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif public async ValueTask InvokeTestStartEventReceiversAsync(TestContext context, CancellationToken cancellationToken) { // Fast path - no allocation if no receivers @@ -98,9 +95,6 @@ public async ValueTask InvokeTestStartEventReceiversAsync(TestContext context, C await InvokeTestStartEventReceiversCore(context, cancellationToken); } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async ValueTask InvokeTestStartEventReceiversCore(TestContext context, CancellationToken cancellationToken) { // Filter scoped attributes - FilterScopedAttributes will materialize the collection @@ -125,9 +119,6 @@ private async ValueTask InvokeTestStartEventReceiversCore(TestContext context, C } [MethodImpl(MethodImplOptions.AggressiveInlining)] - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif public async ValueTask InvokeTestEndEventReceiversAsync(TestContext context, CancellationToken cancellationToken) { if (!_registry.HasTestEndReceivers()) @@ -138,9 +129,6 @@ public async ValueTask InvokeTestEndEventReceiversAsync(TestContext context, Can await InvokeTestEndEventReceiversCore(context, cancellationToken); } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async ValueTask InvokeTestEndEventReceiversCore(TestContext context, CancellationToken cancellationToken) { // Filter scoped attributes - FilterScopedAttributes will materialize the collection @@ -163,9 +151,6 @@ private async ValueTask InvokeTestEndEventReceiversCore(TestContext context, Can } [MethodImpl(MethodImplOptions.AggressiveInlining)] - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif public async ValueTask InvokeTestSkippedEventReceiversAsync(TestContext context, CancellationToken cancellationToken) { if (!_registry.HasTestSkippedReceivers()) @@ -176,9 +161,6 @@ public async ValueTask InvokeTestSkippedEventReceiversAsync(TestContext context, await InvokeTestSkippedEventReceiversCore(context, cancellationToken); } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async ValueTask InvokeTestSkippedEventReceiversCore(TestContext context, CancellationToken cancellationToken) { // Filter scoped attributes - FilterScopedAttributes will materialize the collection @@ -193,9 +175,6 @@ private async ValueTask InvokeTestSkippedEventReceiversCore(TestContext context, } } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif public async ValueTask InvokeTestDiscoveryEventReceiversAsync(TestContext context, DiscoveredTestContext discoveredContext, CancellationToken cancellationToken) { var eventReceivers = context.GetEligibleEventObjects() @@ -212,9 +191,6 @@ public async ValueTask InvokeTestDiscoveryEventReceiversAsync(TestContext contex } } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif public async ValueTask InvokeHookRegistrationEventReceiversAsync(HookRegisteredContext hookContext, CancellationToken cancellationToken) { // Filter scoped attributes to ensure only the highest priority one of each type is invoked diff --git a/TUnit.Engine/Services/HookCollectionService.cs b/TUnit.Engine/Services/HookCollectionService.cs index b99929bd5c..902f7ed057 100644 --- a/TUnit.Engine/Services/HookCollectionService.cs +++ b/TUnit.Engine/Services/HookCollectionService.cs @@ -12,10 +12,22 @@ internal sealed class HookCollectionService : IHookCollectionService private readonly EventReceiverOrchestrator _eventReceiverOrchestrator; private readonly ConcurrentDictionary>> _beforeTestHooksCache = new(); private readonly ConcurrentDictionary>> _afterTestHooksCache = new(); - private readonly ConcurrentDictionary>> _beforeEveryTestHooksCache = new(); - private readonly ConcurrentDictionary>> _afterEveryTestHooksCache = new(); private readonly ConcurrentDictionary>> _beforeClassHooksCache = new(); private readonly ConcurrentDictionary>> _afterClassHooksCache = new(); + private readonly ConcurrentDictionary>> _beforeAssemblyHooksCache = new(); + private readonly ConcurrentDictionary>> _afterAssemblyHooksCache = new(); + + // Pre-computed global hooks (computed once at initialization) + private IReadOnlyList>? _beforeEveryTestHooks; + private IReadOnlyList>? _afterEveryTestHooks; + private IReadOnlyList>? _beforeTestSessionHooks; + private IReadOnlyList>? _afterTestSessionHooks; + private IReadOnlyList>? _beforeTestDiscoveryHooks; + private IReadOnlyList>? _afterTestDiscoveryHooks; + private IReadOnlyList>? _beforeEveryClassHooks; + private IReadOnlyList>? _afterEveryClassHooks; + private IReadOnlyList>? _beforeEveryAssemblyHooks; + private IReadOnlyList>? _afterEveryAssemblyHooks; // Cache for processed hooks to avoid re-processing event receivers private readonly ConcurrentDictionary _processedHooks = new(); @@ -25,9 +37,220 @@ public HookCollectionService(EventReceiverOrchestrator eventReceiverOrchestrator _eventReceiverOrchestrator = eventReceiverOrchestrator; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif + public async ValueTask InitializeAsync() + { + // Pre-compute all global hooks that don't depend on specific types/assemblies + _beforeEveryTestHooks = await BuildGlobalBeforeEveryTestHooksAsync(); + _afterEveryTestHooks = await BuildGlobalAfterEveryTestHooksAsync(); + _beforeTestSessionHooks = BuildGlobalBeforeTestSessionHooks(); + _afterTestSessionHooks = BuildGlobalAfterTestSessionHooks(); + _beforeTestDiscoveryHooks = BuildGlobalBeforeTestDiscoveryHooks(); + _afterTestDiscoveryHooks = BuildGlobalAfterTestDiscoveryHooks(); + _beforeEveryClassHooks = BuildGlobalBeforeEveryClassHooks(); + _afterEveryClassHooks = BuildGlobalAfterEveryClassHooks(); + _beforeEveryAssemblyHooks = BuildGlobalBeforeEveryAssemblyHooks(); + _afterEveryAssemblyHooks = BuildGlobalAfterEveryAssemblyHooks(); + } + + private async Task>> BuildGlobalBeforeEveryTestHooksAsync() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.BeforeEveryTestHooks) + { + var hookFunc = await CreateStaticHookDelegateAsync(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(static h => h.order) + .ThenBy(static h => h.registrationIndex) + .Select(static h => h.hook) + .ToList(); + } + + private async Task>> BuildGlobalAfterEveryTestHooksAsync() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.AfterEveryTestHooks) + { + var hookFunc = await CreateStaticHookDelegateAsync(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(static h => h.order) + .ThenBy(static h => h.registrationIndex) + .Select(static h => h.hook) + .ToList(); + } + + private IReadOnlyList> BuildGlobalBeforeTestSessionHooks() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.BeforeTestSessionHooks) + { + var hookFunc = CreateTestSessionHookDelegate(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(h => h.order) + .ThenBy(h => h.registrationIndex) + .Select(h => h.hook) + .ToList(); + } + + private IReadOnlyList> BuildGlobalAfterTestSessionHooks() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.AfterTestSessionHooks) + { + var hookFunc = CreateTestSessionHookDelegate(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(h => h.order) + .ThenBy(h => h.registrationIndex) + .Select(h => h.hook) + .ToList(); + } + + private IReadOnlyList> BuildGlobalBeforeTestDiscoveryHooks() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.BeforeTestDiscoveryHooks) + { + var hookFunc = CreateBeforeTestDiscoveryHookDelegate(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(h => h.order) + .ThenBy(h => h.registrationIndex) + .Select(h => h.hook) + .ToList(); + } + + private IReadOnlyList> BuildGlobalAfterTestDiscoveryHooks() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.AfterTestDiscoveryHooks) + { + var hookFunc = CreateTestDiscoveryHookDelegate(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(h => h.order) + .ThenBy(h => h.registrationIndex) + .Select(h => h.hook) + .ToList(); + } + + private IReadOnlyList> BuildGlobalBeforeEveryClassHooks() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.BeforeEveryClassHooks) + { + var hookFunc = CreateClassHookDelegate(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(h => h.order) + .ThenBy(h => h.registrationIndex) + .Select(h => h.hook) + .ToList(); + } + + private IReadOnlyList> BuildGlobalAfterEveryClassHooks() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.AfterEveryClassHooks) + { + var hookFunc = CreateClassHookDelegate(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(h => h.order) + .ThenBy(h => h.registrationIndex) + .Select(h => h.hook) + .ToList(); + } + + private IReadOnlyList> BuildGlobalBeforeEveryAssemblyHooks() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.BeforeEveryAssemblyHooks) + { + var hookFunc = CreateAssemblyHookDelegate(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(h => h.order) + .ThenBy(h => h.registrationIndex) + .Select(h => h.hook) + .ToList(); + } + + private IReadOnlyList> BuildGlobalAfterEveryAssemblyHooks() + { + var allHooks = new List<(int order, int registrationIndex, Func hook)>(); + + foreach (var hook in Sources.AfterEveryAssemblyHooks) + { + var hookFunc = CreateAssemblyHookDelegate(hook); + allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); + } + + return allHooks + .OrderBy(h => h.order) + .ThenBy(h => h.registrationIndex) + .Select(h => h.hook) + .ToList(); + } + + private static void SortAndAddHooks( + List> target, + List<(int order, int registrationIndex, TDelegate hook)> hooks) + where TDelegate : Delegate + { + hooks.Sort((a, b) => a.order != b.order + ? a.order.CompareTo(b.order) + : a.registrationIndex.CompareTo(b.registrationIndex)); + + foreach (var (_, _, hook) in hooks) + { + target.Add((Func)(object)hook); + } + } + + private static void SortAndAddClassHooks( + List> target, + List<(int order, int registrationIndex, Func hook)> hooks) + { + hooks.Sort((a, b) => a.order != b.order + ? a.order.CompareTo(b.order) + : a.registrationIndex.CompareTo(b.registrationIndex)); + + foreach (var (_, _, hook) in hooks) + { + target.Add(hook); + } + } + private async Task ProcessHookRegistrationAsync(HookMethod hookMethod, CancellationToken cancellationToken = default) { // Only process each hook once @@ -48,9 +271,6 @@ private async Task ProcessHookRegistrationAsync(HookMethod hookMethod, Cancellat } } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif public async ValueTask>> CollectBeforeTestHooksAsync(Type testClassType) { if (_beforeTestHooksCache.TryGetValue(testClassType, out var cachedHooks)) @@ -63,9 +283,6 @@ public async ValueTask> return hooks; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task>> BuildBeforeTestHooksAsync(Type type) { var hooksByType = new List<(Type type, List<(int order, int registrationIndex, Func hook)> hooks)>(); @@ -115,15 +332,12 @@ private async Task>> Bu foreach (var (_, typeHooks) in hooksByType) { // Within each type level, sort by Order then by RegistrationIndex - finalHooks.AddRange(typeHooks.OrderBy(static h => h.order).ThenBy(static h => h.registrationIndex).Select(static h => h.hook)); + SortAndAddHooks(finalHooks, typeHooks); } return finalHooks; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif public async ValueTask>> CollectAfterTestHooksAsync(Type testClassType) { if (_afterTestHooksCache.TryGetValue(testClassType, out var cachedHooks)) @@ -136,9 +350,6 @@ public async ValueTask> return hooks; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task>> BuildAfterTestHooksAsync(Type type) { var hooksByType = new List<(Type type, List<(int order, int registrationIndex, Func hook)> hooks)>(); @@ -187,82 +398,20 @@ private async Task>> Bu foreach (var (_, typeHooks) in hooksByType) { // Within each type level, sort by Order then by RegistrationIndex - finalHooks.AddRange(typeHooks.OrderBy(static h => h.order).ThenBy(static h => h.registrationIndex).Select(static h => h.hook)); + SortAndAddHooks(finalHooks, typeHooks); } return finalHooks; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif - public async ValueTask>> CollectBeforeEveryTestHooksAsync(Type testClassType) + public ValueTask>> CollectBeforeEveryTestHooksAsync(Type testClassType) { - if (_beforeEveryTestHooksCache.TryGetValue(testClassType, out var cachedHooks)) - { - return cachedHooks; - } - - var hooks = await BuildBeforeEveryTestHooksAsync(testClassType); - _beforeEveryTestHooksCache.TryAdd(testClassType, hooks); - return hooks; - } - - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif - private async Task>> BuildBeforeEveryTestHooksAsync(Type type) - { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - // Collect all global BeforeEvery hooks - foreach (var hook in Sources.BeforeEveryTestHooks) - { - var hookFunc = await CreateStaticHookDelegateAsync(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - return allHooks - .OrderBy(static h => h.order) - .ThenBy(static h => h.registrationIndex) - .Select(static h => h.hook) - .ToList(); + return new ValueTask>>(_beforeEveryTestHooks ?? []); } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif - public async ValueTask>> CollectAfterEveryTestHooksAsync(Type testClassType) + public ValueTask>> CollectAfterEveryTestHooksAsync(Type testClassType) { - if (_afterEveryTestHooksCache.TryGetValue(testClassType, out var cachedHooks)) - { - return cachedHooks; - } - - var hooks = await BuildAfterEveryTestHooksAsync(testClassType); - _afterEveryTestHooksCache.TryAdd(testClassType, hooks); - return hooks; - } - - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif - private async Task>> BuildAfterEveryTestHooksAsync(Type type) - { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - // Collect all global AfterEvery hooks - foreach (var hook in Sources.AfterEveryTestHooks) - { - var hookFunc = await CreateStaticHookDelegateAsync(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - return allHooks - .OrderBy(static h => h.order) - .ThenBy(static h => h.registrationIndex) - .Select(static h => h.hook) - .ToList(); + return new ValueTask>>(_afterEveryTestHooks ?? []); } public ValueTask>> CollectBeforeClassHooksAsync(Type testClassType) @@ -308,15 +457,12 @@ public ValueTask>> currentType = currentType.BaseType; } - // For Before hooks: base class hooks run first - // Reverse the list since we collected from derived to base hooksByType.Reverse(); var finalHooks = new List>(); foreach (var (_, typeHooks) in hooksByType) { - // Within each type level, sort by Order then by RegistrationIndex - finalHooks.AddRange(typeHooks.OrderBy(static h => h.order).ThenBy(static h => h.registrationIndex).Select(static h => h.hook)); + SortAndAddClassHooks(finalHooks, typeHooks); } return finalHooks; @@ -368,14 +514,10 @@ public ValueTask>> currentType = currentType.BaseType; } - // For After hooks: derived class hooks run first - // No need to reverse since we collected from derived to base - var finalHooks = new List>(); foreach (var (_, typeHooks) in hooksByType) { - // Within each type level, sort by Order then by RegistrationIndex - finalHooks.AddRange(typeHooks.OrderBy(static h => h.order).ThenBy(static h => h.registrationIndex).Select(static h => h.hook)); + SortAndAddClassHooks(finalHooks, typeHooks); } return finalHooks; @@ -386,201 +528,92 @@ public ValueTask>> public ValueTask>> CollectBeforeAssemblyHooksAsync(Assembly assembly) { - var allHooks = new List<(int order, Func hook)>(); - - if (Sources.BeforeAssemblyHooks.TryGetValue(assembly, out var assemblyHooks)) + var hooks = _beforeAssemblyHooksCache.GetOrAdd(assembly, asm => { - foreach (var hook in assemblyHooks) + var allHooks = new List<(int order, Func hook)>(); + + if (Sources.BeforeAssemblyHooks.TryGetValue(asm, out var assemblyHooks)) { - var hookFunc = CreateAssemblyHookDelegate(hook); - allHooks.Add((hook.Order, hookFunc)); + foreach (var hook in assemblyHooks) + { + var hookFunc = CreateAssemblyHookDelegate(hook); + allHooks.Add((hook.Order, hookFunc)); + } } - } - var hooks = allHooks - .OrderBy(h => h.order) - .Select(h => h.hook) - .ToList(); + return allHooks + .OrderBy(h => h.order) + .Select(h => h.hook) + .ToList(); + }); return new ValueTask>>(hooks); } public ValueTask>> CollectAfterAssemblyHooksAsync(Assembly assembly) { - var allHooks = new List<(int order, Func hook)>(); - - if (Sources.AfterAssemblyHooks.TryGetValue(assembly, out var assemblyHooks)) + var hooks = _afterAssemblyHooksCache.GetOrAdd(assembly, asm => { - foreach (var hook in assemblyHooks) + var allHooks = new List<(int order, Func hook)>(); + + if (Sources.AfterAssemblyHooks.TryGetValue(asm, out var assemblyHooks)) { - var hookFunc = CreateAssemblyHookDelegate(hook); - allHooks.Add((hook.Order, hookFunc)); + foreach (var hook in assemblyHooks) + { + var hookFunc = CreateAssemblyHookDelegate(hook); + allHooks.Add((hook.Order, hookFunc)); + } } - } - var hooks = allHooks - .OrderBy(h => h.order) - .Select(h => h.hook) - .ToList(); + return allHooks + .OrderBy(h => h.order) + .Select(h => h.hook) + .ToList(); + }); return new ValueTask>>(hooks); } public ValueTask>> CollectBeforeTestSessionHooksAsync() { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - foreach (var hook in Sources.BeforeTestSessionHooks) - { - var hookFunc = CreateTestSessionHookDelegate(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - var hooks = allHooks - .OrderBy(h => h.order) - .ThenBy(h => h.registrationIndex) - .Select(h => h.hook) - .ToList(); - - return new ValueTask>>(hooks); + return new ValueTask>>(_beforeTestSessionHooks ?? []); } public ValueTask>> CollectAfterTestSessionHooksAsync() { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - foreach (var hook in Sources.AfterTestSessionHooks) - { - var hookFunc = CreateTestSessionHookDelegate(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - var hooks = allHooks - .OrderBy(h => h.order) - .ThenBy(h => h.registrationIndex) - .Select(h => h.hook) - .ToList(); - - return new ValueTask>>(hooks); + return new ValueTask>>(_afterTestSessionHooks ?? []); } public ValueTask>> CollectBeforeTestDiscoveryHooksAsync() { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - foreach (var hook in Sources.BeforeTestDiscoveryHooks) - { - var hookFunc = CreateBeforeTestDiscoveryHookDelegate(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - var hooks = allHooks - .OrderBy(h => h.order) - .ThenBy(h => h.registrationIndex) - .Select(h => h.hook) - .ToList(); - - return new ValueTask>>(hooks); + return new ValueTask>>(_beforeTestDiscoveryHooks ?? []); } public ValueTask>> CollectAfterTestDiscoveryHooksAsync() { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - foreach (var hook in Sources.AfterTestDiscoveryHooks) - { - var hookFunc = CreateTestDiscoveryHookDelegate(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - var hooks = allHooks - .OrderBy(h => h.order) - .ThenBy(h => h.registrationIndex) - .Select(h => h.hook) - .ToList(); - - return new ValueTask>>(hooks); + return new ValueTask>>(_afterTestDiscoveryHooks ?? []); } public ValueTask>> CollectBeforeEveryClassHooksAsync() { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - foreach (var hook in Sources.BeforeEveryClassHooks) - { - var hookFunc = CreateClassHookDelegate(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - var hooks = allHooks - .OrderBy(h => h.order) - .ThenBy(h => h.registrationIndex) - .Select(h => h.hook) - .ToList(); - - return new ValueTask>>(hooks); + return new ValueTask>>(_beforeEveryClassHooks ?? []); } public ValueTask>> CollectAfterEveryClassHooksAsync() { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - foreach (var hook in Sources.AfterEveryClassHooks) - { - var hookFunc = CreateClassHookDelegate(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - var hooks = allHooks - .OrderBy(h => h.order) - .ThenBy(h => h.registrationIndex) - .Select(h => h.hook) - .ToList(); - - return new ValueTask>>(hooks); + return new ValueTask>>(_afterEveryClassHooks ?? []); } public ValueTask>> CollectBeforeEveryAssemblyHooksAsync() { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - foreach (var hook in Sources.BeforeEveryAssemblyHooks) - { - var hookFunc = CreateAssemblyHookDelegate(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - var hooks = allHooks - .OrderBy(h => h.order) - .ThenBy(h => h.registrationIndex) - .Select(h => h.hook) - .ToList(); - - return new ValueTask>>(hooks); + return new ValueTask>>(_beforeEveryAssemblyHooks ?? []); } public ValueTask>> CollectAfterEveryAssemblyHooksAsync() { - var allHooks = new List<(int order, int registrationIndex, Func hook)>(); - - foreach (var hook in Sources.AfterEveryAssemblyHooks) - { - var hookFunc = CreateAssemblyHookDelegate(hook); - allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); - } - - var hooks = allHooks - .OrderBy(h => h.order) - .ThenBy(h => h.registrationIndex) - .Select(h => h.hook) - .ToList(); - - return new ValueTask>>(hooks); + return new ValueTask>>(_afterEveryAssemblyHooks ?? []); } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task> CreateInstanceHookDelegateAsync(InstanceHookMethod hook) { // Process hook registration event receivers @@ -599,9 +632,6 @@ private async Task> CreateInstanceHoo }; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif private async Task> CreateStaticHookDelegateAsync(StaticHookMethod hook) { // Process hook registration event receivers diff --git a/TUnit.Engine/Services/HookExecutor.cs b/TUnit.Engine/Services/HookExecutor.cs index a483651f3c..a30eb270ef 100644 --- a/TUnit.Engine/Services/HookExecutor.cs +++ b/TUnit.Engine/Services/HookExecutor.cs @@ -156,9 +156,6 @@ public async Task> ExecuteAfterClassHooksAsync( return exceptions; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Hook discovery uses reflection on methods and attributes")] - #endif public async Task ExecuteBeforeTestHooksAsync(AbstractExecutableTest test, CancellationToken cancellationToken) { var testClassType = test.Metadata.TestClassType; @@ -194,9 +191,6 @@ public async Task ExecuteBeforeTestHooksAsync(AbstractExecutableTest test, Cance } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Hook discovery uses reflection on methods and attributes")] - #endif public async Task ExecuteAfterTestHooksAsync(AbstractExecutableTest test, CancellationToken cancellationToken) { var testClassType = test.Metadata.TestClassType; diff --git a/TUnit.Engine/Services/ObjectRegistrationService.cs b/TUnit.Engine/Services/ObjectRegistrationService.cs index 9adb864090..fc4e0c7408 100644 --- a/TUnit.Engine/Services/ObjectRegistrationService.cs +++ b/TUnit.Engine/Services/ObjectRegistrationService.cs @@ -28,9 +28,6 @@ public ObjectRegistrationService( /// Shared object bag for the test context. Must not be null. /// Method metadata for the test. Can be null. /// Test context events for tracking. Must not be null and must be unique per test permutation. - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public async Task RegisterObjectAsync( object instance, Dictionary objectBag, @@ -66,9 +63,6 @@ await _propertyInjectionService.InjectPropertiesIntoObjectAsync( /// Registers multiple objects (e.g., constructor/method arguments) in parallel. /// Used during test registration to prepare arguments without executing expensive operations. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public async Task RegisterArgumentsAsync( object?[] arguments, Dictionary objectBag, diff --git a/TUnit.Engine/Services/PropertyDataResolver.cs b/TUnit.Engine/Services/PropertyDataResolver.cs index c10c908f54..17bbf8a38f 100644 --- a/TUnit.Engine/Services/PropertyDataResolver.cs +++ b/TUnit.Engine/Services/PropertyDataResolver.cs @@ -35,10 +35,10 @@ internal static class PropertyDataResolver { var args = await factory(); var value = ResolveValueFromArgs(context.PropertyType, args); - + // Resolve any Func wrappers value = await ResolveDelegateValue(value); - + // Initialize the resolved value if needed if (value != null) { @@ -62,7 +62,7 @@ await objectRegistrationService.RegisterObjectAsync( context.Events); } // Note: IAsyncInitializer will be called during execution phase by ObjectInitializationService - + return value; } } @@ -74,13 +74,10 @@ await objectRegistrationService.RegisterObjectAsync( /// Gets an initialized data source from the context. /// Ensures the data source is fully initialized (including property injection) before returning it. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private static async Task GetInitializedDataSourceAsync(PropertyInitializationContext context, DataSourceInitializer dataSourceInitializer) { IDataSourceAttribute? dataSource = null; - + if (context.DataSource != null) { dataSource = context.DataSource; @@ -183,4 +180,4 @@ private static DataGeneratorMetadata CreateDataGeneratorMetadata( { return await PropertyValueProcessor.ResolveTestDataValueAsync(typeof(object), value); } -} \ No newline at end of file +} diff --git a/TUnit.Engine/Services/PropertyInitializationOrchestrator.cs b/TUnit.Engine/Services/PropertyInitializationOrchestrator.cs index 15a7192b39..c8e0f20b4a 100644 --- a/TUnit.Engine/Services/PropertyInitializationOrchestrator.cs +++ b/TUnit.Engine/Services/PropertyInitializationOrchestrator.cs @@ -88,9 +88,7 @@ public async Task InitializePropertiesAsync( /// /// Handles the complete initialization flow for an object with properties. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Reflection-based property initialization uses PropertyInfo")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Specific source gen path")] public async Task InitializeObjectWithPropertiesAsync( object instance, PropertyInjectionPlan plan, diff --git a/TUnit.Engine/Services/PropertyInjectionService.cs b/TUnit.Engine/Services/PropertyInjectionService.cs index f9b739d758..e4d7106af6 100644 --- a/TUnit.Engine/Services/PropertyInjectionService.cs +++ b/TUnit.Engine/Services/PropertyInjectionService.cs @@ -28,9 +28,6 @@ public void Initialize(ObjectRegistrationService objectRegistrationService) /// This ensures properties are only initialized when the test is about to run. /// Arguments are processed in parallel for better performance. /// - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public async Task InjectPropertiesIntoArgumentsAsync(object?[] arguments, Dictionary objectBag, MethodMetadata methodMetadata, TestContextEvents events) { if (arguments.Length == 0) @@ -66,9 +63,6 @@ public async Task InjectPropertiesIntoArgumentsAsync(object?[] arguments, Dictio /// Shared object bag for the test context. Must not be null. /// Method metadata for the test. Can be null. /// Test context events for tracking. Must not be null and must be unique per test permutation. - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public Task InjectPropertiesIntoObjectAsync(object instance, Dictionary objectBag, MethodMetadata? methodMetadata, TestContextEvents events) { if (objectBag == null) @@ -90,9 +84,6 @@ public Task InjectPropertiesIntoObjectAsync(object instance, Dictionary objectBag, MethodMetadata? methodMetadata, TestContextEvents events, ConcurrentDictionary visitedObjects) { if (instance == null) diff --git a/TUnit.Engine/Services/TestArgumentRegistrationService.cs b/TUnit.Engine/Services/TestArgumentRegistrationService.cs index 5b50965fb0..5698233ef3 100644 --- a/TUnit.Engine/Services/TestArgumentRegistrationService.cs +++ b/TUnit.Engine/Services/TestArgumentRegistrationService.cs @@ -34,9 +34,6 @@ public TestArgumentRegistrationService(ObjectRegistrationService objectRegistrat /// Called when a test is registered. This is the correct time to register constructor and method arguments /// for proper reference counting and disposal tracking. /// - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public async ValueTask OnTestRegistered(TestRegisteredContext context) { var testContext = context.TestContext; @@ -68,9 +65,6 @@ await _objectRegistrationService.RegisterArgumentsAsync( /// This ensures proper reference counting for all property-injected instances during discovery. /// Exceptions during data generation will be caught and associated with the test for reporting. /// - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private async ValueTask RegisterPropertiesAsync(TestContext testContext) { try diff --git a/TUnit.Engine/Services/TestDependencyResolver.cs b/TUnit.Engine/Services/TestDependencyResolver.cs index 42c97a1e2c..803fb4d4ba 100644 --- a/TUnit.Engine/Services/TestDependencyResolver.cs +++ b/TUnit.Engine/Services/TestDependencyResolver.cs @@ -10,7 +10,7 @@ internal sealed class TestDependencyResolver private readonly Dictionary> _testsByType = new(); private readonly Dictionary> _testsByMethodName = new(); private readonly Dictionary<(Type ClassType, string MethodName), AbstractExecutableTest> _testLookupCache = new(); - private readonly List _testsWithPendingDependencies = + private readonly HashSet _testsWithPendingDependencies = [ ]; private readonly HashSet _testsBeingResolved = @@ -79,13 +79,10 @@ private bool ResolveDependenciesForTest(AbstractExecutableTest test) foreach (var dependencyMetadata in test.Metadata.Dependencies) { var matchingTests = FindMatchingTests(dependencyMetadata, test); - + if (matchingTests.Count == 0) { - if (!_testsWithPendingDependencies.Contains(test)) - { - _testsWithPendingDependencies.Add(test); - } + _testsWithPendingDependencies.Add(test); allResolved = false; } else diff --git a/TUnit.Engine/Services/TestFilterService.cs b/TUnit.Engine/Services/TestFilterService.cs index 932eeee055..7854b9f9c1 100644 --- a/TUnit.Engine/Services/TestFilterService.cs +++ b/TUnit.Engine/Services/TestFilterService.cs @@ -25,7 +25,7 @@ public IReadOnlyCollection FilterTests(ITestExecutionFil var filteredTests = new List(); var filteredExplicitTests = new List(); - + foreach (var test in testNodes) { if (MatchesTest(testExecutionFilter, test)) @@ -56,9 +56,6 @@ public IReadOnlyCollection FilterTests(ITestExecutionFil return filteredTests; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif private async Task RegisterTest(AbstractExecutableTest test) { var discoveredTest = new DiscoveredTest @@ -92,9 +89,6 @@ private async Task RegisterTest(AbstractExecutableTest test) } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public async Task RegisterTestsAsync(IEnumerable tests) { foreach (var test in tests) @@ -192,7 +186,7 @@ private bool IsExplicitTest(AbstractExecutableTest test) private IReadOnlyCollection FilterOutExplicitTests(IReadOnlyCollection testNodes) { var filteredTests = new List(); - + foreach (var test in testNodes) { if (!IsExplicitTest(test)) @@ -204,7 +198,7 @@ private IReadOnlyCollection FilterOutExplicitTests(IRead logger.LogTrace($"Test {test.TestId} is explicit and no filter was specified, skipping."); } } - + return filteredTests; } } diff --git a/TUnit.Engine/Services/TestGenericTypeResolver.cs b/TUnit.Engine/Services/TestGenericTypeResolver.cs index 79fff27791..aa2e886466 100644 --- a/TUnit.Engine/Services/TestGenericTypeResolver.cs +++ b/TUnit.Engine/Services/TestGenericTypeResolver.cs @@ -3,6 +3,7 @@ using TUnit.Core; using TUnit.Core.Exceptions; using TUnit.Engine.Building; +using TUnit.Engine.Helpers; namespace TUnit.Engine.Services; @@ -522,8 +523,7 @@ private static bool TryInferTypeMapping( // Handle case where parameter is a generic interface and argument implements it if (parameterType is { IsGenericType: true, IsInterface: true }) { - // Check if argument type implements the parameter interface - var implementedInterfaces = argumentType.GetInterfaces(); + var implementedInterfaces = AssemblyReferenceCache.GetInterfaces(argumentType); foreach (var implementedInterface in implementedInterfaces) { if (implementedInterface.IsGenericType) diff --git a/TUnit.Engine/Services/TestGroupingService.cs b/TUnit.Engine/Services/TestGroupingService.cs index c76e022491..bd1473130d 100644 --- a/TUnit.Engine/Services/TestGroupingService.cs +++ b/TUnit.Engine/Services/TestGroupingService.cs @@ -33,7 +33,8 @@ private struct TestSortKey public async ValueTask GroupTestsByConstraintsAsync(IEnumerable tests) { - var testsWithKeys = new List<(AbstractExecutableTest Test, TestSortKey Key)>(); + var testCount = tests is ICollection collection ? collection.Count : 0; + var testsWithKeys = new List<(AbstractExecutableTest Test, TestSortKey Key)>(testCount > 0 ? testCount : 16); foreach (var test in tests) { NotInParallelConstraint? notInParallelConstraint = null; @@ -67,11 +68,12 @@ public async ValueTask GroupTestsByConstraintsAsync(IEnumerable(); - var keyedNotInParallelList = new List<(AbstractExecutableTest Test, string ClassName, IReadOnlyList ConstraintKeys, TestPriority Priority)>(); - var parallelTests = new List(); - var parallelGroups = new Dictionary>>(capacity: 16); - var constrainedParallelGroups = new Dictionary Unconstrained, List<(AbstractExecutableTest, string, IReadOnlyList, TestPriority)> Keyed)>(capacity: 16); + var estimatedCount = testsWithKeys.Count; + var notInParallelList = new List<(AbstractExecutableTest Test, string ClassName, TestPriority Priority)>(estimatedCount / 4); + var keyedNotInParallelList = new List<(AbstractExecutableTest Test, string ClassName, IReadOnlyList ConstraintKeys, TestPriority Priority)>(estimatedCount / 8); + var parallelTests = new List(estimatedCount / 2); + var parallelGroups = new Dictionary>>(capacity: 8); + var constrainedParallelGroups = new Dictionary Unconstrained, List<(AbstractExecutableTest, string, IReadOnlyList, TestPriority)> Keyed)>(capacity: 8); foreach (var (test, sortKey) in testsWithKeys) { diff --git a/TUnit.Engine/Services/TestIdentifierService.cs b/TUnit.Engine/Services/TestIdentifierService.cs index 9c751ac7ca..84ab118e2f 100644 --- a/TUnit.Engine/Services/TestIdentifierService.cs +++ b/TUnit.Engine/Services/TestIdentifierService.cs @@ -116,12 +116,15 @@ private static string GetTypeNameWithGenerics(Type type) var sb = new StringBuilder(); var name = type.Name; - - // Remove the `n suffix from generic type names + var backtickIndex = name.IndexOf('`'); if (backtickIndex > 0) { +#if NET6_0_OR_GREATER + sb.Append(name.AsSpan(0, backtickIndex)); +#else sb.Append(name.Substring(0, backtickIndex)); +#endif } else { diff --git a/TUnit.Engine/TestDiscoveryService.cs b/TUnit.Engine/TestDiscoveryService.cs index 9d8c022f33..aee72fda4a 100644 --- a/TUnit.Engine/TestDiscoveryService.cs +++ b/TUnit.Engine/TestDiscoveryService.cs @@ -49,7 +49,6 @@ public TestDiscoveryService(TestExecutor testExecutor, TestBuilderPipeline testB } #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] #endif public async Task DiscoverTests(string testSessionId, ITestExecutionFilter? filter, CancellationToken cancellationToken, bool isForExecution) @@ -134,10 +133,6 @@ public async Task DiscoverTests(string testSessionId, ITest } /// Streams test discovery for parallel discovery and execution - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly scanning uses dynamic type discovery and reflection")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif private async IAsyncEnumerable DiscoverTestsStreamAsync( string testSessionId, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -168,7 +163,6 @@ private async IAsyncEnumerable DiscoverTestsStreamAsync( /// Simplified streaming test discovery without channels - matches source generation approach /// #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] #endif public async IAsyncEnumerable DiscoverTestsFullyStreamingAsync( diff --git a/TUnit.Engine/TestExecutor.cs b/TUnit.Engine/TestExecutor.cs index c43c4508ec..24b9521b8e 100644 --- a/TUnit.Engine/TestExecutor.cs +++ b/TUnit.Engine/TestExecutor.cs @@ -51,9 +51,6 @@ await _beforeHookTaskCache.GetOrCreateBeforeTestSessionTask(() => /// Creates a test executor delegate that wraps the provided executor with hook orchestration. /// Uses focused services that follow SRP to manage lifecycle and execution. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] - #endif public async Task ExecuteAsync(AbstractExecutableTest executableTest, CancellationToken cancellationToken) { diff --git a/TUnit.Engine/Utilities/ScopedAttributeFilter.cs b/TUnit.Engine/Utilities/ScopedAttributeFilter.cs index d546ad4bd5..48984d1373 100644 --- a/TUnit.Engine/Utilities/ScopedAttributeFilter.cs +++ b/TUnit.Engine/Utilities/ScopedAttributeFilter.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; +using Polyfills; using TUnit.Core; +using TUnit.Engine.Helpers; namespace TUnit.Engine.Utilities; @@ -14,10 +16,7 @@ internal static class ScopedAttributeFilter /// The type of objects to filter /// The collection of items to filter /// A filtered collection with only one instance per scoped attribute type -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")] -#endif - public static List FilterScopedAttributes(IEnumerable items) where T : class + public static List FilterScopedAttributes(IEnumerable items) where T : class { var result = new List(); var scopedAttributesByType = new Dictionary(); @@ -30,23 +29,9 @@ public static List FilterScopedAttributes(IEnumerable items) where T : continue; } - var itemType = item.GetType(); - - // Check if this implements IScopedAttribute - var scopedInterface = itemType.GetInterfaces() - .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IScopedAttribute<>)); - - if (scopedInterface != null) + if (item is IScopedAttribute scopedAttribute) { - // Get the generic type argument (e.g., RetryAttribute from IScopedAttribute) - var scopedType = scopedInterface.GetGenericArguments()[0]; - - - // Keep the first occurrence (which should be the most specific - method > class > assembly) - if (!scopedAttributesByType.ContainsKey(scopedType)) - { - scopedAttributesByType[scopedType] = item; - } + scopedAttributesByType.TryAdd(scopedAttribute.ScopeType, item); } else { @@ -55,41 +40,8 @@ public static List FilterScopedAttributes(IEnumerable items) where T : } } - // Second pass: add the selected scoped attributes to the result result.AddRange(scopedAttributesByType.Values); return result; } - - /// - /// Checks if a type implements IScopedAttribute - /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Interface checking uses Type.GetInterfaces")] -#endif - public static bool IsScopedAttribute(Type type) - { - return type.GetInterfaces() - .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IScopedAttribute<>)); - } - - /// - /// Gets the scoped attribute type from an object that implements IScopedAttribute - /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Scoped attribute type extraction uses Type.GetInterfaces")] -#endif - public static Type? GetScopedAttributeType(object? obj) - { - if (obj == null) - { - return null; - } - - var objType = obj.GetType(); - var scopedInterface = objType.GetInterfaces() - .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IScopedAttribute<>)); - - return scopedInterface?.GetGenericArguments()[0]; - } } diff --git a/TUnit.Playwright/WorkerAwareTest.cs b/TUnit.Playwright/WorkerAwareTest.cs index 9da66688bf..2291ce1616 100644 --- a/TUnit.Playwright/WorkerAwareTest.cs +++ b/TUnit.Playwright/WorkerAwareTest.cs @@ -69,9 +69,6 @@ protected bool TestOk(TestContext testContext) return testContext.Result?.State is TestState.Passed or TestState.Skipped; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Type comes from runtime objects that cannot be annotated")] - #endif public ValueTask OnTestRegistered(TestRegisteredContext context) { if (UseDefaultParallelLimiter) diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt index 3c6f67d108..b0597cb32e 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -74,7 +74,6 @@ namespace public object?[] Values { get; } [.(typeof(.ArgumentsAttribute.d__8))] public .<<.>> GetDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [(.Class | .Method | .Property, AllowMultiple=true)] @@ -85,7 +84,6 @@ namespace public string? Skip { get; set; } [.(typeof(.ArgumentsAttribute.d__6))] public override .<<.>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } public class Artifact @@ -533,7 +531,6 @@ namespace protected virtual void ConfigureThread(.Thread thread) { } protected override sealed . ExecuteAsync(<.> action) { } protected virtual void Initialize() { } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } public class DefaultExecutor : .GenericAbstractExecutor @@ -626,9 +623,10 @@ namespace public static .DiscoveryResult Empty { get; } } [(.Class | .Method, Inherited=false)] - public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute> + public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute { public DisplayNameAttribute(string displayName) { } + public ScopeType { get; } protected override string FormatDisplayName(.DiscoveredTestContext context) { } } [(.Assembly | .Class | .Method, Inherited=false)] @@ -712,11 +710,12 @@ namespace public required string TestId { get; init; } } [(.Assembly | .Class | .Method)] - public class ExecutionPriorityAttribute : .SingleTUnitAttribute, .IScopedAttribute, .IScopedAttribute<.ExecutionPriorityAttribute>, ., . + public class ExecutionPriorityAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . { public ExecutionPriorityAttribute(. priority = 2) { } public int Order { get; } public . Priority { get; } + public ScopeType { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class ExpandedTest @@ -883,9 +882,10 @@ namespace .<.AbstractDynamicTest> CollectDynamicTests(string sessionId); } public interface IRequiresImmediateInitialization { } - public interface IScopedAttribute { } - public interface IScopedAttribute : .IScopedAttribute - where T : { } + public interface IScopedAttribute + { + ScopeType { get; } + } public interface ITestDefinition { } public interface ITypedDataSourceAttribute : .IDataSourceAttribute { @@ -1005,13 +1005,14 @@ namespace protected override bool PrintMembers(.StringBuilder stringBuilder) { } } [(.Assembly | .Class | .Method)] - public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, .IScopedAttribute<.NotInParallelAttribute>, ., . + public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . { public NotInParallelAttribute() { } public NotInParallelAttribute(string constraintKey) { } public NotInParallelAttribute(string[] constraintKeys) { } public string[] ConstraintKeys { get; } public int Order { get; init; } + public ScopeType { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class NotInParallelConstraint : <.NotInParallelConstraint>, . @@ -1051,7 +1052,6 @@ namespace { public ParallelLimiterAttribute() { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [.DebuggerDisplay("{Type} {Name}")] @@ -1139,9 +1139,10 @@ namespace public static void Register( type, ..IPropertySource source) { } } [(.Assembly | .Class | .Method)] - public sealed class RepeatAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.RepeatAttribute> + public sealed class RepeatAttribute : .TUnitAttribute, .IScopedAttribute { public RepeatAttribute(int times) { } + public ScopeType { get; } public int Times { get; } } public sealed class ResolvedDependency @@ -1152,10 +1153,11 @@ namespace public required .AbstractExecutableTest Test { get; init; } } [(.Assembly | .Class | .Method)] - public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.RetryAttribute>, ., . + public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, ., . { public RetryAttribute(int times) { } public int Order { get; } + public ScopeType { get; } public int Times { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } public virtual . ShouldRetry(.TestContext context, exception, int currentRetryCount) { } @@ -1199,7 +1201,6 @@ namespace public SkipAttribute(string reason) { } public int Order { get; } public string Reason { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } public virtual . ShouldSkip(.TestRegisteredContext context) { } } @@ -1543,10 +1544,11 @@ namespace public static bool IsTransient(this .TestState state) { } } [(.Assembly | .Class | .Method)] - public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., ., . + public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, ., ., . { public TimeoutAttribute(int timeoutInMilliseconds) { } public int Order { get; } + public ScopeType { get; } public Timeout { get; } public . OnHookRegistered(.HookRegisteredContext context) { } public . OnTestDiscovered(.DiscoveredTestContext context) { } @@ -1855,7 +1857,6 @@ namespace .Executors public CultureAttribute(.CultureInfo cultureInfo) { } public CultureAttribute(string cultureName) { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } public class HookExecutorAttribute : .TUnitAttribute @@ -1880,7 +1881,6 @@ namespace .Executors { public STAThreadExecutorAttribute() { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [(.Assembly | .Class | .Method)] @@ -1888,7 +1888,6 @@ namespace .Executors { public TestExecutorAttribute([.(..PublicConstructors)] type) { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [(.Assembly | .Class | .Method)] @@ -1897,7 +1896,6 @@ namespace .Executors { public TestExecutorAttribute() { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } } @@ -2340,7 +2338,6 @@ namespace .Interfaces } public interface ITestRegisteredEventReceiver : . { - [.("Type comes from runtime objects that cannot be annotated")] . OnTestRegistered(.TestRegisteredContext context); } public interface ITestRegistry diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 25262b7eaa..73c7693290 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -74,7 +74,6 @@ namespace public object?[] Values { get; } [.(typeof(.ArgumentsAttribute.d__8))] public .<<.>> GetDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [(.Class | .Method | .Property, AllowMultiple=true)] @@ -85,7 +84,6 @@ namespace public string? Skip { get; set; } [.(typeof(.ArgumentsAttribute.d__6))] public override .<<.>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } public class Artifact @@ -533,7 +531,6 @@ namespace protected virtual void ConfigureThread(.Thread thread) { } protected override sealed . ExecuteAsync(<.> action) { } protected virtual void Initialize() { } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } public class DefaultExecutor : .GenericAbstractExecutor @@ -626,9 +623,10 @@ namespace public static .DiscoveryResult Empty { get; } } [(.Class | .Method, Inherited=false)] - public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute> + public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute { public DisplayNameAttribute(string displayName) { } + public ScopeType { get; } protected override string FormatDisplayName(.DiscoveredTestContext context) { } } [(.Assembly | .Class | .Method, Inherited=false)] @@ -712,11 +710,12 @@ namespace public required string TestId { get; init; } } [(.Assembly | .Class | .Method)] - public class ExecutionPriorityAttribute : .SingleTUnitAttribute, .IScopedAttribute, .IScopedAttribute<.ExecutionPriorityAttribute>, ., . + public class ExecutionPriorityAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . { public ExecutionPriorityAttribute(. priority = 2) { } public int Order { get; } public . Priority { get; } + public ScopeType { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class ExpandedTest @@ -883,9 +882,10 @@ namespace .<.AbstractDynamicTest> CollectDynamicTests(string sessionId); } public interface IRequiresImmediateInitialization { } - public interface IScopedAttribute { } - public interface IScopedAttribute : .IScopedAttribute - where T : { } + public interface IScopedAttribute + { + ScopeType { get; } + } public interface ITestDefinition { } public interface ITypedDataSourceAttribute : .IDataSourceAttribute { @@ -1005,13 +1005,14 @@ namespace protected override bool PrintMembers(.StringBuilder stringBuilder) { } } [(.Assembly | .Class | .Method)] - public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, .IScopedAttribute<.NotInParallelAttribute>, ., . + public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . { public NotInParallelAttribute() { } public NotInParallelAttribute(string constraintKey) { } public NotInParallelAttribute(string[] constraintKeys) { } public string[] ConstraintKeys { get; } public int Order { get; init; } + public ScopeType { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class NotInParallelConstraint : <.NotInParallelConstraint>, . @@ -1051,7 +1052,6 @@ namespace { public ParallelLimiterAttribute() { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [.DebuggerDisplay("{Type} {Name}")] @@ -1139,9 +1139,10 @@ namespace public static void Register( type, ..IPropertySource source) { } } [(.Assembly | .Class | .Method)] - public sealed class RepeatAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.RepeatAttribute> + public sealed class RepeatAttribute : .TUnitAttribute, .IScopedAttribute { public RepeatAttribute(int times) { } + public ScopeType { get; } public int Times { get; } } public sealed class ResolvedDependency @@ -1152,10 +1153,11 @@ namespace public required .AbstractExecutableTest Test { get; init; } } [(.Assembly | .Class | .Method)] - public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.RetryAttribute>, ., . + public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, ., . { public RetryAttribute(int times) { } public int Order { get; } + public ScopeType { get; } public int Times { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } public virtual . ShouldRetry(.TestContext context, exception, int currentRetryCount) { } @@ -1199,7 +1201,6 @@ namespace public SkipAttribute(string reason) { } public int Order { get; } public string Reason { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } public virtual . ShouldSkip(.TestRegisteredContext context) { } } @@ -1543,10 +1544,11 @@ namespace public static bool IsTransient(this .TestState state) { } } [(.Assembly | .Class | .Method)] - public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., ., . + public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, ., ., . { public TimeoutAttribute(int timeoutInMilliseconds) { } public int Order { get; } + public ScopeType { get; } public Timeout { get; } public . OnHookRegistered(.HookRegisteredContext context) { } public . OnTestDiscovered(.DiscoveredTestContext context) { } @@ -1855,7 +1857,6 @@ namespace .Executors public CultureAttribute(.CultureInfo cultureInfo) { } public CultureAttribute(string cultureName) { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } public class HookExecutorAttribute : .TUnitAttribute @@ -1880,7 +1881,6 @@ namespace .Executors { public STAThreadExecutorAttribute() { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [(.Assembly | .Class | .Method)] @@ -1888,7 +1888,6 @@ namespace .Executors { public TestExecutorAttribute([.(..PublicConstructors)] type) { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [(.Assembly | .Class | .Method)] @@ -1897,7 +1896,6 @@ namespace .Executors { public TestExecutorAttribute() { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } } @@ -2340,7 +2338,6 @@ namespace .Interfaces } public interface ITestRegisteredEventReceiver : . { - [.("Type comes from runtime objects that cannot be annotated")] . OnTestRegistered(.TestRegisteredContext context); } public interface ITestRegistry diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 8a1dd3cd91..c2d3b83ce6 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -74,7 +74,6 @@ namespace public object?[] Values { get; } [.(typeof(.ArgumentsAttribute.d__8))] public .<<.>> GetDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [(.Class | .Method | .Property, AllowMultiple=true)] @@ -85,7 +84,6 @@ namespace public string? Skip { get; set; } [.(typeof(.ArgumentsAttribute.d__6))] public override .<<.>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } public class Artifact @@ -533,7 +531,6 @@ namespace protected virtual void ConfigureThread(.Thread thread) { } protected override sealed . ExecuteAsync(<.> action) { } protected virtual void Initialize() { } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } public class DefaultExecutor : .GenericAbstractExecutor @@ -626,9 +623,10 @@ namespace public static .DiscoveryResult Empty { get; } } [(.Class | .Method, Inherited=false)] - public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute> + public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute { public DisplayNameAttribute(string displayName) { } + public ScopeType { get; } protected override string FormatDisplayName(.DiscoveredTestContext context) { } } [(.Assembly | .Class | .Method, Inherited=false)] @@ -712,11 +710,12 @@ namespace public required string TestId { get; init; } } [(.Assembly | .Class | .Method)] - public class ExecutionPriorityAttribute : .SingleTUnitAttribute, .IScopedAttribute, .IScopedAttribute<.ExecutionPriorityAttribute>, ., . + public class ExecutionPriorityAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . { public ExecutionPriorityAttribute(. priority = 2) { } public int Order { get; } public . Priority { get; } + public ScopeType { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class ExpandedTest @@ -883,9 +882,10 @@ namespace .<.AbstractDynamicTest> CollectDynamicTests(string sessionId); } public interface IRequiresImmediateInitialization { } - public interface IScopedAttribute { } - public interface IScopedAttribute : .IScopedAttribute - where T : { } + public interface IScopedAttribute + { + ScopeType { get; } + } public interface ITestDefinition { } public interface ITypedDataSourceAttribute : .IDataSourceAttribute { @@ -1005,13 +1005,14 @@ namespace protected override bool PrintMembers(.StringBuilder stringBuilder) { } } [(.Assembly | .Class | .Method)] - public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, .IScopedAttribute<.NotInParallelAttribute>, ., . + public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . { public NotInParallelAttribute() { } public NotInParallelAttribute(string constraintKey) { } public NotInParallelAttribute(string[] constraintKeys) { } public string[] ConstraintKeys { get; } public int Order { get; init; } + public ScopeType { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class NotInParallelConstraint : <.NotInParallelConstraint>, . @@ -1051,7 +1052,6 @@ namespace { public ParallelLimiterAttribute() { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [.DebuggerDisplay("{Type} {Name}")] @@ -1139,9 +1139,10 @@ namespace public static void Register( type, ..IPropertySource source) { } } [(.Assembly | .Class | .Method)] - public sealed class RepeatAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.RepeatAttribute> + public sealed class RepeatAttribute : .TUnitAttribute, .IScopedAttribute { public RepeatAttribute(int times) { } + public ScopeType { get; } public int Times { get; } } public sealed class ResolvedDependency @@ -1152,10 +1153,11 @@ namespace public required .AbstractExecutableTest Test { get; init; } } [(.Assembly | .Class | .Method)] - public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.RetryAttribute>, ., . + public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, ., . { public RetryAttribute(int times) { } public int Order { get; } + public ScopeType { get; } public int Times { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } public virtual . ShouldRetry(.TestContext context, exception, int currentRetryCount) { } @@ -1199,7 +1201,6 @@ namespace public SkipAttribute(string reason) { } public int Order { get; } public string Reason { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } public virtual . ShouldSkip(.TestRegisteredContext context) { } } @@ -1543,10 +1544,11 @@ namespace public static bool IsTransient(this .TestState state) { } } [(.Assembly | .Class | .Method)] - public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., ., . + public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, ., ., . { public TimeoutAttribute(int timeoutInMilliseconds) { } public int Order { get; } + public ScopeType { get; } public Timeout { get; } public . OnHookRegistered(.HookRegisteredContext context) { } public . OnTestDiscovered(.DiscoveredTestContext context) { } @@ -1855,7 +1857,6 @@ namespace .Executors public CultureAttribute(.CultureInfo cultureInfo) { } public CultureAttribute(string cultureName) { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } public class HookExecutorAttribute : .TUnitAttribute @@ -1880,7 +1881,6 @@ namespace .Executors { public STAThreadExecutorAttribute() { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [(.Assembly | .Class | .Method)] @@ -1888,7 +1888,6 @@ namespace .Executors { public TestExecutorAttribute([.(..PublicConstructors)] type) { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } [(.Assembly | .Class | .Method)] @@ -1897,7 +1896,6 @@ namespace .Executors { public TestExecutorAttribute() { } public int Order { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } } } @@ -2340,7 +2338,6 @@ namespace .Interfaces } public interface ITestRegisteredEventReceiver : . { - [.("Type comes from runtime objects that cannot be annotated")] . OnTestRegistered(.TestRegisteredContext context); } public interface ITestRegistry diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index 5cc7d73e45..f6ca8632a8 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -566,9 +566,10 @@ namespace public static .DiscoveryResult Empty { get; } } [(.Class | .Method, Inherited=false)] - public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute> + public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute { public DisplayNameAttribute(string displayName) { } + public ScopeType { get; } protected override string FormatDisplayName(.DiscoveredTestContext context) { } } [(.Assembly | .Class | .Method, Inherited=false)] @@ -648,11 +649,12 @@ namespace public required string TestId { get; init; } } [(.Assembly | .Class | .Method)] - public class ExecutionPriorityAttribute : .SingleTUnitAttribute, .IScopedAttribute, .IScopedAttribute<.ExecutionPriorityAttribute>, ., . + public class ExecutionPriorityAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . { public ExecutionPriorityAttribute(. priority = 2) { } public int Order { get; } public . Priority { get; } + public ScopeType { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class ExpandedTest @@ -819,9 +821,10 @@ namespace .<.AbstractDynamicTest> CollectDynamicTests(string sessionId); } public interface IRequiresImmediateInitialization { } - public interface IScopedAttribute { } - public interface IScopedAttribute : .IScopedAttribute - where T : { } + public interface IScopedAttribute + { + ScopeType { get; } + } public interface ITestDefinition { } public interface ITypedDataSourceAttribute : .IDataSourceAttribute { @@ -922,13 +925,14 @@ namespace protected override bool PrintMembers(.StringBuilder stringBuilder) { } } [(.Assembly | .Class | .Method)] - public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, .IScopedAttribute<.NotInParallelAttribute>, ., . + public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . { public NotInParallelAttribute() { } public NotInParallelAttribute(string constraintKey) { } public NotInParallelAttribute(string[] constraintKeys) { } public string[] ConstraintKeys { get; } public int Order { get; init; } + public ScopeType { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class NotInParallelConstraint : <.NotInParallelConstraint>, . @@ -1052,9 +1056,10 @@ namespace public static void Register( type, ..IPropertySource source) { } } [(.Assembly | .Class | .Method)] - public sealed class RepeatAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.RepeatAttribute> + public sealed class RepeatAttribute : .TUnitAttribute, .IScopedAttribute { public RepeatAttribute(int times) { } + public ScopeType { get; } public int Times { get; } } public sealed class ResolvedDependency @@ -1065,10 +1070,11 @@ namespace public required .AbstractExecutableTest Test { get; init; } } [(.Assembly | .Class | .Method)] - public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.RetryAttribute>, ., . + public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, ., . { public RetryAttribute(int times) { } public int Order { get; } + public ScopeType { get; } public int Times { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } public virtual . ShouldRetry(.TestContext context, exception, int currentRetryCount) { } @@ -1448,10 +1454,11 @@ namespace public static bool IsTransient(this .TestState state) { } } [(.Assembly | .Class | .Method)] - public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., ., . + public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, ., ., . { public TimeoutAttribute(int timeoutInMilliseconds) { } public int Order { get; } + public ScopeType { get; } public Timeout { get; } public . OnHookRegistered(.HookRegisteredContext context) { } public . OnTestDiscovered(.DiscoveredTestContext context) { } diff --git a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet10_0.verified.txt index bbc3fac63d..31463be1ce 100644 --- a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -75,7 +75,6 @@ namespace public WorkerAwareTest() { } public virtual bool UseDefaultParallelLimiter { get; } public int WorkerIndex { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } public . RegisterService(string name, <.> factory) where T : class, .IWorkerService { } diff --git a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 359fa1d581..9b79081d88 100644 --- a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -75,7 +75,6 @@ namespace public WorkerAwareTest() { } public virtual bool UseDefaultParallelLimiter { get; } public int WorkerIndex { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } public . RegisterService(string name, <.> factory) where T : class, .IWorkerService { } diff --git a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 11aa4b50d2..6b1e2b7222 100644 --- a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -75,7 +75,6 @@ namespace public WorkerAwareTest() { } public virtual bool UseDefaultParallelLimiter { get; } public int WorkerIndex { get; } - [.("Type comes from runtime objects that cannot be annotated")] public . OnTestRegistered(.TestRegisteredContext context) { } public . RegisterService(string name, <.> factory) where T : class, .IWorkerService { } diff --git a/TUnit.TestProject/ScopedEventReceiverTests.cs b/TUnit.TestProject/ScopedEventReceiverTests.cs index f1f0a92c1c..cd514b3246 100644 --- a/TUnit.TestProject/ScopedEventReceiverTests.cs +++ b/TUnit.TestProject/ScopedEventReceiverTests.cs @@ -10,7 +10,7 @@ public class ScopedEventReceiverTests internal static readonly object _lock = new(); internal static readonly Dictionary> _testStartEvents = new(); internal static readonly Dictionary> _testEndEvents = new(); - + internal static void RecordStartEvent(string testName, string source) { lock (_lock) @@ -22,7 +22,7 @@ internal static void RecordStartEvent(string testName, string source) _testStartEvents[testName].Add(source); } } - + internal static void RecordEndEvent(string testName, string source) { lock (_lock) @@ -42,18 +42,18 @@ public async Task TestWithMethodLevelEventReceivers() { // Wait a bit to ensure events have been recorded await Task.Delay(100); - + // Method-level attributes should override assembly level List startEvents; List endEvents; - + lock (_lock) { startEvents = _testStartEvents.GetValueOrDefault(nameof(TestWithMethodLevelEventReceivers)) ?? new List(); endEvents = _testEndEvents.GetValueOrDefault(nameof(TestWithMethodLevelEventReceivers)) ?? new List(); } - - + + await Assert.That(startEvents).Contains("method"); await Assert.That(startEvents).DoesNotContain("assembly"); // EndEvents might not be populated yet as they fire after the test @@ -64,18 +64,18 @@ public async Task TestWithClassLevelEventReceivers() { // Wait a bit to ensure events have been recorded await Task.Delay(100); - + // Should use assembly-level since no class-level overrides exist List startEvents; List endEvents; - + lock (_lock) { startEvents = _testStartEvents.GetValueOrDefault(nameof(TestWithClassLevelEventReceivers)) ?? new List(); endEvents = _testEndEvents.GetValueOrDefault(nameof(TestWithClassLevelEventReceivers)) ?? new List(); } - - + + await Assert.That(startEvents).Contains("assembly"); await Assert.That(startEvents).DoesNotContain("method"); } @@ -90,12 +90,12 @@ public async Task TestWithOnlyClassLevelEventReceivers() { // Wait a bit to ensure events have been recorded await Task.Delay(100); - + // Should use class-level since it overrides assembly level var methodName = nameof(TestWithOnlyClassLevelEventReceivers); var startEvents = new List(); var endEvents = new List(); - + lock (ScopedEventReceiverTests._lock) { if (ScopedEventReceiverTests._testStartEvents.ContainsKey(methodName)) @@ -107,8 +107,8 @@ public async Task TestWithOnlyClassLevelEventReceivers() endEvents = ScopedEventReceiverTests._testEndEvents[methodName]; } } - - + + // Assert outside of lock await Assert.That(startEvents).Contains("class"); await Assert.That(startEvents).DoesNotContain("assembly"); @@ -116,7 +116,7 @@ public async Task TestWithOnlyClassLevelEventReceivers() } // Custom event receiver attributes that implement IScopedAttribute -public class TestStartLoggerAttribute : Attribute, ITestStartEventReceiver, IScopedAttribute +public class TestStartLoggerAttribute : Attribute, ITestStartEventReceiver, IScopedAttribute { private readonly string _source; @@ -132,9 +132,11 @@ public ValueTask OnTestStart(TestContext context) ScopedEventReceiverTests.RecordStartEvent(context.TestDetails.MethodName, _source); return default; } + + public Type ScopeType => typeof(TestStartLoggerAttribute); } -public class TestEndLoggerAttribute : Attribute, ITestEndEventReceiver, IScopedAttribute +public class TestEndLoggerAttribute : Attribute, ITestEndEventReceiver, IScopedAttribute { private readonly string _source; @@ -150,4 +152,6 @@ public ValueTask OnTestEnd(TestContext context) ScopedEventReceiverTests.RecordEndEvent(context.TestDetails.MethodName, _source); return default; } -} \ No newline at end of file + + public Type ScopeType => typeof(TestEndLoggerAttribute); +} diff --git a/tools/speed-comparison/Tests.Benchmark/RuntimeBenchmarks.cs b/tools/speed-comparison/Tests.Benchmark/RuntimeBenchmarks.cs index cb10a9bbd4..b56c24af26 100644 --- a/tools/speed-comparison/Tests.Benchmark/RuntimeBenchmarks.cs +++ b/tools/speed-comparison/Tests.Benchmark/RuntimeBenchmarks.cs @@ -19,6 +19,10 @@ public async Task TUnit_AOT() await Cli.Wrap(Path.Combine(aotPath, exeName)) .WithArguments(["--treenode-filter", $"/*/*/{ClassName}/*"]) + .WithEnvironmentVariables(new Dictionary + { + ["TUNIT_DISABLE_GITHUB_REPORTER"] = "true" + }) .WithStandardOutputPipe(PipeTarget.ToStream(OutputStream)) .ExecuteBufferedAsync(); } @@ -31,6 +35,10 @@ public async Task TUnit() await Cli.Wrap(Path.Combine(binPath, exeName)) .WithArguments(["--treenode-filter", $"/*/*/{ClassName}/*"]) + .WithEnvironmentVariables(new Dictionary + { + ["TUNIT_DISABLE_GITHUB_REPORTER"] = "true" + }) .WithStandardOutputPipe(PipeTarget.ToStream(OutputStream)) .ExecuteBufferedAsync(); }