Skip to content

Commit 09e997d

Browse files
authored
[MatrixExclusion(...)] attribute for excluding specific combinations of test data (#1793)
1 parent 9d0e909 commit 09e997d

File tree

6 files changed

+251
-2
lines changed

6 files changed

+251
-2
lines changed

TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,4 +1730,166 @@ file partial class MatrixTests : global::TUnit.Core.Interfaces.SourceGenerator.I
17301730
}
17311731
}
17321732

1733+
1734+
// <auto-generated/>
1735+
#pragma warning disable
1736+
using global::System.Linq;
1737+
using global::System.Reflection;
1738+
using global::TUnit.Core;
1739+
using global::TUnit.Core.Extensions;
1740+
1741+
namespace TUnit.SourceGenerated;
1742+
1743+
[global::System.Diagnostics.StackTraceHidden]
1744+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
1745+
file partial class MatrixTests : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource
1746+
{
1747+
[global::System.Runtime.CompilerServices.ModuleInitializer]
1748+
public static void Initialise()
1749+
{
1750+
global::TUnit.Core.SourceRegistrar.Register(new MatrixTests());
1751+
}
1752+
public global::System.Collections.Generic.IReadOnlyList<SourceGeneratedTestNode> CollectTests(string sessionId)
1753+
{
1754+
return Tests0(sessionId);
1755+
}
1756+
private global::System.Collections.Generic.List<SourceGeneratedTestNode> Tests0(string sessionId)
1757+
{
1758+
global::System.Collections.Generic.List<SourceGeneratedTestNode> nodes = [];
1759+
var classDataIndex = 0;
1760+
var testMethodDataIndex = 0;
1761+
try
1762+
{
1763+
var testInformation = new global::TUnit.Core.SourceGeneratedMethodInformation
1764+
{
1765+
Type = typeof(global::TUnit.TestProject.MatrixTests),
1766+
Name = "Exclusion",
1767+
GenericTypeCount = 0,
1768+
ReturnType = typeof(global::System.Threading.Tasks.Task),
1769+
Attributes =
1770+
[
1771+
new global::TUnit.Core.TestAttribute(),
1772+
new global::TUnit.Core.MatrixDataSourceAttribute(),
1773+
new global::TUnit.Core.MatrixExclusionAttribute(1, 1),
1774+
new global::TUnit.Core.MatrixExclusionAttribute(2, 2),
1775+
new global::TUnit.Core.MatrixExclusionAttribute(3, 3)
1776+
],
1777+
Parameters =
1778+
[
1779+
new global::TUnit.Core.SourceGeneratedParameterInformation<int>
1780+
{
1781+
Name = "item",
1782+
Attributes =
1783+
[
1784+
new global::TUnit.Core.MatrixMethodAttribute<global::TUnit.TestProject.MatrixTests>("EnumerableMethod")
1785+
],
1786+
},
1787+
new global::TUnit.Core.SourceGeneratedParameterInformation<int>
1788+
{
1789+
Name = "item2",
1790+
Attributes =
1791+
[
1792+
new global::TUnit.Core.MatrixMethodAttribute<global::TUnit.TestProject.MatrixTests>("EnumerableMethod")
1793+
],
1794+
},
1795+
],
1796+
Class = global::TUnit.Core.SourceGeneratedClassInformation.GetOrAdd("global::TUnit.TestProject.MatrixTests", () => new global::TUnit.Core.SourceGeneratedClassInformation
1797+
{
1798+
Type = typeof(global::TUnit.TestProject.MatrixTests),
1799+
Assembly = global::TUnit.Core.SourceGeneratedAssemblyInformation.GetOrAdd("MatrixTests", () => new global::TUnit.Core.SourceGeneratedAssemblyInformation
1800+
{
1801+
Name = "MatrixTests",
1802+
Attributes = [],
1803+
}),
1804+
Name = "MatrixTests",
1805+
Namespace = "TUnit.TestProject",
1806+
Attributes = [],
1807+
Parameters = [],
1808+
Properties = [],
1809+
}),
1810+
};
1811+
1812+
var testBuilderContext = new global::TUnit.Core.TestBuilderContext();
1813+
var testBuilderContextAccessor = new global::TUnit.Core.TestBuilderContextAccessor(testBuilderContext);
1814+
var methodArgDataGeneratorMetadata = new DataGeneratorMetadata
1815+
{
1816+
Type = global::TUnit.Core.Enums.DataGeneratorType.TestParameters,
1817+
TestBuilderContext = testBuilderContextAccessor,
1818+
TestInformation = testInformation,
1819+
MembersToGenerate =
1820+
[
1821+
new global::TUnit.Core.SourceGeneratedParameterInformation<int>
1822+
{
1823+
Name = "item",
1824+
Attributes =
1825+
[
1826+
new global::TUnit.Core.MatrixMethodAttribute<global::TUnit.TestProject.MatrixTests>("EnumerableMethod")
1827+
],
1828+
},
1829+
new global::TUnit.Core.SourceGeneratedParameterInformation<int>
1830+
{
1831+
Name = "item2",
1832+
Attributes =
1833+
[
1834+
new global::TUnit.Core.MatrixMethodAttribute<global::TUnit.TestProject.MatrixTests>("EnumerableMethod")
1835+
],
1836+
},
1837+
],
1838+
TestSessionId = sessionId,
1839+
};
1840+
var methodDataAttribute = new global::TUnit.Core.MatrixDataSourceAttribute();
1841+
1842+
var methodArgGeneratedDataArray = methodDataAttribute.GenerateDataSources(methodArgDataGeneratorMetadata);
1843+
1844+
foreach (var methodArgGeneratedDataAccessor in methodArgGeneratedDataArray)
1845+
{
1846+
testMethodDataIndex++;
1847+
1848+
var methodArgGeneratedData = methodArgGeneratedDataAccessor();
1849+
int methodArg = global::TUnit.Core.Helpers.CastHelper.Cast<int>(methodArgGeneratedData[0]);
1850+
int methodArg1 = global::TUnit.Core.Helpers.CastHelper.Cast<int>(methodArgGeneratedData[1]);
1851+
var resettableClassFactoryDelegate = () => new ResettableLazy<global::TUnit.TestProject.MatrixTests>(() =>
1852+
new global::TUnit.TestProject.MatrixTests()
1853+
, sessionId, testBuilderContext);
1854+
1855+
var resettableClassFactory = resettableClassFactoryDelegate();
1856+
1857+
nodes.Add(new TestMetadata<global::TUnit.TestProject.MatrixTests>
1858+
{
1859+
TestId = $"global::TUnit.Core.MatrixDataSourceAttribute:{testMethodDataIndex}:TL-GAC0:TUnit.TestProject.MatrixTests.Exclusion(int,int):0",
1860+
TestClassArguments = [],
1861+
TestMethodArguments = [methodArg, methodArg1],
1862+
TestClassProperties = [],
1863+
CurrentRepeatAttempt = 0,
1864+
RepeatLimit = 0,
1865+
ResettableClassFactory = resettableClassFactory,
1866+
TestMethodFactory = (classInstance, cancellationToken) => AsyncConvert.Convert(() => classInstance.Exclusion(methodArg, methodArg1)),
1867+
TestFilePath = @"",
1868+
TestLineNumber = 123,
1869+
TestMethod = testInformation,
1870+
TestBuilderContext = testBuilderContext,
1871+
});
1872+
resettableClassFactory = resettableClassFactoryDelegate();
1873+
testBuilderContext = new();
1874+
testBuilderContextAccessor.Current = testBuilderContext;
1875+
}
1876+
}
1877+
catch (global::System.Exception exception)
1878+
{
1879+
nodes.Add(new global::TUnit.Core.FailedInitializationTest
1880+
{
1881+
TestId = $"global::TUnit.Core.MatrixDataSourceAttribute:{testMethodDataIndex}:TL-GAC0:TUnit.TestProject.MatrixTests.Exclusion(int,int):0",
1882+
TestClass = typeof(global::TUnit.TestProject.MatrixTests),
1883+
ReturnType = typeof(global::System.Threading.Tasks.Task),
1884+
ParameterTypeFullNames = [typeof(int), typeof(int)],
1885+
TestName = "Exclusion",
1886+
TestFilePath = @"",
1887+
TestLineNumber = 123,
1888+
Exception = exception,
1889+
});
1890+
}
1891+
return nodes;
1892+
}
1893+
}
1894+
17331895
]

