Skip to content

Commit

Permalink
[MatrixExclusion(...)] attribute for excluding specific combination…
Browse files Browse the repository at this point in the history
…s of test data (#1793)
  • Loading branch information
thomhurst authored Feb 8, 2025
1 parent 9d0e909 commit 09e997d
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 2 deletions.
162 changes: 162 additions & 0 deletions TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1730,4 +1730,166 @@ file partial class MatrixTests : global::TUnit.Core.Interfaces.SourceGenerator.I
}
}


// <auto-generated/>
#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<SourceGeneratedTestNode> CollectTests(string sessionId)
{
return Tests0(sessionId);
}
private global::System.Collections.Generic.List<SourceGeneratedTestNode> Tests0(string sessionId)
{
global::System.Collections.Generic.List<SourceGeneratedTestNode> 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<int>
{
Name = "item",
Attributes =
[
new global::TUnit.Core.MatrixMethodAttribute<global::TUnit.TestProject.MatrixTests>("EnumerableMethod")
],
},
new global::TUnit.Core.SourceGeneratedParameterInformation<int>
{
Name = "item2",
Attributes =
[
new global::TUnit.Core.MatrixMethodAttribute<global::TUnit.TestProject.MatrixTests>("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<int>
{
Name = "item",
Attributes =
[
new global::TUnit.Core.MatrixMethodAttribute<global::TUnit.TestProject.MatrixTests>("EnumerableMethod")
],
},
new global::TUnit.Core.SourceGeneratedParameterInformation<int>
{
Name = "item2",
Attributes =
[
new global::TUnit.Core.MatrixMethodAttribute<global::TUnit.TestProject.MatrixTests>("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<int>(methodArgGeneratedData[0]);
int methodArg1 = global::TUnit.Core.Helpers.CastHelper.Cast<int>(methodArgGeneratedData[1]);
var resettableClassFactoryDelegate = () => new ResettableLazy<global::TUnit.TestProject.MatrixTests>(() =>
new global::TUnit.TestProject.MatrixTests()
, sessionId, testBuilderContext);

var resettableClassFactory = resettableClassFactoryDelegate();

nodes.Add(new TestMetadata<global::TUnit.TestProject.MatrixTests>
{
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;
}
}

]
2 changes: 1 addition & 1 deletion TUnit.Core.SourceGenerator.Tests/MatrixTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
19 changes: 18 additions & 1 deletion TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace TUnit.Core;
using TUnit.Core.Enums;

namespace TUnit.Core;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class MatrixDataSourceAttribute : NonTypedDataSourceGeneratorAttribute
Expand All @@ -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<MatrixExclusionAttribute>()
.Select(x => x.Objects)
.ToArray();
}

private IReadOnlyList<object?> GetAllArguments(SourceGeneratedParameterInformation sourceGeneratedParameterInformation)
{
var matrixAttribute = sourceGeneratedParameterInformation.Attributes.OfType<MatrixAttribute>().FirstOrDefault();
Expand Down
7 changes: 7 additions & 0 deletions TUnit.Core/Attributes/TestData/MatrixExclusionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TUnit.Core;

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class MatrixExclusionAttribute(params object?[]? objects) : TUnitAttribute
{
public object?[] Objects { get; } = objects ?? [ null ];
}
12 changes: 12 additions & 0 deletions TUnit.TestProject/MatrixTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ public async Task Method4(
{
await Task.CompletedTask;
}

[Test]
[MatrixDataSource]
[MatrixExclusion(1, 1)]
[MatrixExclusion(2, 2)]
[MatrixExclusion(3, 3)]
public async Task Exclusion(
[MatrixMethod<MatrixTests>(nameof(EnumerableMethod))] int item,
[MatrixMethod<MatrixTests>(nameof(EnumerableMethod))] int item2)
{
await Task.CompletedTask;
}

public enum CountToTenEnum
{
Expand Down
51 changes: 51 additions & 0 deletions docs/docs/tutorial-basics/matrix-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,54 @@ public class MyTestClass
}
}
```

## Matrix Exclusions

You can also add a `[MatrixExclusion(...)]` attribute to your tests.
This works similar to the `[Arguments(...)]` attribute, and if objects match a generated matrix test case, it'll be ignored.

This helps you exclude specific one-off scenarios without having to complicate your tests with `if` conditions.

```csharp
using TUnit.Assertions;
using TUnit.Assertions.Extensions;
using TUnit.Assertions.Extensions.Is;
using TUnit.Core;

namespace MyTestProject;

public class MyTestClass
{
[Test]
[MatrixDataSource]
[MatrixExclusion(1, 1)]
[MatrixExclusion(2, 2)]
[MatrixExclusion(3, 3)]
public async Task MyTest(
[MatrixRange<int>(1, 3)] int value1,
[MatrixRange<int>(1, 3)] int value2
)
{
...
}
}
```

Whereas the above Matrix would usually generate:
- 1, 1
- 1, 2
- 1, 3
- 2, 1
- 2, 2
- 2, 3
- 3, 1
- 3, 2
- 3, 3

Because of the exclusion attributes, it'll only generate:
- 1, 2
- 1, 3
- 2, 1
- 2, 3
- 3, 1
- 3, 2

0 comments on commit 09e997d

Please sign in to comment.