diff --git a/README_Template.md b/README_Template.md index 1bb0069537f..e95741164fa 100644 --- a/README_Template.md +++ b/README_Template.md @@ -53,7 +53,7 @@ Visual Studio Code is supported. - Native AOT / Trimmed Single File application support - Source generated tests -- Dependency injection support ([See here](https://thomhurst.github.io/TUnit/docs/tutorial-extras/class-constructors)) +- Property injection - Full async support - Parallel by default, with mechanisms to: - Run specific tests completely on their own @@ -69,6 +69,7 @@ Visual Studio Code is supported. - Class - Test - Designed to avoid common pitfalls such as leaky test states +- Dependency injection support ([See here](https://thomhurst.github.io/TUnit/docs/tutorial-extras/class-constructors)) - Ability to view and interrogate metadata and results from various assembly/class/test context objects ## Installation diff --git a/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs b/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs index 9d61ef9fbe0..0a0bc94907f 100644 --- a/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs +++ b/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs @@ -1,6 +1,6 @@ namespace TUnit.Core; -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public sealed class ArgumentsAttribute : TestDataAttribute { public object?[] Values { get; } diff --git a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs index bac7ffe71db..f5a08eea3ed 100644 --- a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs @@ -2,7 +2,7 @@ namespace TUnit.Core; -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public sealed class ClassDataSourceAttribute<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : TestDataAttribute where T : new() { diff --git a/TUnit.Core/Attributes/TestData/DataSourceGeneratorAttribute.cs b/TUnit.Core/Attributes/TestData/DataSourceGeneratorAttribute.cs index dfb9833b536..6beef6a0699 100644 --- a/TUnit.Core/Attributes/TestData/DataSourceGeneratorAttribute.cs +++ b/TUnit.Core/Attributes/TestData/DataSourceGeneratorAttribute.cs @@ -1,30 +1,30 @@ namespace TUnit.Core; -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute : TestDataAttribute, IDataSourceGeneratorAttribute { public abstract IEnumerable GenerateDataSources(); } -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute : TestDataAttribute, IDataSourceGeneratorAttribute { public abstract IEnumerable<(T1, T2)> GenerateDataSources(); } -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute : TestDataAttribute, IDataSourceGeneratorAttribute { public abstract IEnumerable<(T1, T2, T3)> GenerateDataSources(); } -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute : TestDataAttribute, IDataSourceGeneratorAttribute { public abstract IEnumerable<(T1, T2, T3, T4)> GenerateDataSources(); } -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute : TestDataAttribute, IDataSourceGeneratorAttribute { public abstract IEnumerable<(T1, T2, T3, T4, T5)> GenerateDataSources(); diff --git a/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs index 39de5873c67..7b1d69caeac 100644 --- a/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs @@ -1,6 +1,6 @@ namespace TUnit.Core; -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public class MethodDataSourceAttribute : TestDataAttribute { public Type? ClassProvidingDataSource { get; } diff --git a/TUnit.Engine.SourceGenerator.Tests/ClassConstructorTest.cs b/TUnit.Engine.SourceGenerator.Tests/ClassConstructorTest.cs index 23f38eded51..895b39751d7 100644 --- a/TUnit.Engine.SourceGenerator.Tests/ClassConstructorTest.cs +++ b/TUnit.Engine.SourceGenerator.Tests/ClassConstructorTest.cs @@ -1,4 +1,5 @@ using TUnit.Engine.SourceGenerator.CodeGenerators; +using TUnit.Engine.SourceGenerator.Tests.Extensions; using TUnit.Engine.SourceGenerator.Tests.Options; namespace TUnit.Engine.SourceGenerator.Tests; diff --git a/TUnit.Engine.SourceGenerator.Tests/ClassTupleDataSourceDrivenTests.cs b/TUnit.Engine.SourceGenerator.Tests/ClassTupleDataSourceDrivenTests.cs index de9b6b9690f..943ac58aedf 100644 --- a/TUnit.Engine.SourceGenerator.Tests/ClassTupleDataSourceDrivenTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/ClassTupleDataSourceDrivenTests.cs @@ -16,18 +16,18 @@ public Task Test(int index, string classMethodName, string testMethodName) => Ru Assert.That(generatedFiles.Length, Is.EqualTo(4)); Assert.That(generatedFiles[index], Does.Contain($"var classArgTuples = global::System.TupleExtensions.ToTuple(global::TUnit.TestProject.ClassTupleDataSourceDrivenTests.{classMethodName}());")); - Assert.That(generatedFiles[index], Does.Contain("global::System.Int32 classArg0 = classArgTuples.Item1;")); + Assert.That(generatedFiles[index], Does.Contain("global::System.Int32 classArg = classArgTuples.Item1;")); Assert.That(generatedFiles[index], Does.Contain("global::System.String classArg1 = classArgTuples.Item2;")); Assert.That(generatedFiles[index], Does.Contain("global::System.Boolean classArg2 = classArgTuples.Item3;")); - Assert.That(generatedFiles[index], Does.Contain("var resettableClassFactoryDelegate = () => new ResettableLazy(() => new global::TUnit.TestProject.ClassTupleDataSourceDrivenTests(classArg0, classArg1, classArg2));")); + Assert.That(generatedFiles[index], Does.Contain("var resettableClassFactoryDelegate = () => new ResettableLazy(() => new global::TUnit.TestProject.ClassTupleDataSourceDrivenTests(classArg, classArg1, classArg2));")); Assert.That(generatedFiles[index], Does.Contain($"var methodArgTuples = global::System.TupleExtensions.ToTuple(global::TUnit.TestProject.ClassTupleDataSourceDrivenTests.{testMethodName}());")); - Assert.That(generatedFiles[index], Does.Contain("global::System.Int32 methodArg0 = methodArgTuples.Item1;")); + Assert.That(generatedFiles[index], Does.Contain("global::System.Int32 methodArg = methodArgTuples.Item1;")); Assert.That(generatedFiles[index], Does.Contain("global::System.String methodArg1 = methodArgTuples.Item2;")); Assert.That(generatedFiles[index], Does.Contain("global::System.Boolean methodArg2 = methodArgTuples.Item3;")); - Assert.That(generatedFiles[index], Does.Contain("TestMethodArguments = [methodArg0, methodArg1, methodArg2],")); - Assert.That(generatedFiles[index], Does.Contain("DisplayName = $\"DataSource_TupleMethod({methodArg0}, {methodArg1}, {methodArg2})\",")); - Assert.That(generatedFiles[index], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataSource_TupleMethod(methodArg0, methodArg1, methodArg2))")); + Assert.That(generatedFiles[index], Does.Contain("TestMethodArguments = [methodArg, methodArg1, methodArg2],")); + Assert.That(generatedFiles[index], Does.Contain("DisplayName = $\"DataSource_TupleMethod({methodArg}, {methodArg1}, {methodArg2})\",")); + Assert.That(generatedFiles[index], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataSource_TupleMethod(methodArg, methodArg1, methodArg2))")); }); } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator.Tests/CustomDisplayNameTests.cs b/TUnit.Engine.SourceGenerator.Tests/CustomDisplayNameTests.cs index 85c701f2aa7..f9d660ff592 100644 --- a/TUnit.Engine.SourceGenerator.Tests/CustomDisplayNameTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/CustomDisplayNameTests.cs @@ -14,7 +14,7 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, Assert.That(generatedFiles[0], Does.Contain("DisplayName = $\"A super important test!\",")); Assert.That(generatedFiles[1], Does.Contain("DisplayName = $\"Another super important test!\",")); - Assert.That(generatedFiles[2], Does.Contain("DisplayName = $\"Test with: {methodArg0} {methodArg1} {methodArg2}!\",")); - Assert.That(generatedFiles[3], Does.Contain("DisplayName = $\"Test with: {methodArg0} {methodArg1} {methodArg2}!\",")); + Assert.That(generatedFiles[2], Does.Contain("DisplayName = $\"Test with: {methodArg} {methodArg1} {methodArg2}!\",")); + Assert.That(generatedFiles[3], Does.Contain("DisplayName = $\"Test with: {methodArg} {methodArg1} {methodArg2}!\",")); }); } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator.Tests/DataDrivenTests.cs b/TUnit.Engine.SourceGenerator.Tests/DataDrivenTests.cs index b71ded713a9..866164d8f89 100644 --- a/TUnit.Engine.SourceGenerator.Tests/DataDrivenTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/DataDrivenTests.cs @@ -22,35 +22,35 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, { Assert.That(generatedFiles.Length, Is.EqualTo(18)); - Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg0 = 1;")); - Assert.That(generatedFiles[1], Does.Contain("global::System.Int32 methodArg0 = 2;")); - Assert.That(generatedFiles[2], Does.Contain("global::System.Int32 methodArg0 = 3;")); + Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg = 1;")); + Assert.That(generatedFiles[1], Does.Contain("global::System.Int32 methodArg = 2;")); + Assert.That(generatedFiles[2], Does.Contain("global::System.Int32 methodArg = 3;")); - Assert.That(generatedFiles[3], Does.Contain("global::System.Int32 methodArg0 = 1;")); + Assert.That(generatedFiles[3], Does.Contain("global::System.Int32 methodArg = 1;")); Assert.That(generatedFiles[3], Does.Contain("global::System.String methodArg1 = \"String\";")); - Assert.That(generatedFiles[4], Does.Contain("global::System.Int32 methodArg0 = 2;")); + Assert.That(generatedFiles[4], Does.Contain("global::System.Int32 methodArg = 2;")); Assert.That(generatedFiles[4], Does.Contain("global::System.String methodArg1 = \"String2\";")); - Assert.That(generatedFiles[5], Does.Contain("global::System.Int32 methodArg0 = 3;")); + Assert.That(generatedFiles[5], Does.Contain("global::System.Int32 methodArg = 3;")); Assert.That(generatedFiles[5], Does.Contain("global::System.String methodArg1 = \"String3\";")); - Assert.That(generatedFiles[6], Does.Contain("global::TUnit.TestProject.TestEnum methodArg0 = global::TUnit.TestProject.TestEnum.One;")); - Assert.That(generatedFiles[7], Does.Contain("global::TUnit.TestProject.TestEnum methodArg0 = global::TUnit.TestProject.TestEnum.Two;")); - Assert.That(generatedFiles[8], Does.Contain("global::TUnit.TestProject.TestEnum methodArg0 = (global::TUnit.TestProject.TestEnum)(-1);")); + Assert.That(generatedFiles[6], Does.Contain("global::TUnit.TestProject.TestEnum methodArg = global::TUnit.TestProject.TestEnum.One;")); + Assert.That(generatedFiles[7], Does.Contain("global::TUnit.TestProject.TestEnum methodArg = global::TUnit.TestProject.TestEnum.Two;")); + Assert.That(generatedFiles[8], Does.Contain("global::TUnit.TestProject.TestEnum methodArg = (global::TUnit.TestProject.TestEnum)(-1);")); - Assert.That(generatedFiles[9], Does.Contain("global::System.String methodArg0 = null;")); + Assert.That(generatedFiles[9], Does.Contain("global::System.String methodArg = null;")); - Assert.That(generatedFiles[10], Does.Contain("global::System.String methodArg0 = \"\";")); + Assert.That(generatedFiles[10], Does.Contain("global::System.String methodArg = \"\";")); - Assert.That(generatedFiles[11], Does.Contain("global::System.String methodArg0 = \"Foo bar!\";")); + Assert.That(generatedFiles[11], Does.Contain("global::System.String methodArg = \"Foo bar!\";")); - Assert.That(generatedFiles[12], Does.Contain("global::System.Boolean? methodArg0 = null;")); - Assert.That(generatedFiles[13], Does.Contain("global::System.Boolean? methodArg0 = false;")); - Assert.That(generatedFiles[14], Does.Contain("global::System.Boolean? methodArg0 = true;")); + Assert.That(generatedFiles[12], Does.Contain("global::System.Boolean? methodArg = null;")); + Assert.That(generatedFiles[13], Does.Contain("global::System.Boolean? methodArg = false;")); + Assert.That(generatedFiles[14], Does.Contain("global::System.Boolean? methodArg = true;")); - Assert.That(generatedFiles[15], Does.Contain("global::System.Type methodArg0 = typeof(global::System.Object);")); + Assert.That(generatedFiles[15], Does.Contain("global::System.Type methodArg = typeof(global::System.Object);")); - Assert.That(generatedFiles[16], Does.Contain("global::System.Int32[] methodArg0 = new[] { 1, 2, 3 };")); + Assert.That(generatedFiles[16], Does.Contain("global::System.Int32[] methodArg = new[] { 1, 2, 3 };")); - Assert.That(generatedFiles[17], Does.Contain("global::System.Int32 methodArg0 = global::System.Int32.MaxValue;")); + Assert.That(generatedFiles[17], Does.Contain("global::System.Int32 methodArg = global::System.Int32.MaxValue;")); }); } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator.Tests/DataSourceGeneratorTests.cs b/TUnit.Engine.SourceGenerator.Tests/DataSourceGeneratorTests.cs index e181e2e4dd4..17e92aad323 100644 --- a/TUnit.Engine.SourceGenerator.Tests/DataSourceGeneratorTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/DataSourceGeneratorTests.cs @@ -20,17 +20,17 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, Assert.That(generatedFiles[2], Does.Contain("TestId = $\"CL-GAC0:TL-GAC1:TUnit.TestProject.DataSourceGeneratorTests(System.Int32,System.String,System.Boolean).GeneratedData_Method2(System.Int32,System.String,System.Boolean):0\",")); Assert.That(generatedFiles[2], Does.Contain("var methodArgGeneratedDataArray = global::System.Reflection.CustomAttributeExtensions.GetCustomAttributes>(methodInfo).SelectMany(x => x.GenerateDataSources());")); Assert.That(generatedFiles[2], Does.Contain("foreach (var methodArgGeneratedData in methodArgGeneratedDataArray)")); - Assert.That(generatedFiles[2], Does.Contain("global::System.Int32 methodArg0 = methodArgGeneratedData.Item1;")); + Assert.That(generatedFiles[2], Does.Contain("global::System.Int32 methodArg = methodArgGeneratedData.Item1;")); Assert.That(generatedFiles[2], Does.Contain("global::System.String methodArg1 = methodArgGeneratedData.Item2;")); Assert.That(generatedFiles[2], Does.Contain("global::System.Boolean methodArg2 = methodArgGeneratedData.Item3;")); - Assert.That(generatedFiles[2], Does.Contain("classInstance.GeneratedData_Method2(methodArg0, methodArg1, methodArg2)")); + Assert.That(generatedFiles[2], Does.Contain("classInstance.GeneratedData_Method2(methodArg, methodArg1, methodArg2)")); Assert.That(generatedFiles[4], Does.Contain("TestId = $\"CL-GAC0:TL-GAC1:TUnit.TestProject.DataSourceGeneratorTests(System.Int32,System.String,System.Boolean).GeneratedData_Method3(System.Int32,System.String,System.Boolean):0\",")); Assert.That(generatedFiles[4], Does.Contain("var methodArgGeneratedDataArray = global::System.Reflection.CustomAttributeExtensions.GetCustomAttributes(methodInfo).SelectMany(x => x.GenerateDataSources());")); Assert.That(generatedFiles[4], Does.Contain("foreach (var methodArgGeneratedData in methodArgGeneratedDataArray)")); - Assert.That(generatedFiles[4], Does.Contain("global::System.Int32 methodArg0 = methodArgGeneratedData.Item1;")); + Assert.That(generatedFiles[4], Does.Contain("global::System.Int32 methodArg = methodArgGeneratedData.Item1;")); Assert.That(generatedFiles[4], Does.Contain("global::System.String methodArg1 = methodArgGeneratedData.Item2;")); Assert.That(generatedFiles[4], Does.Contain("global::System.Boolean methodArg2 = methodArgGeneratedData.Item3;")); - Assert.That(generatedFiles[4], Does.Contain("classInstance.GeneratedData_Method3(methodArg0, methodArg1, methodArg2)")); + Assert.That(generatedFiles[4], Does.Contain("classInstance.GeneratedData_Method3(methodArg, methodArg1, methodArg2)")); }); } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator.Tests/EnumerableTupleDataSourceDrivenTests.cs b/TUnit.Engine.SourceGenerator.Tests/EnumerableTupleDataSourceDrivenTests.cs index 996d6b3e538..f2dbb4603dc 100644 --- a/TUnit.Engine.SourceGenerator.Tests/EnumerableTupleDataSourceDrivenTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/EnumerableTupleDataSourceDrivenTests.cs @@ -14,21 +14,21 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, Assert.That(generatedFiles[0], Does.Contain("foreach (var methodData in global::TUnit.TestProject.EnumerableTupleDataSourceDrivenTests.TupleMethod())")); Assert.That(generatedFiles[0], Does.Contain("var methodArgTuples = global::System.TupleExtensions.ToTuple(methodData);")); - Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg0 = methodArgTuples.Item1;")); + Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg = methodArgTuples.Item1;")); Assert.That(generatedFiles[0], Does.Contain("global::System.String methodArg1 = methodArgTuples.Item2;")); Assert.That(generatedFiles[0], Does.Contain("global::System.Boolean methodArg2 = methodArgTuples.Item3;")); - Assert.That(generatedFiles[0], Does.Contain("TestMethodArguments = [methodArg0, methodArg1, methodArg2],")); - Assert.That(generatedFiles[0], Does.Contain("DisplayName = $\"DataSource_TupleMethod({methodArg0}, {methodArg1}, {methodArg2})\",")); - Assert.That(generatedFiles[0], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataSource_TupleMethod(methodArg0, methodArg1, methodArg2))")); + Assert.That(generatedFiles[0], Does.Contain("TestMethodArguments = [methodArg, methodArg1, methodArg2],")); + Assert.That(generatedFiles[0], Does.Contain("DisplayName = $\"DataSource_TupleMethod({methodArg}, {methodArg1}, {methodArg2})\",")); + Assert.That(generatedFiles[0], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataSource_TupleMethod(methodArg, methodArg1, methodArg2))")); Assert.That(generatedFiles[1], Does.Contain("foreach (var methodData in global::TUnit.TestProject.EnumerableTupleDataSourceDrivenTests.NamedTupleMethod())")); Assert.That(generatedFiles[0], Does.Contain("var methodArgTuples = global::System.TupleExtensions.ToTuple(methodData);")); - Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg0 = methodArgTuples.Item1;")); + Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg = methodArgTuples.Item1;")); Assert.That(generatedFiles[0], Does.Contain("global::System.String methodArg1 = methodArgTuples.Item2;")); Assert.That(generatedFiles[0], Does.Contain("global::System.Boolean methodArg2 = methodArgTuples.Item3;")); - Assert.That(generatedFiles[0], Does.Contain("TestMethodArguments = [methodArg0, methodArg1, methodArg2],")); - Assert.That(generatedFiles[1], Does.Contain("TestMethodArguments = [methodArg0, methodArg1, methodArg2],")); - Assert.That(generatedFiles[1], Does.Contain("DisplayName = $\"DataSource_TupleMethod({methodArg0}, {methodArg1}, {methodArg2})\",")); - Assert.That(generatedFiles[1], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataSource_TupleMethod(methodArg0, methodArg1, methodArg2))")); + Assert.That(generatedFiles[0], Does.Contain("TestMethodArguments = [methodArg, methodArg1, methodArg2],")); + Assert.That(generatedFiles[1], Does.Contain("TestMethodArguments = [methodArg, methodArg1, methodArg2],")); + Assert.That(generatedFiles[1], Does.Contain("DisplayName = $\"DataSource_TupleMethod({methodArg}, {methodArg1}, {methodArg2})\",")); + Assert.That(generatedFiles[1], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataSource_TupleMethod(methodArg, methodArg1, methodArg2))")); }); } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator.Tests/InheritedPropertySetterTests.cs b/TUnit.Engine.SourceGenerator.Tests/InheritedPropertySetterTests.cs new file mode 100644 index 00000000000..e5476439470 --- /dev/null +++ b/TUnit.Engine.SourceGenerator.Tests/InheritedPropertySetterTests.cs @@ -0,0 +1,25 @@ +using TUnit.Engine.SourceGenerator.CodeGenerators; +using TUnit.Engine.SourceGenerator.Tests.Options; + +namespace TUnit.Engine.SourceGenerator.Tests; + +internal class InheritedPropertySetterTests : TestsBase +{ + [Test] + public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, + "TUnit.TestProject", + "InheritedPropertySetterTests.cs"), + new RunTestOptions + { + AdditionalFiles = + [ + Path.Combine(Git.RootDirectory.FullName, + "TUnit.TestProject", + "PropertySetterTests.cs") + ] + }, + generatedFiles => + { + Assert.That(generatedFiles.Length, Is.EqualTo(1)); + }); +} \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator.Tests/NullableByteArgumentTests.cs b/TUnit.Engine.SourceGenerator.Tests/NullableByteArgumentTests.cs index 9f38d3dbe3c..0429398bfb2 100644 --- a/TUnit.Engine.SourceGenerator.Tests/NullableByteArgumentTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/NullableByteArgumentTests.cs @@ -15,23 +15,23 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, Assert.That(generatedFiles[0], Does.Contain( """ - global::System.Byte? methodArg0 = (global::System.Byte)1; + global::System.Byte? methodArg = (global::System.Byte)1; """)); Assert.That(generatedFiles[1], Does.Contain( """ - global::System.Byte? methodArg0 = null; + global::System.Byte? methodArg = null; """)); Assert.That(generatedFiles[2].IgnoreWhitespaceFormatting(), Does.Contain( """ - global::System.Byte methodArg0 = (global::System.Byte)1; + global::System.Byte methodArg = (global::System.Byte)1; global::System.Byte? methodArg1 = (global::System.Byte)1; """.IgnoreWhitespaceFormatting())); Assert.That(generatedFiles[3].IgnoreWhitespaceFormatting(), Does.Contain( """ - global::System.Byte methodArg0 = (global::System.Byte)1; + global::System.Byte methodArg = (global::System.Byte)1; global::System.Byte? methodArg1 = null; """.IgnoreWhitespaceFormatting())); }); diff --git a/TUnit.Engine.SourceGenerator.Tests/NumberArgumentTests.cs b/TUnit.Engine.SourceGenerator.Tests/NumberArgumentTests.cs index c17604da0d0..c91ccbb9ebe 100644 --- a/TUnit.Engine.SourceGenerator.Tests/NumberArgumentTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/NumberArgumentTests.cs @@ -12,12 +12,12 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, { Assert.That(generatedFiles.Length, Is.EqualTo(6)); - Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg0 = 1;")); - Assert.That(generatedFiles[1], Does.Contain("global::System.Double methodArg0 = 1.1;")); - Assert.That(generatedFiles[2], Does.Contain("global::System.Single methodArg0 = 1.1f;")); - Assert.That(generatedFiles[3], Does.Contain("global::System.Int64 methodArg0 = 1L;")); - Assert.That(generatedFiles[4], Does.Contain("global::System.UInt64 methodArg0 = 1UL;")); - Assert.That(generatedFiles[5], Does.Contain("global::System.UInt32 methodArg0 = 1U;")); + Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg = 1;")); + Assert.That(generatedFiles[1], Does.Contain("global::System.Double methodArg = 1.1;")); + Assert.That(generatedFiles[2], Does.Contain("global::System.Single methodArg = 1.1f;")); + Assert.That(generatedFiles[3], Does.Contain("global::System.Int64 methodArg = 1L;")); + Assert.That(generatedFiles[4], Does.Contain("global::System.UInt64 methodArg = 1UL;")); + Assert.That(generatedFiles[5], Does.Contain("global::System.UInt32 methodArg = 1U;")); }); [Test] diff --git a/TUnit.Engine.SourceGenerator.Tests/PropertySetterTests.cs b/TUnit.Engine.SourceGenerator.Tests/PropertySetterTests.cs new file mode 100644 index 00000000000..552769b8741 --- /dev/null +++ b/TUnit.Engine.SourceGenerator.Tests/PropertySetterTests.cs @@ -0,0 +1,15 @@ +using TUnit.Engine.SourceGenerator.CodeGenerators; + +namespace TUnit.Engine.SourceGenerator.Tests; + +internal class PropertySetterTests : TestsBase +{ + [Test] + public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, + "TUnit.TestProject", + "PropertySetterTests.cs"), + generatedFiles => + { + Assert.That(generatedFiles.Length, Is.EqualTo(1)); + }); +} \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator.Tests/StringArgumentTests.cs b/TUnit.Engine.SourceGenerator.Tests/StringArgumentTests.cs index 4a0e51f8bc3..0be81b2741d 100644 --- a/TUnit.Engine.SourceGenerator.Tests/StringArgumentTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/StringArgumentTests.cs @@ -12,42 +12,42 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, { Assert.That(generatedFiles[0], Does.Contain( """ - global::System.String methodArg0 = ""; + global::System.String methodArg = ""; """)); Assert.That(generatedFiles[1], Does.Contain( """ - global::System.String methodArg0 = @"\"; + global::System.String methodArg = @"\"; """)); Assert.That(generatedFiles[2], Does.Contain( """ - global::System.String methodArg0 = @"\t"; + global::System.String methodArg = @"\t"; """)); Assert.That(generatedFiles[3], Does.Contain( """ - global::System.String methodArg0 = "\t"; + global::System.String methodArg = "\t"; """)); Assert.That(generatedFiles[4], Does.Contain( """ - global::System.String methodArg0 = "\\t"; + global::System.String methodArg = "\\t"; """)); Assert.That(generatedFiles[5], Does.Contain( """ - global::System.String methodArg0 = "\\\t"; + global::System.String methodArg = "\\\t"; """)); Assert.That(generatedFiles[6], Does.Contain( """ - global::System.String methodArg0 = "\\\\t"; + global::System.String methodArg = "\\\\t"; """)); Assert.That(generatedFiles[7], Does.Contain( """" - global::System.String methodArg0 = """ + global::System.String methodArg = """ Hello World """; diff --git a/TUnit.Engine.SourceGenerator.Tests/TestsBase.cs b/TUnit.Engine.SourceGenerator.Tests/TestsBase.cs index 1f198e974cf..e02dffd34cd 100644 --- a/TUnit.Engine.SourceGenerator.Tests/TestsBase.cs +++ b/TUnit.Engine.SourceGenerator.Tests/TestsBase.cs @@ -53,16 +53,25 @@ ..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => File.ReadAllText ) .AddReferences(ReferencesHelper.References) .AddSyntaxTrees(additionalSources.Select(x => CSharpSyntaxTree.ParseText(x))); - - foreach (var error in compilation.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error)) - { - throw new Exception( - $"There was an error with the compilation. Have you added required references and additional files?{Environment.NewLine}{Environment.NewLine}{error}"); - } // Run generators. Don't forget to use the new compilation rather than the previous one. driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out var diagnostics); + foreach (var error in diagnostics.Where(x => IsError(x))) + { + throw new Exception + ( + $""" + There was an error with the compilation. + Have you added required references and additional files? + + {error} + + {string.Join(Environment.NewLine, newCompilation.SyntaxTrees.Select(x => x.GetText()))} + """ + ); + } + // Retrieve all files in the compilation. var generatedFiles = newCompilation.SyntaxTrees .Select(t => t.GetText().ToString()) @@ -78,4 +87,19 @@ ..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => File.ReadAllText assertions(generatedFiles); } + + private static bool IsError(Diagnostic x) + { + if (x.Severity == DiagnosticSeverity.Error) + { + return true; + } + + if (x.Severity == DiagnosticSeverity.Warning && x.GetMessage().Contains("failed to generate source")) + { + return true; + } + + return false; + } } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator.Tests/TimeoutCancellationTokenTests.cs b/TUnit.Engine.SourceGenerator.Tests/TimeoutCancellationTokenTests.cs index 16f9aa234fc..37694978421 100644 --- a/TUnit.Engine.SourceGenerator.Tests/TimeoutCancellationTokenTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/TimeoutCancellationTokenTests.cs @@ -19,23 +19,23 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, Assert.That(generatedFiles[1], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.InheritedTimeoutAttribute(cancellationToken))")); Assert.That(generatedFiles[2], Does.Contain("TestName = \"DataTest\"")); - Assert.That(generatedFiles[2], Does.Contain("global::System.Int32 methodArg0 = 1;")); - Assert.That(generatedFiles[2], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataTest(methodArg0, cancellationToken))")); + Assert.That(generatedFiles[2], Does.Contain("global::System.Int32 methodArg = 1;")); + Assert.That(generatedFiles[2], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataTest(methodArg, cancellationToken))")); Assert.That(generatedFiles[3], Does.Contain("TestName = \"DataSourceTest\"")); Assert.That(generatedFiles[3], Does.Contain("global::System.Int32 methodArg = global::TUnit.TestProject.TimeoutCancellationTokenTests.DataSource();")); Assert.That(generatedFiles[3], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataSourceTest(methodArg, cancellationToken))")); Assert.That(generatedFiles[4], Does.Contain("TestName = \"MatrixTest\"")); - Assert.That(generatedFiles[4], Does.Contain("global::System.Int32 methodArg0 = 1;")); - Assert.That(generatedFiles[4], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.MatrixTest(methodArg0, cancellationToken))")); + Assert.That(generatedFiles[4], Does.Contain("global::System.Int32 methodArg = 1;")); + Assert.That(generatedFiles[4], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.MatrixTest(methodArg, cancellationToken))")); Assert.That(generatedFiles[5], Does.Contain("TestName = \"MatrixTest\"")); - Assert.That(generatedFiles[5], Does.Contain("global::System.Int32 methodArg0 = 2;")); - Assert.That(generatedFiles[5], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.MatrixTest(methodArg0, cancellationToken))")); + Assert.That(generatedFiles[5], Does.Contain("global::System.Int32 methodArg = 2;")); + Assert.That(generatedFiles[5], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.MatrixTest(methodArg, cancellationToken))")); Assert.That(generatedFiles[6], Does.Contain("TestName = \"MatrixTest\"")); - Assert.That(generatedFiles[6], Does.Contain("global::System.Int32 methodArg0 = 3;")); - Assert.That(generatedFiles[6], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.MatrixTest(methodArg0, cancellationToken))")); + Assert.That(generatedFiles[6], Does.Contain("global::System.Int32 methodArg = 3;")); + Assert.That(generatedFiles[6], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.MatrixTest(methodArg, cancellationToken))")); }); } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator.Tests/TupleDataSourceDrivenTests.cs b/TUnit.Engine.SourceGenerator.Tests/TupleDataSourceDrivenTests.cs index 9f0d64bf2b5..082b733216c 100644 --- a/TUnit.Engine.SourceGenerator.Tests/TupleDataSourceDrivenTests.cs +++ b/TUnit.Engine.SourceGenerator.Tests/TupleDataSourceDrivenTests.cs @@ -13,11 +13,11 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName, Assert.That(generatedFiles.Length, Is.EqualTo(1)); Assert.That(generatedFiles[0], Does.Contain("var methodArgTuples = global::System.TupleExtensions.ToTuple(global::TUnit.TestProject.TupleDataSourceDrivenTests.TupleMethod());")); - Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg0 = methodArgTuples.Item1;")); + Assert.That(generatedFiles[0], Does.Contain("global::System.Int32 methodArg = methodArgTuples.Item1;")); Assert.That(generatedFiles[0], Does.Contain("global::System.String methodArg1 = methodArgTuples.Item2;")); Assert.That(generatedFiles[0], Does.Contain("global::System.Boolean methodArg2 = methodArgTuples.Item3;")); - Assert.That(generatedFiles[0], Does.Contain("TestMethodArguments = [methodArg0, methodArg1, methodArg2],")); - Assert.That(generatedFiles[0], Does.Contain("DisplayName = $\"DataSource_TupleMethod({methodArg0}, {methodArg1}, {methodArg2})\",")); - Assert.That(generatedFiles[0], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataSource_TupleMethod(methodArg0, methodArg1, methodArg2))")); + Assert.That(generatedFiles[0], Does.Contain("TestMethodArguments = [methodArg, methodArg1, methodArg2],")); + Assert.That(generatedFiles[0], Does.Contain("DisplayName = $\"DataSource_TupleMethod({methodArg}, {methodArg1}, {methodArg2})\",")); + Assert.That(generatedFiles[0], Does.Contain("TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.DataSource_TupleMethod(methodArg, methodArg1, methodArg2))")); }); } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ArgumentsRetriever.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ArgumentsRetriever.cs index 18b007457b4..48c727937ea 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ArgumentsRetriever.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ArgumentsRetriever.cs @@ -10,15 +10,16 @@ internal static class ArgumentsRetriever { public static IEnumerable GetArguments(GeneratorAttributeSyntaxContext context, ImmutableArray parameters, + ImmutableArray parameterOrPropertyTypes, ImmutableArray dataAttributes, INamedTypeSymbol namedTypeSymbol, - ArgumentsType argumentsType) + ArgumentsType argumentsType, + string? propertyName = null) { - if (parameters.IsDefaultOrEmpty || !IsDataDriven(dataAttributes, parameters)) + if (parameterOrPropertyTypes.IsDefaultOrEmpty || !IsDataDriven(dataAttributes, parameters)) { - yield return new EmptyArgumentsContainer + yield return new EmptyArgumentsContainer(argumentsType) { - ArgumentsType = argumentsType, DisposeAfterTest = false }; @@ -43,12 +44,12 @@ public static IEnumerable GetArguments(GeneratorAttributeSyn if (name == WellKnownFullyQualifiedClassNames.ArgumentsAttribute.WithGlobalPrefix) { - yield return DataDrivenArgumentsRetriever.ParseArguments(context, dataAttribute, parameters, argumentsType, index); + yield return DataDrivenArgumentsRetriever.ParseArguments(context, dataAttribute, parameterOrPropertyTypes, argumentsType, index); } if (name == WellKnownFullyQualifiedClassNames.MethodDataSourceAttribute.WithGlobalPrefix) { - yield return MethodDataSourceRetriever.ParseMethodData(context, parameters, namedTypeSymbol, dataAttribute, argumentsType, index); + yield return MethodDataSourceRetriever.ParseMethodData(context, parameterOrPropertyTypes, namedTypeSymbol, dataAttribute, argumentsType, index); } if (name == WellKnownFullyQualifiedClassNames.ClassDataSourceAttribute.WithGlobalPrefix) @@ -63,7 +64,7 @@ public static IEnumerable GetArguments(GeneratorAttributeSyn if (dataAttribute.AttributeClass?.IsOrInherits(WellKnownFullyQualifiedClassNames.DataSourceGeneratorAttribute.WithGlobalPrefix) == true) { - yield return DataSourceGeneratorRetriever.Parse(parameters, namedTypeSymbol, dataAttribute, argumentsType, index); + yield return DataSourceGeneratorRetriever.Parse(namedTypeSymbol, dataAttribute, argumentsType, index, propertyName); } } } @@ -74,4 +75,32 @@ private static bool IsDataDriven(ImmutableArray dataAttributes, return dataAttributes.Any(x => x.IsDataSourceAttribute()) || parameters.HasMatrixAttribute(); } + + public static ClassPropertiesContainer GetProperties(GeneratorAttributeSyntaxContext context, INamedTypeSymbol namedTypeSymbol) + { + var settableProperties = namedTypeSymbol + .GetSelfAndBaseTypes() + .SelectMany(x => x.GetMembers()) + .OfType() + .Where(x => x.IsRequired) + .ToList(); + + if (!settableProperties.Any()) + { + return new ClassPropertiesContainer([]); + } + + var list = new List<(IPropertySymbol, ArgumentsContainer)>(); + + foreach (var propertySymbol in settableProperties) + { + var dataSourceAttributes = propertySymbol.GetAttributes().Where(x => x.IsDataSourceAttribute()).ToImmutableArray(); + if (dataSourceAttributes.Any()) + { + list.AddRange(GetArguments(context, ImmutableArray.Empty, ImmutableArray.Create(propertySymbol.Type), dataSourceAttributes, namedTypeSymbol, ArgumentsType.Property, propertySymbol.Name).Select(argumentsContainer => (propertySymbol, argumentsContainer))); + } + } + + return new ClassPropertiesContainer(list.Where(x => x.Item2 is not EmptyArgumentsContainer).ToList()); + } } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ClassConstructorRetriever.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ClassConstructorRetriever.cs index ac080502fa9..d8eb6749f0e 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ClassConstructorRetriever.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ClassConstructorRetriever.cs @@ -10,7 +10,8 @@ public static ArgumentsContainer Parse(AttributeData dataAttribute, int index) { var type = dataAttribute.AttributeClass!.TypeArguments.First(); - return new ClassConstructorAttributeContainer + // TODO: Property injection? + return new ClassConstructorAttributeContainer(ArgumentsType.ClassConstructor) { AttributeIndex = index, ClassConstructorType = type.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ClassDataSourceRetriever.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ClassDataSourceRetriever.cs index 0f640321fe6..b2830c123e8 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ClassDataSourceRetriever.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/ClassDataSourceRetriever.cs @@ -18,13 +18,12 @@ public static ArgumentsContainer ParseClassData(INamedTypeSymbol namedTypeSymbol var key = classDataAttribute.NamedArguments.SafeFirstOrDefault(x => x.Key == "Key").Value.Value as string; - return new ClassDataSourceAttributeContainer + return new ClassDataSourceAttributeContainer(argumentsType) { Attribute = classDataAttribute, AttributeIndex = index, Key = key, ForClass = namedTypeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), - ArgumentsType = argumentsType, SharedArgumentType = sharedArgumentType, TypeName = fullyQualifiedGenericType, DisposeAfterTest = classDataAttribute.NamedArguments.FirstOrDefault(x => x.Key == "DisposeAfterTest").Value.Value as bool? ?? true, diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/DataDrivenArgumentsRetriever.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/DataDrivenArgumentsRetriever.cs index bca8bb99e66..5d21d8219bf 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/DataDrivenArgumentsRetriever.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/DataDrivenArgumentsRetriever.cs @@ -10,7 +10,8 @@ namespace TUnit.Engine.SourceGenerator.CodeGenerators.Helpers; internal static class DataDrivenArgumentsRetriever { public static ArgumentsContainer ParseArguments(GeneratorAttributeSyntaxContext context, - AttributeData argumentAttribute, ImmutableArray parameterSymbols, + AttributeData argumentAttribute, + ImmutableArray parameterOrPropertyTypeSymbols, ArgumentsType argumentsType, int dataAttributeIndex) { @@ -18,18 +19,14 @@ public static ArgumentsContainer ParseArguments(GeneratorAttributeSyntaxContext if (constructorArgument.IsNull) { - return new ArgumentsAttributeContainer + return new ArgumentsAttributeContainer(argumentsType, [new Argument(type: parameterOrPropertyTypeSymbols.SafeFirstOrDefault()? + .ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) ?? "var", + invocation: null + )]) { ArgumentsType = argumentsType, Attribute = argumentAttribute, AttributeIndex = dataAttributeIndex, - Arguments = - [ - new Argument(type: parameterSymbols.SafeFirstOrDefault()?.Type - .ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) ?? "var", - invocation: null - ), - ], DisposeAfterTest = argumentAttribute.NamedArguments.FirstOrDefault(x => x.Key == "DisposeAfterTest").Value.Value as bool? ?? true, }; } @@ -38,14 +35,12 @@ public static ArgumentsContainer ParseArguments(GeneratorAttributeSyntaxContext var arguments = attributeSyntax.ArgumentList!.Arguments; var objectArray = constructorArgument.Values; - var args = GetArguments(context, objectArray, arguments, parameterSymbols); + var args = GetArguments(context, objectArray, arguments, parameterOrPropertyTypeSymbols); - return new ArgumentsAttributeContainer + return new ArgumentsAttributeContainer(argumentsType, [.. args]) { - ArgumentsType = argumentsType, Attribute = argumentAttribute, AttributeIndex = dataAttributeIndex, - Arguments = [.. args], DisposeAfterTest = argumentAttribute.NamedArguments.FirstOrDefault(x => x.Key == "DisposeAfterTest").Value.Value as bool? ?? true, }; } @@ -53,11 +48,11 @@ public static ArgumentsContainer ParseArguments(GeneratorAttributeSyntaxContext private static IEnumerable GetArguments(GeneratorAttributeSyntaxContext context, ImmutableArray objectArray, SeparatedSyntaxList arguments, - ImmutableArray parameterSymbols) + ImmutableArray parameterOrPropertyTypeSymbols) { if (objectArray.IsDefaultOrEmpty) { - var type = parameterSymbols.SafeFirstOrDefault()?.Type + var type = parameterOrPropertyTypeSymbols.SafeFirstOrDefault() ?.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) ?? "var"; return [new Argument(type, null)]; @@ -65,7 +60,7 @@ private static IEnumerable GetArguments(GeneratorAttributeSyntaxContex return objectArray.Zip(arguments, (o, a) => (o, a)).Select((element, index) => { - var type = GetTypeFromParameters(parameterSymbols, index); + var type = GetTypeFromParameters(parameterOrPropertyTypeSymbols, index); return new Argument(type?.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) ?? TypedConstantParser.GetFullyQualifiedTypeNameFromTypedConstantValue(element.o), @@ -73,13 +68,13 @@ private static IEnumerable GetArguments(GeneratorAttributeSyntaxContex }); } - private static ITypeSymbol? GetTypeFromParameters(ImmutableArray parameterSymbols, int index) + private static ITypeSymbol? GetTypeFromParameters(ImmutableArray parameterSymbols, int index) { if (parameterSymbols.IsDefaultOrEmpty) { return null; } - return parameterSymbols.ElementAtOrDefault(index)?.Type; + return parameterSymbols.ElementAtOrDefault(index); } } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/DataSourceGeneratorRetriever.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/DataSourceGeneratorRetriever.cs index 0bdd1f7e5b4..18204dfc7e5 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/DataSourceGeneratorRetriever.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/DataSourceGeneratorRetriever.cs @@ -1,5 +1,4 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using TUnit.Engine.SourceGenerator.Enums; using TUnit.Engine.SourceGenerator.Extensions; using TUnit.Engine.SourceGenerator.Models.Arguments; @@ -8,17 +7,28 @@ namespace TUnit.Engine.SourceGenerator.CodeGenerators.Helpers; internal static class DataSourceGeneratorRetriever { - public static ArgumentsContainer Parse(ImmutableArray parameters, - INamedTypeSymbol namedTypeSymbol, AttributeData attributeData, ArgumentsType argumentsType, int index) + public static ArgumentsContainer Parse( + INamedTypeSymbol namedTypeSymbol, + AttributeData attributeData, + ArgumentsType argumentsType, + int index, + string? propertyName) { return new GeneratedArgumentsContainer + ( + ArgumentsType: argumentsType, + TestClassTypeName: namedTypeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), + AttributeDataGeneratorType: attributeData.AttributeClass!.ToDisplayString(DisplayFormats + .FullyQualifiedGenericWithGlobalPrefix), + GenericArguments: GetDataGeneratorAttributeBaseClass(attributeData.AttributeClass).TypeArguments + .Select(x => x.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix)).ToArray(), + AttributeIndex: index + ) { - ArgumentsType = argumentsType, - TestClassTypeName = namedTypeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), - AttributeDataGeneratorType = attributeData.AttributeClass!.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), - GenericArguments = GetDataGeneratorAttributeBaseClass(attributeData.AttributeClass).TypeArguments.Select(x => x.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix)).ToArray(), - AttributeIndex = index, - DisposeAfterTest = attributeData.NamedArguments.FirstOrDefault(x => x.Key == "DisposeAfterTest").Value.Value as bool? ?? true, + DisposeAfterTest = + attributeData.NamedArguments.FirstOrDefault(x => x.Key == "DisposeAfterTest").Value.Value as bool? ?? + true, + PropertyName = propertyName }; } diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/MatrixRetriever.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/MatrixRetriever.cs index a6b6ff25570..33b06cfa305 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/MatrixRetriever.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/MatrixRetriever.cs @@ -44,12 +44,10 @@ public static IEnumerable Parse(GeneratorAttributeSyntaxCont var index = 0; return GetMatrixArgumentsList(mappedToArgumentArrays) .Select(x => MapToArgumentEnumerable(context, x, parameters)) - .Select(x => new ArgumentsAttributeContainer + .Select(x => new ArgumentsAttributeContainer(argumentsType, [.. x]) { - ArgumentsType = argumentsType, Attribute = attr, AttributeIndex = ++index, - Arguments = [.. x], DisposeAfterTest = attr.NamedArguments.FirstOrDefault(x => x.Key == "DisposeAfterTest").Value.Value as bool? ?? true, }); } diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/MethodDataSourceRetriever.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/MethodDataSourceRetriever.cs index ca1a3b733cc..a07135c4c4e 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/MethodDataSourceRetriever.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/MethodDataSourceRetriever.cs @@ -9,7 +9,7 @@ namespace TUnit.Engine.SourceGenerator.CodeGenerators.Helpers; internal static class MethodDataSourceRetriever { public static ArgumentsContainer ParseMethodData(GeneratorAttributeSyntaxContext context, - ImmutableArray parameters, + ImmutableArray parameterOrPropertyTypes, INamedTypeSymbol namedTypeSymbol, AttributeData methodDataAttribute, ArgumentsType argumentsType, int index) { string typeName; @@ -19,8 +19,10 @@ public static ArgumentsContainer ParseMethodData(GeneratorAttributeSyntaxContext typeName = namedTypeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix); - dataSourceMethod = namedTypeSymbol.GetMembers(methodDataAttribute.ConstructorArguments[0].Value!.ToString()) - .OfType().First(); + dataSourceMethod = namedTypeSymbol + .GetMembersIncludingBase() + .OfType() + .First(x => x.Name == methodDataAttribute.ConstructorArguments[0].Value!.ToString()); } else { @@ -30,8 +32,9 @@ public static ArgumentsContainer ParseMethodData(GeneratorAttributeSyntaxContext .FullyQualifiedGenericWithGlobalPrefix); dataSourceMethod = typeContainingDataSourceMethod - .GetMembers(methodDataAttribute.ConstructorArguments[1].Value!.ToString()) - .OfType().First(); + .GetMembersIncludingBase() + .OfType() + .First(x => x.Name == methodDataAttribute.ConstructorArguments[1].Value!.ToString()); } var methodName = dataSourceMethod.Name; @@ -41,7 +44,7 @@ public static ArgumentsContainer ParseMethodData(GeneratorAttributeSyntaxContext methodDataAttribute.NamedArguments.FirstOrDefault(x => x.Key == "DisposeAfterTest").Value.Value as bool? ?? true; - var isEnumerable = dataSourceMethod.ReturnType.EnumerableGenericTypeIs(context, parameters.Select(x => x.Type).ToImmutableArray(), out var innerType); + var isEnumerable = dataSourceMethod.ReturnType.EnumerableGenericTypeIs(context, parameterOrPropertyTypes, out var innerType); if (!isEnumerable) { @@ -53,47 +56,54 @@ public static ArgumentsContainer ParseMethodData(GeneratorAttributeSyntaxContext var tupleTypes = typeSymbol.TupleUnderlyingType?.TypeArguments ?? typeSymbol.TypeArguments; - if (CheckTupleTypes(parameters, tupleTypes) is {} result) + if (CheckTupleTypes(parameterOrPropertyTypes, tupleTypes) is {} result) { return new MethodDataSourceAttributeContainer + ( + TestClassTypeName: namedTypeSymbol.ToDisplayString(DisplayFormats + .FullyQualifiedGenericWithGlobalPrefix), + ArgumentsType: argumentsType, + IsEnumerableData: isEnumerable, + IsStatic: isStatic, + MethodName: methodName, + TypeName: typeName, + MethodReturnType: dataSourceMethod.ReturnType.ToDisplayString(DisplayFormats + .FullyQualifiedGenericWithGlobalPrefix), + TupleTypes: result.ToArray() + ) { - TestClassTypeName = namedTypeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), - ArgumentsType = argumentsType, Attribute = methodDataAttribute, AttributeIndex = index, - IsEnumerableData = isEnumerable, - IsStatic = isStatic, - MethodName = methodName, - TypeName = typeName, DisposeAfterTest = disposeAfterTest, - MethodReturnType = dataSourceMethod.ReturnType.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), - TupleTypes = result.ToArray() }; } - } - + } + return new MethodDataSourceAttributeContainer + ( + TestClassTypeName: namedTypeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), + ArgumentsType: argumentsType, + IsEnumerableData: isEnumerable, + IsStatic: isStatic, + MethodName: methodName, + TypeName: typeName, + MethodReturnType: dataSourceMethod.ReturnType.ToDisplayString(DisplayFormats + .FullyQualifiedGenericWithGlobalPrefix), + TupleTypes: [] + ) { - TestClassTypeName = namedTypeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), - ArgumentsType = argumentsType, Attribute = methodDataAttribute, AttributeIndex = index, - IsEnumerableData = isEnumerable, - IsStatic = isStatic, - MethodName = methodName, - TypeName = typeName, DisposeAfterTest = disposeAfterTest, - MethodReturnType = dataSourceMethod.ReturnType.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), - TupleTypes = [], }; } - private static IEnumerable CheckTupleTypes(ImmutableArray parameters, ImmutableArray tupleTypes) + private static IEnumerable CheckTupleTypes(ImmutableArray parameterOrPropertyTypes, ImmutableArray tupleTypes) { for (var index = 0; index < tupleTypes.Length; index++) { var tupleType = tupleTypes.ElementAtOrDefault(index); - var parameterType = parameters.ElementAtOrDefault(index)?.Type; + var parameterType = parameterOrPropertyTypes.ElementAtOrDefault(index); yield return tupleType?.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) ?? parameterType?.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix)!; diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/TestSourceDataModelRetriever.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/TestSourceDataModelRetriever.cs index d4c4a6ed917..a57f79e7ad9 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/TestSourceDataModelRetriever.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Helpers/TestSourceDataModelRetriever.cs @@ -19,9 +19,11 @@ public static IEnumerable ParseTestDatas(this IMethodSymbol } var testAttribute = methodSymbol.GetRequiredTestAttribute(); - - var classArgumentsContainers = ArgumentsRetriever.GetArguments(context, namedTypeSymbol.InstanceConstructors.FirstOrDefault()?.Parameters ?? ImmutableArray.Empty, GetClassAttributes(namedTypeSymbol).Concat(namedTypeSymbol.ContainingAssembly.GetAttributes().Where(x => x.IsDataSourceAttribute())).ToImmutableArray(), namedTypeSymbol, ArgumentsType.ClassConstructor).ToArray(); - var testArgumentsContainers = ArgumentsRetriever.GetArguments(context, methodSymbol.Parameters, methodSymbol.GetAttributes(), namedTypeSymbol, ArgumentsType.Method); + + var constructorParameters = namedTypeSymbol.InstanceConstructors.FirstOrDefault()?.Parameters ?? ImmutableArray.Empty; + var classArgumentsContainers = ArgumentsRetriever.GetArguments(context, constructorParameters, constructorParameters.Select(x => x.Type).ToImmutableArray(), GetClassAttributes(namedTypeSymbol).Concat(namedTypeSymbol.ContainingAssembly.GetAttributes().Where(x => x.IsDataSourceAttribute())).ToImmutableArray(), namedTypeSymbol, ArgumentsType.ClassConstructor).ToArray(); + var testArgumentsContainers = ArgumentsRetriever.GetArguments(context, methodSymbol.Parameters, methodSymbol.Parameters.Select(x => x.Type).ToImmutableArray(), methodSymbol.GetAttributes(), namedTypeSymbol, ArgumentsType.Method); + var propertyArgumentsContainer = ArgumentsRetriever.GetProperties(context, namedTypeSymbol); var repeatCount = TestInformationRetriever.GetRepeatCount(methodSymbol.GetAttributesIncludingClass(namedTypeSymbol)); @@ -32,7 +34,7 @@ public static IEnumerable ParseTestDatas(this IMethodSymbol { foreach (var classArguments in classArgumentsContainers) { - foreach (var testSourceDataModel in GenerateTestSourceDataModels(methodSymbol, namedTypeSymbol, classArguments, runCount, testAttribute, testArguments)) + foreach (var testSourceDataModel in GenerateTestSourceDataModels(methodSymbol, namedTypeSymbol, classArguments, runCount, testAttribute, testArguments, propertyArgumentsContainer)) { yield return testSourceDataModel; } @@ -46,12 +48,11 @@ private static IEnumerable GetClassAttributes(INamedTypeSymbol na } private static IEnumerable GenerateTestSourceDataModels(IMethodSymbol methodSymbol, INamedTypeSymbol namedTypeSymbol, - ArgumentsContainer classArguments, int runCount, AttributeData testAttribute, ArgumentsContainer testArguments) + ArgumentsContainer classArguments, int runCount, AttributeData testAttribute, ArgumentsContainer testArguments, ClassPropertiesContainer classPropertiesContainer) { if (classArguments is EmptyArgumentsContainer) { - foreach (var testSourceDataModel in GenerateSingleClassInstance(methodSymbol, namedTypeSymbol, runCount, testAttribute, - testArguments)) + foreach (var testSourceDataModel in GenerateSingleClassInstance(methodSymbol, namedTypeSymbol, runCount, testAttribute, testArguments, classPropertiesContainer)) { yield return testSourceDataModel; } @@ -60,7 +61,7 @@ private static IEnumerable GenerateTestSourceDataModels(IMe } foreach (var generateMultipleClassInstance in GenerateMultipleClassInstances(methodSymbol, namedTypeSymbol, runCount, testAttribute, - classArguments, testArguments)) + classArguments, testArguments, classPropertiesContainer)) { yield return generateMultipleClassInstance; } @@ -68,7 +69,8 @@ private static IEnumerable GenerateTestSourceDataModels(IMe private static IEnumerable GenerateSingleClassInstance(IMethodSymbol methodSymbol, INamedTypeSymbol namedTypeSymbol, int runCount, AttributeData testAttribute, - ArgumentsContainer testArguments) + ArgumentsContainer testArguments, + ClassPropertiesContainer classPropertiesContainer) { for (var i = 0; i < runCount; i++) { @@ -76,21 +78,22 @@ private static IEnumerable GenerateSingleClassInstance(IMet { MethodSymbol = methodSymbol, ClassSymbol = namedTypeSymbol, - ClassArguments = new EmptyArgumentsContainer + ClassArguments = new EmptyArgumentsContainer(ArgumentsType.ClassConstructor) { - ArgumentsType = ArgumentsType.ClassConstructor, DisposeAfterTest = false, }, TestArguments = testArguments, CurrentRepeatAttempt = i, TestAttribute = testAttribute, + PropertyArguments = classPropertiesContainer }); } } private static IEnumerable GenerateMultipleClassInstances(IMethodSymbol methodSymbol, INamedTypeSymbol namedTypeSymbol, int runCount, AttributeData testAttribute, ArgumentsContainer classArguments, - ArgumentsContainer testArguments) + ArgumentsContainer testArguments, + ClassPropertiesContainer classPropertiesContainer) { for (var i = 0; i < runCount; i++) { @@ -102,6 +105,7 @@ private static IEnumerable GenerateMultipleClassInstances(I TestArguments = testArguments, CurrentRepeatAttempt = i, TestAttribute = testAttribute, + PropertyArguments = classPropertiesContainer }); } } @@ -136,6 +140,7 @@ private static TestSourceDataModel GetTestSourceDataModel(TestGenerationContext TestExecutor = allAttributes.FirstOrDefault(x => x.AttributeClass?.IsOrInherits("global::TUnit.Core.Executors.TestExecutorAttribute") == true)?.AttributeClass?.TypeArguments.FirstOrDefault()?.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), ParallelLimit = allAttributes.FirstOrDefault(x => x.AttributeClass?.IsOrInherits("global::TUnit.Core.ParallelLimiterAttribute") == true)?.AttributeClass?.TypeArguments.FirstOrDefault()?.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix), AttributeTypes = allAttributes.Select(x => x.AttributeClass?.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix)).OfType().Distinct().ToArray(), + PropertyArguments = testGenerationContext.PropertyArguments, }; } } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/VariableNames.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/VariableNames.cs index 405e05bfc4a..7618aa03879 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/VariableNames.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/VariableNames.cs @@ -6,6 +6,7 @@ public static class VariableNames public const string MethodData = "methodData"; public const string ClassArg = "classArg"; + public const string PropertyArg = "propertyArg"; public const string MethodArg = "methodArg"; diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/DisplayNameWriter.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/DisplayNameWriter.cs index 95a47c07ed9..779890d133c 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/DisplayNameWriter.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/DisplayNameWriter.cs @@ -25,7 +25,7 @@ public static string GetDisplayName(TestSourceDataModel testSourceDataModel) return null; } - var args = testSourceDataModel.MethodArguments.GenerateArgumentVariableNames(); + var args = testSourceDataModel.MethodArguments.VariableNames; for (var index = 0; index < testSourceDataModel.MethodParameterNames.Length; index++) { @@ -38,7 +38,7 @@ public static string GetDisplayName(TestSourceDataModel testSourceDataModel) private static string GetMethodArgs(TestSourceDataModel testSourceDataModel) { - var variableNames = testSourceDataModel.MethodArguments.GenerateArgumentVariableNames(); + var variableNames = testSourceDataModel.MethodArguments.VariableNames; if (!variableNames.Any()) { diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/GenericTestInvocationWriter.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/GenericTestInvocationWriter.cs index acbfe09d297..5311b06de11 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/GenericTestInvocationWriter.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/GenericTestInvocationWriter.cs @@ -18,24 +18,34 @@ public static void GenerateTestInvocationCode(SourceCodeWriter sourceBuilder, sourceBuilder.WriteLine( $"var methodInfo = typeof({fullyQualifiedClassType}).GetMethod(\"{testSourceDataModel.MethodName}\", {testSourceDataModel.MethodGenericTypeCount}, [{methodParameterTypesList}]);"); - testSourceDataModel.ClassArguments.GenerateInvocationStatements(sourceBuilder); + sourceBuilder.WriteLine(); - sourceBuilder.WriteLine( - $"var resettableClassFactoryDelegate = () => new ResettableLazy<{fullyQualifiedClassType}>(() => {NewClassWriter.ConstructClass(testSourceDataModel.FullyQualifiedTypeName, testSourceDataModel.ClassArguments)});"); + testSourceDataModel.ClassArguments.WriteVariableAssignments(sourceBuilder); + + testSourceDataModel.PropertyArguments.WriteVariableAssignments(sourceBuilder); + sourceBuilder.WriteLine(); + + sourceBuilder.Write($"var resettableClassFactoryDelegate = () => new ResettableLazy<{fullyQualifiedClassType}>(() => "); + + NewClassWriter.ConstructClass(sourceBuilder, testSourceDataModel.FullyQualifiedTypeName, testSourceDataModel.ClassArguments, testSourceDataModel.PropertyArguments); + + sourceBuilder.Write(");"); + + sourceBuilder.WriteLine(); sourceBuilder.WriteLine( - $"var resettableClassFactory = resettableClassFactoryDelegate();"); + "var resettableClassFactory = resettableClassFactoryDelegate();"); sourceBuilder.WriteLine(); - testSourceDataModel.MethodArguments.GenerateInvocationStatements(sourceBuilder); + testSourceDataModel.MethodArguments.WriteVariableAssignments(sourceBuilder); sourceBuilder.WriteLine($"TestRegistrar.RegisterTest<{fullyQualifiedClassType}>(new TestMetadata<{fullyQualifiedClassType}>"); sourceBuilder.WriteLine("{"); sourceBuilder.WriteLine($"TestId = $\"{testId}\","); - sourceBuilder.WriteLine($"TestClassArguments = [{testSourceDataModel.ClassArguments.GenerateArgumentVariableNames().ToCommaSeparatedString()}],"); - sourceBuilder.WriteLine($"TestMethodArguments = [{testSourceDataModel.MethodArguments.GenerateArgumentVariableNames().ToCommaSeparatedString()}],"); + sourceBuilder.WriteLine($"TestClassArguments = [{testSourceDataModel.ClassArguments.VariableNames.ToCommaSeparatedString()}],"); + sourceBuilder.WriteLine($"TestMethodArguments = [{testSourceDataModel.MethodArguments.VariableNames.ToCommaSeparatedString()}],"); sourceBuilder.WriteLine($"InternalTestClassArguments = [{ToInjectedTypes(testSourceDataModel.ClassArguments).ToCommaSeparatedString()}],"); sourceBuilder.WriteLine($"InternalTestMethodArguments = [{ToInjectedTypes(testSourceDataModel.MethodArguments).ToCommaSeparatedString()}],"); sourceBuilder.WriteLine($"CurrentRepeatAttempt = {testSourceDataModel.CurrentRepeatAttempt},"); @@ -84,7 +94,7 @@ private static string GetParallelLimit(string? parallelLimit) private static IEnumerable ToInjectedTypes(ArgumentsContainer argumentsContainer) { var types = argumentsContainer.GetArgumentTypes(); - var variableNames = argumentsContainer.GenerateArgumentVariableNames(); + var variableNames = argumentsContainer.VariableNames; if (argumentsContainer is not ClassDataSourceAttributeContainer classDataSourceAttributeContainer || classDataSourceAttributeContainer.SharedArgumentType == "TUnit.Core.SharedType.None") diff --git a/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/NewClassWriter.cs b/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/NewClassWriter.cs index 4bff666e187..1d820818cf3 100644 --- a/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/NewClassWriter.cs +++ b/TUnit.Engine.SourceGenerator/CodeGenerators/Writers/NewClassWriter.cs @@ -5,13 +5,16 @@ namespace TUnit.Engine.SourceGenerator.CodeGenerators.Writers; internal static class NewClassWriter { - public static string ConstructClass(string typeName, ArgumentsContainer argumentsContainer) + public static void ConstructClass(SourceCodeWriter sourceCodeWriter, string typeName, ArgumentsContainer argumentsContainer, ClassPropertiesContainer classPropertiesContainer) { if (argumentsContainer is ClassConstructorAttributeContainer) { - return $"classConstructor.Create<{typeName}>()"; + sourceCodeWriter.Write($"classConstructor.Create<{typeName}>()"); + return; } - return $"new {typeName}({argumentsContainer.GenerateArgumentVariableNames().ToCommaSeparatedString()})"; + sourceCodeWriter.Write($"new {typeName}({argumentsContainer.VariableNames.ToCommaSeparatedString()})"); + + classPropertiesContainer.WriteObjectInitializer(sourceCodeWriter); } } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/Enums/ArgumentsType.cs b/TUnit.Engine.SourceGenerator/Enums/ArgumentsType.cs index dbf3ca90b13..797ace95187 100644 --- a/TUnit.Engine.SourceGenerator/Enums/ArgumentsType.cs +++ b/TUnit.Engine.SourceGenerator/Enums/ArgumentsType.cs @@ -3,5 +3,6 @@ public enum ArgumentsType { ClassConstructor, - Method + Method, + Property, } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/Extensions/TypeExtensions.cs b/TUnit.Engine.SourceGenerator/Extensions/TypeExtensions.cs index 63e5d6ce61c..08e6f023729 100644 --- a/TUnit.Engine.SourceGenerator/Extensions/TypeExtensions.cs +++ b/TUnit.Engine.SourceGenerator/Extensions/TypeExtensions.cs @@ -19,8 +19,7 @@ public static IEnumerable GetMembersIncludingBase(this ITypeSymbol name throw new Exception("ErrorTypeSymbol - Have you added any missing file sources to the compilation?"); } - if (symbol.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) - is "global::System.Object") + if (symbol.SpecialType == SpecialType.System_Object) { break; } diff --git a/TUnit.Engine.SourceGenerator/Models/Arguments/ArgumentsAttributeContainer.cs b/TUnit.Engine.SourceGenerator/Models/Arguments/ArgumentsAttributeContainer.cs index 8c1ce1ded7e..ae962221b1b 100644 --- a/TUnit.Engine.SourceGenerator/Models/Arguments/ArgumentsAttributeContainer.cs +++ b/TUnit.Engine.SourceGenerator/Models/Arguments/ArgumentsAttributeContainer.cs @@ -5,17 +5,24 @@ namespace TUnit.Engine.SourceGenerator.Models.Arguments; internal record ArgumentsAttributeContainer : DataAttributeContainer { - public required Argument[] Arguments { get; init; } + public Argument[] Arguments { get; init; } + public override string[] VariableNames { get; } - public override void GenerateInvocationStatements(SourceCodeWriter sourceCodeWriter) + public ArgumentsAttributeContainer(ArgumentsType argumentsType, Argument[] arguments) : base(argumentsType) + { + Arguments = arguments; + VariableNames = arguments.Select((_, i) => GenerateVariableName(i)).ToArray(); + } + + public override void WriteVariableAssignments(SourceCodeWriter sourceCodeWriter) { for (var index = 0; index < Arguments.Length; index++) { - var argument = Arguments.ElementAt(index); + var argument = Arguments[index]; var invocation = argument.Invocation; - var variableName = $"{VariableNamePrefix}{index}"; + var variableName = VariableNames[index]; sourceCodeWriter.WriteLine($"{argument.Type} {variableName} = {invocation};"); } @@ -28,11 +35,6 @@ public override void CloseInvocationStatementsParenthesis(SourceCodeWriter sourc // Nothing } - public override string[] GenerateArgumentVariableNames() - { - return Arguments.Select((_, index) => $"{VariableNamePrefix}{index}").ToArray(); - } - public override string[] GetArgumentTypes() { return Arguments.Select(x => x.Type).ToArray(); diff --git a/TUnit.Engine.SourceGenerator/Models/Arguments/ArgumentsContainer.cs b/TUnit.Engine.SourceGenerator/Models/Arguments/ArgumentsContainer.cs index ea59d0c7721..17f18c3587a 100644 --- a/TUnit.Engine.SourceGenerator/Models/Arguments/ArgumentsContainer.cs +++ b/TUnit.Engine.SourceGenerator/Models/Arguments/ArgumentsContainer.cs @@ -3,16 +3,39 @@ namespace TUnit.Engine.SourceGenerator.Models.Arguments; -internal abstract record ArgumentsContainer +internal abstract record ArgumentsContainer(ArgumentsType ArgumentsType) { - public required ArgumentsType ArgumentsType { get; init; } public required bool DisposeAfterTest { get; init; } - public abstract void GenerateInvocationStatements(SourceCodeWriter sourceCodeWriter); + public abstract void WriteVariableAssignments(SourceCodeWriter sourceCodeWriter); public abstract void CloseInvocationStatementsParenthesis(SourceCodeWriter sourceCodeWriter); - public abstract string[] GenerateArgumentVariableNames(); + public abstract string[] VariableNames { get; } public abstract string[] GetArgumentTypes(); - protected string VariableNamePrefix => ArgumentsType == ArgumentsType.ClassConstructor - ? VariableNames.ClassArg - : VariableNames.MethodArg; + protected string VariableNamePrefix + { + get + { + return ArgumentsType switch + { + ArgumentsType.ClassConstructor => CodeGenerators.VariableNames.ClassArg, + ArgumentsType.Property => CodeGenerators.VariableNames.PropertyArg, + _ => CodeGenerators.VariableNames.MethodArg + }; + } + } + + protected string GenerateVariableName(int index) + { + if (ArgumentsType == ArgumentsType.Property) + { + return $"{VariableNamePrefix}_{Guid.NewGuid():N}"; + } + + if (index == 0) + { + return VariableNamePrefix; + } + + return $"{VariableNamePrefix}{index}"; + } }; \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/Models/Arguments/ClassConstructorAttributeContainer.cs b/TUnit.Engine.SourceGenerator/Models/Arguments/ClassConstructorAttributeContainer.cs index 18325996a91..7014261e78f 100644 --- a/TUnit.Engine.SourceGenerator/Models/Arguments/ClassConstructorAttributeContainer.cs +++ b/TUnit.Engine.SourceGenerator/Models/Arguments/ClassConstructorAttributeContainer.cs @@ -1,3 +1,5 @@ +using TUnit.Engine.SourceGenerator.Enums; + namespace TUnit.Engine.SourceGenerator.Models.Arguments; internal record ClassConstructorAttributeContainer : ArgumentsContainer @@ -5,9 +7,16 @@ internal record ClassConstructorAttributeContainer : ArgumentsContainer public required string ClassConstructorType { get; init; } public int AttributeIndex { get; init; } - public override void GenerateInvocationStatements(SourceCodeWriter sourceCodeWriter) + public ClassConstructorAttributeContainer(ArgumentsType argumentsType) : base(argumentsType) + { + VariableNames = ArgumentsType == ArgumentsType.Property + ? [$"classConstructor{Guid.NewGuid():N}"] + : []; + } + + public override void WriteVariableAssignments(SourceCodeWriter sourceCodeWriter) { - sourceCodeWriter.WriteLine($"var classConstructor = new {ClassConstructorType}();"); + sourceCodeWriter.WriteLine($"var {VariableNames.ElementAtOrDefault(0) ?? "classConstructor"} = new {ClassConstructorType}();"); sourceCodeWriter.WriteLine(); } @@ -16,10 +25,7 @@ public override void CloseInvocationStatementsParenthesis(SourceCodeWriter sourc // Nothing } - public override string[] GenerateArgumentVariableNames() - { - return []; - } + public override string[] VariableNames { get; } public override string[] GetArgumentTypes() { diff --git a/TUnit.Engine.SourceGenerator/Models/Arguments/ClassDataSourceAttributeContainer.cs b/TUnit.Engine.SourceGenerator/Models/Arguments/ClassDataSourceAttributeContainer.cs index ff3da7f5f69..129d95f984c 100644 --- a/TUnit.Engine.SourceGenerator/Models/Arguments/ClassDataSourceAttributeContainer.cs +++ b/TUnit.Engine.SourceGenerator/Models/Arguments/ClassDataSourceAttributeContainer.cs @@ -5,16 +5,19 @@ namespace TUnit.Engine.SourceGenerator.Models.Arguments; internal record ClassDataSourceAttributeContainer : DataAttributeContainer { + public ClassDataSourceAttributeContainer(ArgumentsType ArgumentsType) : base(ArgumentsType) + { + VariableNames = [GenerateVariableName(0)]; + } + public required string TypeName { get; init; } public required string SharedArgumentType { get; init; } public required string? ForClass { get; init; } public required string? Key { get; init; } - public override void GenerateInvocationStatements(SourceCodeWriter sourceCodeWriter) + public override void WriteVariableAssignments(SourceCodeWriter sourceCodeWriter) { - var variableName = ArgumentsType == ArgumentsType.ClassConstructor - ? VariableNames.ClassArg - : VariableNames.MethodArg; + var variableName = VariableNames[0]; if (SharedArgumentType is "TUnit.Core.SharedType.Globally") { @@ -47,14 +50,7 @@ public override void CloseInvocationStatementsParenthesis(SourceCodeWriter sourc // Nothing } - public override string[] GenerateArgumentVariableNames() - { - var variableName = ArgumentsType == ArgumentsType.ClassConstructor - ? VariableNames.ClassArg - : VariableNames.MethodArg; - - return [$"{variableName}"]; - } + public override string[] VariableNames { get; } public override string[] GetArgumentTypes() { diff --git a/TUnit.Engine.SourceGenerator/Models/Arguments/ClassPropertiesContainer.cs b/TUnit.Engine.SourceGenerator/Models/Arguments/ClassPropertiesContainer.cs new file mode 100644 index 00000000000..e751d4cacf7 --- /dev/null +++ b/TUnit.Engine.SourceGenerator/Models/Arguments/ClassPropertiesContainer.cs @@ -0,0 +1,31 @@ +using Microsoft.CodeAnalysis; + +namespace TUnit.Engine.SourceGenerator.Models.Arguments; + +internal record ClassPropertiesContainer(IReadOnlyCollection<(IPropertySymbol PropertySymbol, ArgumentsContainer ArgumentsContainer)> PropertyContainers) +{ + public void WriteVariableAssignments(SourceCodeWriter sourceCodeWriter) + { + foreach (var (_, argumentsContainer) in PropertyContainers) + { + argumentsContainer.WriteVariableAssignments(sourceCodeWriter); + } + } + + public void WriteObjectInitializer(SourceCodeWriter sourceCodeWriter) + { + if (!PropertyContainers.Any()) + { + return; + } + + sourceCodeWriter.WriteLine("{"); + + foreach (var (propertySymbol, argumentsContainer) in PropertyContainers) + { + sourceCodeWriter.WriteLine($"{propertySymbol.Name} = {argumentsContainer.VariableNames[0]},"); + } + + sourceCodeWriter.WriteLine("}"); + } +} \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/Models/Arguments/DataAttributeContainer.cs b/TUnit.Engine.SourceGenerator/Models/Arguments/DataAttributeContainer.cs index 325d553965c..d6d48965464 100644 --- a/TUnit.Engine.SourceGenerator/Models/Arguments/DataAttributeContainer.cs +++ b/TUnit.Engine.SourceGenerator/Models/Arguments/DataAttributeContainer.cs @@ -1,8 +1,9 @@ using Microsoft.CodeAnalysis; +using TUnit.Engine.SourceGenerator.Enums; namespace TUnit.Engine.SourceGenerator.Models.Arguments; -internal abstract record DataAttributeContainer : ArgumentsContainer +internal abstract record DataAttributeContainer(ArgumentsType ArgumentsType) : ArgumentsContainer(ArgumentsType) { public required AttributeData Attribute { get; init; } public required int? AttributeIndex { get; init; } diff --git a/TUnit.Engine.SourceGenerator/Models/Arguments/EmptyArgumentsContainer.cs b/TUnit.Engine.SourceGenerator/Models/Arguments/EmptyArgumentsContainer.cs index 73cb3bba92d..beeedda187a 100644 --- a/TUnit.Engine.SourceGenerator/Models/Arguments/EmptyArgumentsContainer.cs +++ b/TUnit.Engine.SourceGenerator/Models/Arguments/EmptyArgumentsContainer.cs @@ -1,8 +1,10 @@ +using TUnit.Engine.SourceGenerator.Enums; + namespace TUnit.Engine.SourceGenerator.Models.Arguments; -internal record EmptyArgumentsContainer : ArgumentsContainer +internal record EmptyArgumentsContainer(ArgumentsType ArgumentsType) : ArgumentsContainer(ArgumentsType) { - public override void GenerateInvocationStatements(SourceCodeWriter sourceCodeWriter) + public override void WriteVariableAssignments(SourceCodeWriter sourceCodeWriter) { // Nothing } @@ -12,10 +14,7 @@ public override void CloseInvocationStatementsParenthesis(SourceCodeWriter sourc // Nothing } - public override string[] GenerateArgumentVariableNames() - { - return []; - } + public override string[] VariableNames { get; } = []; public override string[] GetArgumentTypes() { diff --git a/TUnit.Engine.SourceGenerator/Models/Arguments/GeneratedArgumentsContainer.cs b/TUnit.Engine.SourceGenerator/Models/Arguments/GeneratedArgumentsContainer.cs index 5f33df57afb..4de1d0f6380 100644 --- a/TUnit.Engine.SourceGenerator/Models/Arguments/GeneratedArgumentsContainer.cs +++ b/TUnit.Engine.SourceGenerator/Models/Arguments/GeneratedArgumentsContainer.cs @@ -4,26 +4,45 @@ namespace TUnit.Engine.SourceGenerator.Models.Arguments; internal record GeneratedArgumentsContainer : ArgumentsContainer { - public required int AttributeIndex { get; set; } - public required string TestClassTypeName { get; init; } - public required string[] GenericArguments { get; init; } - public required string AttributeDataGeneratorType { get; init; } + public GeneratedArgumentsContainer(ArgumentsType ArgumentsType, int AttributeIndex, string TestClassTypeName, string[] GenericArguments, string AttributeDataGeneratorType) : base(ArgumentsType) + { + this.AttributeIndex = AttributeIndex; + this.TestClassTypeName = TestClassTypeName; + this.GenericArguments = GenericArguments; + this.AttributeDataGeneratorType = AttributeDataGeneratorType; + + VariableNames = GenerateArgumentVariableNames(); + } + + public required string? PropertyName { get; init; } - public override void GenerateInvocationStatements(SourceCodeWriter sourceCodeWriter) + public override void WriteVariableAssignments(SourceCodeWriter sourceCodeWriter) { - var objectToGetAttributesFrom = ArgumentsType == ArgumentsType.Method - ? "methodInfo" - : $"typeof({TestClassTypeName})"; + var objectToGetAttributesFrom = ArgumentsType switch + { + ArgumentsType.Method => "methodInfo", + ArgumentsType.Property => $"typeof({TestClassTypeName}).GetProperty(\"{PropertyName}\")", + _ => $"typeof({TestClassTypeName})" + }; + + if (ArgumentsType == ArgumentsType.Property) + { + sourceCodeWriter.WriteLine($"var {VariableNames[0]} = global::System.Reflection.CustomAttributeExtensions.GetCustomAttributes<{AttributeDataGeneratorType}>({objectToGetAttributesFrom}).SelectMany(x => x.GenerateDataSources()).ElementAtOrDefault(0);"); + return; + } + + var arrayVariableName = $"{VariableNamePrefix}GeneratedDataArray"; + var generatedDataVariableName = $"{VariableNamePrefix}GeneratedData"; - sourceCodeWriter.WriteLine($"var {VariableNamePrefix}GeneratedDataArray = global::System.Reflection.CustomAttributeExtensions.GetCustomAttributes<{AttributeDataGeneratorType}>({objectToGetAttributesFrom}).SelectMany(x => x.GenerateDataSources());"); - sourceCodeWriter.WriteLine($"foreach (var {VariableNamePrefix}GeneratedData in {VariableNamePrefix}GeneratedDataArray)"); + sourceCodeWriter.WriteLine($"var {arrayVariableName} = global::System.Reflection.CustomAttributeExtensions.GetCustomAttributes<{AttributeDataGeneratorType}>({objectToGetAttributesFrom}).SelectMany(x => x.GenerateDataSources());"); + sourceCodeWriter.WriteLine($"foreach (var {generatedDataVariableName} in {arrayVariableName})"); sourceCodeWriter.WriteLine("{"); if (GenericArguments.Length > 1) { for (var i = 0; i < GenericArguments.Length; i++) { - sourceCodeWriter.WriteLine($"{GenericArguments[i]} {VariableNamePrefix}{i} = {VariableNamePrefix}GeneratedData.Item{i + 1};"); + sourceCodeWriter.WriteLine($"{GenericArguments[i]} {VariableNames[i]} = {generatedDataVariableName}.Item{i + 1};"); } sourceCodeWriter.WriteLine(); @@ -35,18 +54,38 @@ public override void CloseInvocationStatementsParenthesis(SourceCodeWriter sourc sourceCodeWriter.WriteLine("}"); } - public override string[] GenerateArgumentVariableNames() + public override string[] VariableNames { get; } + + public override string[] GetArgumentTypes() { + return GenericArguments; + } + + public int AttributeIndex { get; init; } + + public string TestClassTypeName { get; init; } + + public string[] GenericArguments { get; init; } + + public string AttributeDataGeneratorType { get; init; } + + private string[] GenerateArgumentVariableNames() + { + if (ArgumentsType == ArgumentsType.Property) + { + return [GenerateVariableName(0)]; + } + if (GenericArguments.Length == 1) { return [$"{VariableNamePrefix}GeneratedData"]; } - - return Enumerable.Range(0, GenericArguments.Length).Select(i => $"{VariableNamePrefix}{i}").ToArray(); - } - public override string[] GetArgumentTypes() - { - return GenericArguments; + if (ArgumentsType == ArgumentsType.Property) + { + throw new Exception("Multiple data values not supported for property injection."); + } + + return Enumerable.Range(0, GenericArguments.Length).Select(i => $"{GenerateVariableName(i)}").ToArray(); } } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/Models/Arguments/MethodDataSourceAttributeContainer.cs b/TUnit.Engine.SourceGenerator/Models/Arguments/MethodDataSourceAttributeContainer.cs index 2d26cd6a282..b28646ba2a2 100644 --- a/TUnit.Engine.SourceGenerator/Models/Arguments/MethodDataSourceAttributeContainer.cs +++ b/TUnit.Engine.SourceGenerator/Models/Arguments/MethodDataSourceAttributeContainer.cs @@ -5,29 +5,35 @@ namespace TUnit.Engine.SourceGenerator.Models.Arguments; internal record MethodDataSourceAttributeContainer : DataAttributeContainer { - public required string TestClassTypeName { get; init; } - public required string TypeName { get; init; } - public required string MethodName { get; init; } - public required bool IsStatic { get; init; } - public required bool IsEnumerableData { get; init; } - public required string[] TupleTypes { get; init; } - public required string MethodReturnType { get; set; } + public MethodDataSourceAttributeContainer(ArgumentsType ArgumentsType, string TestClassTypeName, string TypeName, string MethodName, bool IsStatic, bool IsEnumerableData, string[] TupleTypes, string MethodReturnType) : base(ArgumentsType) + { + this.TestClassTypeName = TestClassTypeName; + this.TypeName = TypeName; + this.MethodName = MethodName; + this.IsStatic = IsStatic; + this.IsEnumerableData = IsEnumerableData; + this.TupleTypes = TupleTypes; + this.MethodReturnType = MethodReturnType; + + VariableNames = GenerateArgumentVariableNames(); + } - public override void GenerateInvocationStatements(SourceCodeWriter sourceCodeWriter) + public override void WriteVariableAssignments(SourceCodeWriter sourceCodeWriter) { - var argsVariableNamePrefix = ArgumentsType == ArgumentsType.ClassConstructor - ? VariableNames.ClassArg - : VariableNames.MethodArg; - if (IsEnumerableData) { + if (ArgumentsType == ArgumentsType.Property) + { + throw new Exception("Property Injection is not supported with Enumerable data"); + } + var enumerableIndexName = ArgumentsType == ArgumentsType.ClassConstructor - ? VariableNames.EnumerableClassDataIndex - : VariableNames.EnumerableTestDataIndex; + ? CodeGenerators.VariableNames.EnumerableClassDataIndex + : CodeGenerators.VariableNames.EnumerableTestDataIndex; var dataName = ArgumentsType == ArgumentsType.ClassConstructor - ? VariableNames.ClassData - : VariableNames.MethodData; + ? CodeGenerators.VariableNames.ClassData + : CodeGenerators.VariableNames.MethodData; sourceCodeWriter.WriteLine($"foreach (var {dataName} in {GetMethodInvocation()})"); sourceCodeWriter.WriteLine("{"); @@ -35,31 +41,43 @@ public override void GenerateInvocationStatements(SourceCodeWriter sourceCodeWri if (TupleTypes.Any()) { - sourceCodeWriter.WriteLine($"var {argsVariableNamePrefix}Tuples = global::System.TupleExtensions.ToTuple<{string.Join(", ", TupleTypes)}>({dataName});"); + var tupleVariableName = $"{VariableNamePrefix}Tuples"; + if (ArgumentsType == ArgumentsType.Property) + { + tupleVariableName += Guid.NewGuid().ToString("N"); + } + + sourceCodeWriter.WriteLine($"var {tupleVariableName} = global::System.TupleExtensions.ToTuple<{string.Join(", ", TupleTypes)}>({dataName});"); for (var index = 0; index < TupleTypes.Length; index++) { var tupleType = TupleTypes[index]; - sourceCodeWriter.WriteLine($"{tupleType} {argsVariableNamePrefix}{index} = {argsVariableNamePrefix}Tuples.Item{index+1};"); + sourceCodeWriter.WriteLine($"{tupleType} {VariableNames[index]} = {tupleVariableName}.Item{index+1};"); } } } else if (TupleTypes.Any()) { - sourceCodeWriter.WriteLine($"var {argsVariableNamePrefix}Tuples = global::System.TupleExtensions.ToTuple<{string.Join(", ", TupleTypes)}>({GetMethodInvocation()});"); + var tupleVariableName = $"{VariableNamePrefix}Tuples"; + if (ArgumentsType == ArgumentsType.Property) + { + tupleVariableName += Guid.NewGuid().ToString("N"); + } + + sourceCodeWriter.WriteLine($"var {tupleVariableName} = global::System.TupleExtensions.ToTuple<{string.Join(", ", TupleTypes)}>({GetMethodInvocation()});"); for (var index = 0; index < TupleTypes.Length; index++) { var tupleType = TupleTypes[index]; - sourceCodeWriter.WriteLine($"{tupleType} {argsVariableNamePrefix}{index} = {argsVariableNamePrefix}Tuples.Item{index+1};"); + sourceCodeWriter.WriteLine($"{tupleType} {VariableNames[index]} = {tupleVariableName}.Item{index+1};"); } } else { - sourceCodeWriter.WriteLine($"{MethodReturnType} {argsVariableNamePrefix} = {GetMethodInvocation()};"); + sourceCodeWriter.WriteLine($"{MethodReturnType} {VariableNames[0]} = {GetMethodInvocation()};"); } sourceCodeWriter.WriteLine(); @@ -88,12 +106,14 @@ public override void CloseInvocationStatementsParenthesis(SourceCodeWriter sourc } } - public override string[] GenerateArgumentVariableNames() + public override string[] VariableNames { get; } + + public string[] GenerateArgumentVariableNames() { if (TupleTypes.Any()) { return TupleTypes - .Select((_, i) => $"{VariableNamePrefix}{i}") + .Select((_, i) => GenerateVariableName(i)) .ToArray(); } @@ -102,14 +122,14 @@ public override string[] GenerateArgumentVariableNames() return [ ArgumentsType == ArgumentsType.ClassConstructor - ? VariableNames.ClassData - : VariableNames.MethodData + ? CodeGenerators.VariableNames.ClassData + : CodeGenerators.VariableNames.MethodData ]; } return [ - VariableNamePrefix + GenerateVariableName(0) ]; } @@ -122,4 +142,12 @@ public override string[] GetArgumentTypes() return [MethodReturnType]; } + + public string TestClassTypeName { get; init; } + public string TypeName { get; init; } + public string MethodName { get; init; } + public bool IsStatic { get; init; } + public bool IsEnumerableData { get; init; } + public string[] TupleTypes { get; init; } + public string MethodReturnType { get; init; } } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/Models/TestGenerationContext.cs b/TUnit.Engine.SourceGenerator/Models/TestGenerationContext.cs index 057d5394800..3d389cdd70d 100644 --- a/TUnit.Engine.SourceGenerator/Models/TestGenerationContext.cs +++ b/TUnit.Engine.SourceGenerator/Models/TestGenerationContext.cs @@ -10,5 +10,6 @@ internal record TestGenerationContext public required IMethodSymbol MethodSymbol { get; init; } public required ArgumentsContainer TestArguments { get; init; } public required ArgumentsContainer ClassArguments { get; init; } + public required ClassPropertiesContainer PropertyArguments { get; init; } public required int CurrentRepeatAttempt { get; init; } } \ No newline at end of file diff --git a/TUnit.Engine.SourceGenerator/Models/TestSourceDataModel.cs b/TUnit.Engine.SourceGenerator/Models/TestSourceDataModel.cs index 57c05eb46b5..e818f854af8 100644 --- a/TUnit.Engine.SourceGenerator/Models/TestSourceDataModel.cs +++ b/TUnit.Engine.SourceGenerator/Models/TestSourceDataModel.cs @@ -67,10 +67,11 @@ public override int GetHashCode() public required string? TestExecutor { get; init; } public required string? ParallelLimit { get; init; } public required string[] AttributeTypes { get; init; } + public required ClassPropertiesContainer PropertyArguments { get; init; } public string MethodVariablesWithCancellationToken() { - var variableNames = MethodArguments.GenerateArgumentVariableNames(); + var variableNames = MethodArguments.VariableNames; if (HasTimeoutAttribute) { diff --git a/TUnit.TestProject/InheritedPropertySetterTests.cs b/TUnit.TestProject/InheritedPropertySetterTests.cs new file mode 100644 index 00000000000..a0c402f8983 --- /dev/null +++ b/TUnit.TestProject/InheritedPropertySetterTests.cs @@ -0,0 +1,4 @@ +namespace TUnit.TestProject; + +[InheritsTests] +public class InheritedPropertySetterTests : PropertySetterTests; \ No newline at end of file diff --git a/TUnit.TestProject/PropertySetterTests.cs b/TUnit.TestProject/PropertySetterTests.cs new file mode 100644 index 00000000000..a2b02655ec1 --- /dev/null +++ b/TUnit.TestProject/PropertySetterTests.cs @@ -0,0 +1,37 @@ +namespace TUnit.TestProject; + +public class PropertySetterTests +{ + [Arguments("1")] + public required string Property1 { get; init; } + + [MethodDataSource(nameof(MethodData))] + public required string Property2 { get; init; } + + [ClassDataSource] + public required InnerModel Property3 { get; init; } + + [ClassDataSource(Shared = SharedType.Globally)] + public required InnerModel Property4 { get; init; } + + [ClassDataSource(Shared = SharedType.ForClass)] + public required InnerModel Property5 { get; init; } + + [ClassDataSource(Shared = SharedType.Keyed, Key = "Key")] + public required InnerModel Property6 { get; init; } + + [DataSourceGeneratorTests.AutoFixtureGenerator] + public required string Property7 { get; init; } + + [Test] + public void Test() + { + Console.WriteLine(Property7); + } + + public class InnerModel + { + } + + public static string MethodData() => "2"; +} \ No newline at end of file diff --git a/docs/docs/tutorial-extras/property-injection.md b/docs/docs/tutorial-extras/property-injection.md new file mode 100644 index 00000000000..28c200b297e --- /dev/null +++ b/docs/docs/tutorial-extras/property-injection.md @@ -0,0 +1,54 @@ +--- +sidebar_position: 13 +--- + +# Property Injection + +TUnit makes it easy to initialise some properties on your test class instead of passing them into the constructor. + +Your properties must be marked with the `required` keyword and then simply place a data attribute on it. +The required keyword keeps your code clean and correct. If a property isn't passed in, you'll get a compiler warning, so you know something has gone wrong. It also gets rid of any pesky nullability warnings. + +Supported attributes for properties are: +- Argument +- MethodDataSource +- ClassDataSource +- DataSourceGeneratorAttribute (though limited to the first item returned) + +This can help simplify base classes with common behaviour and avoid having to write boilerplate constructors everywhere. + +```csharp +using TUnit.Core; + +namespace MyTestProject; + +public class PropertySetterTests +{ + [Arguments("1")] + public required string Property1 { get; init; } + + [MethodDataSource(nameof(MethodData))] + public required string Property2 { get; init; } + + [ClassDataSource] + public required InnerModel Property3 { get; init; } + + [ClassDataSource(Shared = SharedType.Globally)] + public required InnerModel Property4 { get; init; } + + [ClassDataSource(Shared = SharedType.ForClass)] + public required InnerModel Property5 { get; init; } + + [ClassDataSource(Shared = SharedType.Keyed, Key = "Key")] + public required InnerModel Property6 { get; init; } + + [DataSourceGeneratorTests.AutoFixtureGenerator] + public required string Property7 { get; init; } + + [Test] + public void Test() + { + Console.WriteLine(Property7); + } +} +```