TUnit.Core.SourceGenerator.Tests/MatrixTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName,
2020
},
2121
async generatedFiles =>
2222
{
23-
await Assert.That(generatedFiles.Length).IsEqualTo(11);
23+
await Assert.That(generatedFiles.Length).IsEqualTo(12);
2424
});
2525
}

TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace TUnit.Core;
1+
using TUnit.Core.Enums;
2+
3+
namespace TUnit.Core;
24

35
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
46
public sealed class MatrixDataSourceAttribute : NonTypedDataSourceGeneratorAttribute
@@ -16,12 +18,27 @@ public sealed class MatrixDataSourceAttribute : NonTypedDataSourceGeneratorAttri
1618
throw new Exception("[MatrixDataSource] only supports parameterised tests");
1719
}
1820

21+
var exclusions = GetExclusions(dataGeneratorMetadata.Type == DataGeneratorType.TestParameters
22+
? dataGeneratorMetadata.TestInformation.Attributes : dataGeneratorMetadata.TestInformation.Class.Attributes);
23+
1924
foreach (var row in GetMatrixValues(parameterInformation.Select(GetAllArguments)))
2025
{
26+
if (exclusions.Any(e => e.SequenceEqual(row)))
27+
{
28+
continue;
29+
}
30+
2131
yield return () => row.ToArray();
2232
}
2333
}
2434

