diff --git a/TUnit.Core/SharedDataSources.cs b/TUnit.Core/SharedDataSources.cs new file mode 100644 index 0000000000..27ec9236da --- /dev/null +++ b/TUnit.Core/SharedDataSources.cs @@ -0,0 +1,216 @@ +using System.Diagnostics.CodeAnalysis; +using TUnit.Core.Helpers; + +namespace TUnit.Core; + +/// +/// Provides shared instance management for custom data sources. +/// Use this class to implement custom data source attributes that need the same +/// sharing behavior as . +/// +public static class SharedDataSources +{ + /// + /// Gets or creates a shared instance based on the specified sharing type, using the default constructor. + /// + /// The type of instance to get or create. Must have a parameterless constructor. + /// How the instance should be shared across tests. + /// The data generator metadata (used to extract the test class type). + /// The sharing key (required for ). + /// The shared or newly created instance. + /// + /// Thrown when is null/empty and is . + /// + /// Thrown when is not a valid value. + public static T GetOrCreate(SharedType sharedType, DataGeneratorMetadata dataGeneratorMetadata, string? key = null) where T : new() + { + return GetOrCreate(sharedType, dataGeneratorMetadata, key, static () => new T()); + } + + /// + /// Gets or creates a shared instance based on the specified sharing type, using the default constructor. + /// + /// The type of instance to get or create. Must have a parameterless constructor. + /// How the instance should be shared across tests. + /// The test class type (required for ). + /// The sharing key (required for ). + /// The shared or newly created instance. + /// + /// Thrown when is null and is or , + /// or when is null/empty and is . + /// + /// Thrown when is not a valid value. + public static T GetOrCreate(SharedType sharedType, Type? testClassType = null, string? key = null) where T : new() + { + return GetOrCreate(sharedType, testClassType, key, static () => new T()); + } + + /// + /// Gets or creates a shared instance based on the specified sharing type. + /// + /// The type of instance to get or create. + /// How the instance should be shared across tests. + /// The test class type (required for ). + /// The sharing key (required for ). + /// A factory function to create the instance if not already cached. + /// The shared or newly created instance. + /// + /// Thrown when is null and is or , + /// or when is null/empty and is . + /// + /// Thrown when is not a valid value. + public static T GetOrCreate(SharedType sharedType, Type? testClassType, string? key, Func factory) + { + _ = factory ?? throw new ArgumentNullException(nameof(factory)); + + return sharedType switch + { + SharedType.None => factory(), + SharedType.PerTestSession => (T)TestDataContainer.GetGlobalInstance(typeof(T), _ => factory()!)!, + SharedType.PerClass => GetForClass(testClassType, factory), + SharedType.Keyed => GetForKey(key, factory), + SharedType.PerAssembly => GetForAssembly(testClassType, factory), + _ => throw new ArgumentOutOfRangeException(nameof(sharedType), sharedType, "Invalid SharedType value.") + }; + } + + /// + /// Gets or creates a shared instance based on the specified sharing type. + /// + /// The type of instance to get or create. + /// How the instance should be shared across tests. + /// The data generator metadata (used to extract the test class type). + /// The sharing key (required for ). + /// A factory function to create the instance if not already cached. + /// The shared or newly created instance. + /// + /// Thrown when is null/empty and is . + /// + /// Thrown when is not a valid value. + public static T GetOrCreate(SharedType sharedType, DataGeneratorMetadata dataGeneratorMetadata, string? key, Func factory) + { + var testClassType = TestClassTypeHelper.GetTestClassType(dataGeneratorMetadata); + return GetOrCreate(sharedType, testClassType, key, factory); + } + + /// + /// Gets or creates a shared instance based on the specified sharing type. + /// + /// How the instance should be shared across tests. + /// The type of instance to get or create. + /// The data generator metadata (used to extract the test class type). + /// The sharing key (required for ). + /// A factory function to create the instance if not already cached. + /// The shared or newly created instance. + /// + /// Thrown when is null/empty and is . + /// + /// Thrown when is not a valid value. + public static object? GetOrCreate( + SharedType sharedType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type, + DataGeneratorMetadata dataGeneratorMetadata, + string? key, + Func factory) + { + var testClassType = TestClassTypeHelper.GetTestClassType(dataGeneratorMetadata); + return GetOrCreate(sharedType, type, testClassType, key, factory); + } + + /// + /// Gets or creates a shared instance based on the specified sharing type. + /// + /// How the instance should be shared across tests. + /// The type of instance to get or create. + /// The test class type (required for ). + /// The sharing key (required for ). + /// A factory function to create the instance if not already cached. + /// The shared or newly created instance. + /// + /// Thrown when is null and is or , + /// or when is null/empty and is . + /// + /// Thrown when is not a valid value. + public static object? GetOrCreate( + SharedType sharedType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type, + Type? testClassType, + string? key, + Func factory) + { + _ = type ?? throw new ArgumentNullException(nameof(type)); + _ = factory ?? throw new ArgumentNullException(nameof(factory)); + + return sharedType switch + { + SharedType.None => factory(), + SharedType.PerTestSession => TestDataContainer.GetGlobalInstance(type, _ => factory()!), + SharedType.PerClass => GetForClass(type, testClassType, factory), + SharedType.Keyed => GetForKey(type, key, factory), + SharedType.PerAssembly => GetForAssembly(type, testClassType, factory), + _ => throw new ArgumentOutOfRangeException(nameof(sharedType), sharedType, "Invalid SharedType value.") + }; + } + + private static T GetForClass(Type? testClassType, Func factory) + { + if (testClassType is null) + { + throw new ArgumentNullException(nameof(testClassType), "testClassType is required when SharedType is PerClass."); + } + + return (T)TestDataContainer.GetInstanceForClass(testClassType, typeof(T), _ => factory()!)!; + } + + private static object? GetForClass(Type type, Type? testClassType, Func factory) + { + if (testClassType is null) + { + throw new ArgumentNullException(nameof(testClassType), "testClassType is required when SharedType is PerClass."); + } + + return TestDataContainer.GetInstanceForClass(testClassType, type, _ => factory()!); + } + + private static T GetForKey(string? key, Func factory) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentNullException(nameof(key), "key is required when SharedType is Keyed."); + } + + return (T)TestDataContainer.GetInstanceForKey(key!, typeof(T), _ => factory()!)!; + } + + private static object? GetForKey(Type type, string? key, Func factory) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentNullException(nameof(key), "key is required when SharedType is Keyed."); + } + + return TestDataContainer.GetInstanceForKey(key!, type, _ => factory()!); + } + + private static T GetForAssembly(Type? testClassType, Func factory) + { + if (testClassType is null) + { + throw new ArgumentNullException(nameof(testClassType), "testClassType is required when SharedType is PerAssembly."); + } + + return (T)TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, typeof(T), _ => factory()!)!; + } + + private static object? GetForAssembly(Type type, Type? testClassType, Func factory) + { + if (testClassType is null) + { + throw new ArgumentNullException(nameof(testClassType), "testClassType is required when SharedType is PerAssembly."); + } + + return TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, type, _ => factory()!); + } +} 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 1ff9acff74..b08d85780f 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 @@ -1198,6 +1198,17 @@ namespace public string Key { get; init; } public Type { get; init; } } + public static class SharedDataSources + { + public static object? GetOrCreate(.SharedType sharedType, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors)] type, ? testClassType, string? key, factory) { } + public static object? GetOrCreate(.SharedType sharedType, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors)] type, .DataGeneratorMetadata dataGeneratorMetadata, string? key, factory) { } + public static T GetOrCreate(.SharedType sharedType, ? testClassType = null, string? key = null) + where T : new() { } + public static T GetOrCreate(.SharedType sharedType, .DataGeneratorMetadata dataGeneratorMetadata, string? key = null) + where T : new() { } + public static T GetOrCreate(.SharedType sharedType, ? testClassType, string? key, factory) { } + public static T GetOrCreate(.SharedType sharedType, .DataGeneratorMetadata dataGeneratorMetadata, string? key, factory) { } + } public enum SharedType { None = 0, 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 97a073cc98..2de5ad38a2 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 @@ -1198,6 +1198,17 @@ namespace public string Key { get; init; } public Type { get; init; } } + public static class SharedDataSources + { + public static object? GetOrCreate(.SharedType sharedType, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors)] type, ? testClassType, string? key, factory) { } + public static object? GetOrCreate(.SharedType sharedType, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors)] type, .DataGeneratorMetadata dataGeneratorMetadata, string? key, factory) { } + public static T GetOrCreate(.SharedType sharedType, ? testClassType = null, string? key = null) + where T : new() { } + public static T GetOrCreate(.SharedType sharedType, .DataGeneratorMetadata dataGeneratorMetadata, string? key = null) + where T : new() { } + public static T GetOrCreate(.SharedType sharedType, ? testClassType, string? key, factory) { } + public static T GetOrCreate(.SharedType sharedType, .DataGeneratorMetadata dataGeneratorMetadata, string? key, factory) { } + } public enum SharedType { None = 0, 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 bd1f9325d5..bb8a1a6d3d 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 @@ -1198,6 +1198,17 @@ namespace public string Key { get; init; } public Type { get; init; } } + public static class SharedDataSources + { + public static object? GetOrCreate(.SharedType sharedType, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors)] type, ? testClassType, string? key, factory) { } + public static object? GetOrCreate(.SharedType sharedType, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors)] type, .DataGeneratorMetadata dataGeneratorMetadata, string? key, factory) { } + public static T GetOrCreate(.SharedType sharedType, ? testClassType = null, string? key = null) + where T : new() { } + public static T GetOrCreate(.SharedType sharedType, .DataGeneratorMetadata dataGeneratorMetadata, string? key = null) + where T : new() { } + public static T GetOrCreate(.SharedType sharedType, ? testClassType, string? key, factory) { } + public static T GetOrCreate(.SharedType sharedType, .DataGeneratorMetadata dataGeneratorMetadata, string? key, factory) { } + } public enum SharedType { None = 0, 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 08b1e25e4b..093c221195 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 @@ -1156,6 +1156,17 @@ namespace public string Key { get; init; } public Type { get; init; } } + public static class SharedDataSources + { + public static object? GetOrCreate(.SharedType sharedType, type, ? testClassType, string? key, factory) { } + public static object? GetOrCreate(.SharedType sharedType, type, .DataGeneratorMetadata dataGeneratorMetadata, string? key, factory) { } + public static T GetOrCreate(.SharedType sharedType, ? testClassType = null, string? key = null) + where T : new() { } + public static T GetOrCreate(.SharedType sharedType, .DataGeneratorMetadata dataGeneratorMetadata, string? key = null) + where T : new() { } + public static T GetOrCreate(.SharedType sharedType, ? testClassType, string? key, factory) { } + public static T GetOrCreate(.SharedType sharedType, .DataGeneratorMetadata dataGeneratorMetadata, string? key, factory) { } + } public enum SharedType { None = 0,