From 09e997d12afe542d2581dbc6c54be04f2217bd64 Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sat, 8 Feb 2025 14:57:46 +0000
Subject: [PATCH] `[MatrixExclusion(...)]` attribute for excluding specific
combinations of test data (#1793)
---
.../MatrixTests.Test.verified.txt | 162 ++++++++++++++++++
.../MatrixTests.cs | 2 +-
.../TestData/MatrixDataSourceAttribute.cs | 19 +-
.../TestData/MatrixExclusionAttribute.cs | 7 +
TUnit.TestProject/MatrixTests.cs | 12 ++
docs/docs/tutorial-basics/matrix-tests.md | 51 ++++++
6 files changed, 251 insertions(+), 2 deletions(-)
create mode 100644 TUnit.Core/Attributes/TestData/MatrixExclusionAttribute.cs
diff --git a/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt
index 3b518f8820..70dfd7ba62 100644
--- a/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt
+++ b/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt
@@ -1730,4 +1730,166 @@ file partial class MatrixTests : global::TUnit.Core.Interfaces.SourceGenerator.I
}
}
+
+//
+#pragma warning disable
+using global::System.Linq;
+using global::System.Reflection;
+using global::TUnit.Core;
+using global::TUnit.Core.Extensions;
+
+namespace TUnit.SourceGenerated;
+
+[global::System.Diagnostics.StackTraceHidden]
+[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+file partial class MatrixTests : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource
+{
+ [global::System.Runtime.CompilerServices.ModuleInitializer]
+ public static void Initialise()
+ {
+ global::TUnit.Core.SourceRegistrar.Register(new MatrixTests());
+ }
+ public global::System.Collections.Generic.IReadOnlyList CollectTests(string sessionId)
+ {
+ return Tests0(sessionId);
+ }
+ private global::System.Collections.Generic.List Tests0(string sessionId)
+ {
+ global::System.Collections.Generic.List nodes = [];
+ var classDataIndex = 0;
+ var testMethodDataIndex = 0;
+ try
+ {
+ var testInformation = new global::TUnit.Core.SourceGeneratedMethodInformation
+ {
+ Type = typeof(global::TUnit.TestProject.MatrixTests),
+ Name = "Exclusion",
+ GenericTypeCount = 0,
+ ReturnType = typeof(global::System.Threading.Tasks.Task),
+ Attributes =
+ [
+ new global::TUnit.Core.TestAttribute(),
+ new global::TUnit.Core.MatrixDataSourceAttribute(),
+ new global::TUnit.Core.MatrixExclusionAttribute(1, 1),
+ new global::TUnit.Core.MatrixExclusionAttribute(2, 2),
+ new global::TUnit.Core.MatrixExclusionAttribute(3, 3)
+ ],
+ Parameters =
+ [
+ new global::TUnit.Core.SourceGeneratedParameterInformation
+ {
+ Name = "item",
+ Attributes =
+ [
+ new global::TUnit.Core.MatrixMethodAttribute("EnumerableMethod")
+ ],
+ },
+ new global::TUnit.Core.SourceGeneratedParameterInformation
+ {
+ Name = "item2",
+ Attributes =
+ [
+ new global::TUnit.Core.MatrixMethodAttribute("EnumerableMethod")
+ ],
+ },
+ ],
+ Class = global::TUnit.Core.SourceGeneratedClassInformation.GetOrAdd("global::TUnit.TestProject.MatrixTests", () => new global::TUnit.Core.SourceGeneratedClassInformation
+ {
+ Type = typeof(global::TUnit.TestProject.MatrixTests),
+ Assembly = global::TUnit.Core.SourceGeneratedAssemblyInformation.GetOrAdd("MatrixTests", () => new global::TUnit.Core.SourceGeneratedAssemblyInformation
+ {
+ Name = "MatrixTests",
+ Attributes = [],
+ }),
+ Name = "MatrixTests",
+ Namespace = "TUnit.TestProject",
+ Attributes = [],
+ Parameters = [],
+ Properties = [],
+ }),
+};
+
+ var testBuilderContext = new global::TUnit.Core.TestBuilderContext();
+ var testBuilderContextAccessor = new global::TUnit.Core.TestBuilderContextAccessor(testBuilderContext);
+ var methodArgDataGeneratorMetadata = new DataGeneratorMetadata
+ {
+ Type = global::TUnit.Core.Enums.DataGeneratorType.TestParameters,
+ TestBuilderContext = testBuilderContextAccessor,
+ TestInformation = testInformation,
+ MembersToGenerate =
+ [
+ new global::TUnit.Core.SourceGeneratedParameterInformation
+ {
+ Name = "item",
+ Attributes =
+ [
+ new global::TUnit.Core.MatrixMethodAttribute("EnumerableMethod")
+ ],
+ },
+ new global::TUnit.Core.SourceGeneratedParameterInformation
+ {
+ Name = "item2",
+ Attributes =
+ [
+ new global::TUnit.Core.MatrixMethodAttribute("EnumerableMethod")
+ ],
+ },
+ ],
+ TestSessionId = sessionId,
+ };
+ var methodDataAttribute = new global::TUnit.Core.MatrixDataSourceAttribute();
+
+ var methodArgGeneratedDataArray = methodDataAttribute.GenerateDataSources(methodArgDataGeneratorMetadata);
+
+ foreach (var methodArgGeneratedDataAccessor in methodArgGeneratedDataArray)
+ {
+ testMethodDataIndex++;
+
+ var methodArgGeneratedData = methodArgGeneratedDataAccessor();
+ int methodArg = global::TUnit.Core.Helpers.CastHelper.Cast(methodArgGeneratedData[0]);
+ int methodArg1 = global::TUnit.Core.Helpers.CastHelper.Cast(methodArgGeneratedData[1]);
+ var resettableClassFactoryDelegate = () => new ResettableLazy(() =>
+ new global::TUnit.TestProject.MatrixTests()
+ , sessionId, testBuilderContext);
+
+ var resettableClassFactory = resettableClassFactoryDelegate();
+
+ nodes.Add(new TestMetadata
+ {
+ TestId = $"global::TUnit.Core.MatrixDataSourceAttribute:{testMethodDataIndex}:TL-GAC0:TUnit.TestProject.MatrixTests.Exclusion(int,int):0",
+ TestClassArguments = [],
+ TestMethodArguments = [methodArg, methodArg1],
+ TestClassProperties = [],
+ CurrentRepeatAttempt = 0,
+ RepeatLimit = 0,
+ ResettableClassFactory = resettableClassFactory,
+ TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.Exclusion(methodArg, methodArg1)),
+ TestFilePath = @"",
+ TestLineNumber = 123,
+ TestMethod = testInformation,
+ TestBuilderContext = testBuilderContext,
+ });
+ resettableClassFactory = resettableClassFactoryDelegate();
+ testBuilderContext = new();
+ testBuilderContextAccessor.Current = testBuilderContext;
+ }
+ }
+ catch (global::System.Exception exception)
+ {
+ nodes.Add(new global::TUnit.Core.FailedInitializationTest
+ {
+ TestId = $"global::TUnit.Core.MatrixDataSourceAttribute:{testMethodDataIndex}:TL-GAC0:TUnit.TestProject.MatrixTests.Exclusion(int,int):0",
+ TestClass = typeof(global::TUnit.TestProject.MatrixTests),
+ ReturnType = typeof(global::System.Threading.Tasks.Task),
+ ParameterTypeFullNames = [typeof(int), typeof(int)],
+ TestName = "Exclusion",
+ TestFilePath = @"",
+ TestLineNumber = 123,
+ Exception = exception,
+ });
+ }
+ return nodes;
+ }
+ }
+
]
\ No newline at end of file
diff --git a/TUnit.Core.SourceGenerator.Tests/MatrixTests.cs b/TUnit.Core.SourceGenerator.Tests/MatrixTests.cs
index 4e9bff3c5e..b359bd07ef 100644
--- a/TUnit.Core.SourceGenerator.Tests/MatrixTests.cs
+++ b/TUnit.Core.SourceGenerator.Tests/MatrixTests.cs
@@ -20,6 +20,6 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName,
},
async generatedFiles =>
{
- await Assert.That(generatedFiles.Length).IsEqualTo(11);
+ await Assert.That(generatedFiles.Length).IsEqualTo(12);
});
}
\ No newline at end of file
diff --git a/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs
index 3c4bcd118b..6869b5a796 100644
--- a/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs
+++ b/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs
@@ -1,4 +1,6 @@
-namespace TUnit.Core;
+using TUnit.Core.Enums;
+
+namespace TUnit.Core;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class MatrixDataSourceAttribute : NonTypedDataSourceGeneratorAttribute
@@ -16,12 +18,27 @@ public sealed class MatrixDataSourceAttribute : NonTypedDataSourceGeneratorAttri
throw new Exception("[MatrixDataSource] only supports parameterised tests");
}
+ var exclusions = GetExclusions(dataGeneratorMetadata.Type == DataGeneratorType.TestParameters
+ ? dataGeneratorMetadata.TestInformation.Attributes : dataGeneratorMetadata.TestInformation.Class.Attributes);
+
foreach (var row in GetMatrixValues(parameterInformation.Select(GetAllArguments)))
{
+ if (exclusions.Any(e => e.SequenceEqual(row)))
+ {
+ continue;
+ }
+
yield return () => row.ToArray();
}
}
+ private object?[][] GetExclusions(Attribute[] attributes)
+ {
+ return attributes.OfType()
+ .Select(x => x.Objects)
+ .ToArray();
+ }
+
private IReadOnlyList