35+
private object?[][] GetExclusions(Attribute[] attributes)
36+
{
37+
return attributes.OfType<MatrixExclusionAttribute>()
38+
.Select(x => x.Objects)
39+
.ToArray();
40+
}
41+
2542
private IReadOnlyList<object?> GetAllArguments(SourceGeneratedParameterInformation sourceGeneratedParameterInformation)
2643
{
2744
var matrixAttribute = sourceGeneratedParameterInformation.Attributes.OfType<MatrixAttribute>().FirstOrDefault();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace TUnit.Core;
2+
3+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
4+
public class MatrixExclusionAttribute(params object?[]? objects) : TUnitAttribute
5+
{
6+
public object?[] Objects { get; } = objects ?? [ null ];
7+
}

TUnit.TestProject/MatrixTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ public async Task Method4(
119119
{
120120
await Task.CompletedTask;
121121
}
122+
123+
[Test]
124+
[MatrixDataSource]
125+
[MatrixExclusion(1, 1)]
126+
[MatrixExclusion(2, 2)]
127+
[MatrixExclusion(3, 3)]
128+
public async Task Exclusion(
129+
[MatrixMethod<MatrixTests>(nameof(EnumerableMethod))] int item,
130+
[MatrixMethod<MatrixTests>(nameof(EnumerableMethod))] int item2)
131+
{
132+
await Task.CompletedTask;
133+
}
122134

123135
public enum CountToTenEnum
124136
{

docs/docs/tutorial-basics/matrix-tests.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,54 @@ public class MyTestClass
124124
}
125125
}
126126
```
127+
128+
## Matrix Exclusions
129+
130+
You can also add a `[MatrixExclusion(...)]` attribute to your tests.
131+
This works similar to the `[Arguments(...)]` attribute, and if objects match a generated matrix test case, it'll be ignored.
132+
133+
This helps you exclude specific one-off scenarios without having to complicate your tests with `if` conditions.
134+
135+
```csharp
136+
using TUnit.Assertions;
137+
using TUnit.Assertions.Extensions;
138+
using TUnit.Assertions.Extensions.Is;
139+
using TUnit.Core;
140+
141+
namespace MyTestProject;
142+
143+
public class MyTestClass
144+
{
145+
[Test]
146+
[MatrixDataSource]
147+
[MatrixExclusion(1, 1)]
148+
[MatrixExclusion(2, 2)]
149+
[MatrixExclusion(3, 3)]
150+
public async Task MyTest(
151+
[MatrixRange<int>(1, 3)] int value1,
152+
[MatrixRange<int>(1, 3)] int value2
153+
)
154+
{
155+
...
156+
}
157+
}
158+
```
159+
160+
Whereas the above Matrix would usually generate:
161+
- 1, 1
162+
- 1, 2
163+
- 1, 3
164+
- 2, 1
165+
- 2, 2
166+
- 2, 3
167+
- 3, 1
168+
- 3, 2
169+
- 3, 3
170+
171+
Because of the exclusion attributes, it'll only generate:
172+
- 1, 2
173+
- 1, 3
174+
- 2, 1
175+
- 2, 3
176+
- 3, 1
177+
- 3, 2

0 commit comments

Comments
 (0)