diff --git a/.gitignore b/.gitignore
index cc2b124..a3c91fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -358,3 +358,6 @@ MigrationBackup/
# Analysis results
*.sarif
+
+# JetBrains Rider
+.idea
\ No newline at end of file
diff --git a/src/Xunit.Combinatorial/CombinatorialClassDataAttribute.cs b/src/Xunit.Combinatorial/CombinatorialClassDataAttribute.cs
new file mode 100644
index 0000000..ba98344
--- /dev/null
+++ b/src/Xunit.Combinatorial/CombinatorialClassDataAttribute.cs
@@ -0,0 +1,80 @@
+// Copyright (c) Andrew Arnott. All rights reserved.
+// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
+
+using System.Collections;
+using System.Globalization;
+using System.Reflection;
+
+namespace Xunit;
+
+///
+/// Specifies a class that provides the values for a combinatorial test.
+///
+public class CombinatorialClassDataAttribute : Attribute, ICombinatorialValuesProvider
+{
+ private readonly object?[] values;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type of the class that provides the values for a combinatorial test.
+ /// The arguments to pass to the constructor of .
+ public CombinatorialClassDataAttribute(Type valuesSourceType, params object[]? arguments)
+ {
+ this.values = GetValues(valuesSourceType, arguments);
+ }
+
+ ///
+ public object?[] GetValues(ParameterInfo parameter)
+ {
+ return this.values;
+ }
+
+ private static object?[] GetValues(Type valuesSourceType, object[]? args)
+ {
+ Requires.NotNull(valuesSourceType, nameof(valuesSourceType));
+
+ EnsureValidValuesSourceType(valuesSourceType);
+
+ IEnumerable? values;
+
+ try
+ {
+ values = (IEnumerable)Activator.CreateInstance(
+ valuesSourceType,
+ BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding,
+ null,
+ args,
+ CultureInfo.InvariantCulture);
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ $"Failed to create an instance of {valuesSourceType}. " +
+ $"Please make sure the type has a public constructor and the arguments match.",
+ ex);
+ }
+
+ if (TheoryDataHelper.TryGetTheoryDataValues(values, out object?[]? data))
+ {
+ return data;
+ }
+
+ return values.Cast().SelectMany(rows => rows).ToArray();
+ }
+
+ private static void EnsureValidValuesSourceType(Type valuesSourceType)
+ {
+ if (typeof(IEnumerable).IsAssignableFrom(valuesSourceType))
+ {
+ return;
+ }
+
+ if (TheoryDataHelper.IsTheoryDataType(valuesSourceType))
+ {
+ return;
+ }
+
+ throw new InvalidOperationException($"The values source {valuesSourceType} must be assignable to {typeof(IEnumerable)}), {typeof(TheoryData<>)} or {typeof(TheoryDataBase<,>)}.");
+ }
+}
diff --git a/src/Xunit.Combinatorial/CombinatorialMemberDataAttribute.cs b/src/Xunit.Combinatorial/CombinatorialMemberDataAttribute.cs
index 668354d..221a77e 100644
--- a/src/Xunit.Combinatorial/CombinatorialMemberDataAttribute.cs
+++ b/src/Xunit.Combinatorial/CombinatorialMemberDataAttribute.cs
@@ -10,13 +10,14 @@ namespace Xunit;
/// Specifies which member should provide data for this parameter used for running the test method.
///
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
-public class CombinatorialMemberDataAttribute : Attribute
+public class CombinatorialMemberDataAttribute : Attribute, ICombinatorialValuesProvider
{
///
/// Initializes a new instance of the class.
///
/// The name of the public static member on the test class that will provide the test data.
/// The arguments for the member (only supported for methods; ignored for everything else).
+ /// Optional parameters on methods are not supported.
public CombinatorialMemberDataAttribute(string memberName, params object?[]? arguments)
{
this.MemberName = memberName ?? throw new ArgumentNullException(nameof(memberName));
@@ -64,8 +65,19 @@ public CombinatorialMemberDataAttribute(string memberName, params object?[]? arg
throw new ArgumentException($"Could not find public static member (property, field, or method) named '{this.MemberName}' on {type.FullName}{parameterText}.");
}
- var obj = (IEnumerable)accessor();
- return obj.Cast().ToArray();
+ var values = (IEnumerable)accessor();
+
+ if (values is IEnumerable untypedValues)
+ {
+ return untypedValues.SelectMany(rows => rows).ToArray();
+ }
+
+ if (TheoryDataHelper.TryGetTheoryDataValues(values, out object?[]? theoryDataValues))
+ {
+ return theoryDataValues;
+ }
+
+ return values.Cast().ToArray();
}
///
@@ -75,19 +87,10 @@ public CombinatorialMemberDataAttribute(string memberName, params object?[]? arg
/// The generic type argument for (one of) the interface)s) implemented by the .
private static TypeInfo? GetEnumeratedType(Type enumerableType)
{
- if (enumerableType.IsGenericType)
+ if (enumerableType.IsGenericType && enumerableType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
- if (enumerableType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
- {
- Type[] enumerableGenericTypeArgs = enumerableType.GetTypeInfo().GetGenericArguments();
- return enumerableGenericTypeArgs[0].GetTypeInfo();
- }
-
- if (enumerableType.GetGenericTypeDefinition() == typeof(TheoryData<>))
- {
- Type[] enumerableGenericTypeArgs = enumerableType.GetTypeInfo().GetGenericArguments();
- return enumerableGenericTypeArgs[0].GetTypeInfo();
- }
+ Type[] enumerableGenericTypeArgs = enumerableType.GetTypeInfo().GetGenericArguments();
+ return enumerableGenericTypeArgs[0].GetTypeInfo();
}
foreach (Type implementedInterface in enumerableType.GetTypeInfo().ImplementedInterfaces)
@@ -157,6 +160,11 @@ private bool ParameterTypesCompatible(ParameterInfo[] parameters, object?[]? arg
return false;
}
+ if (parameters.Length != arguments.Length)
+ {
+ return false;
+ }
+
for (int i = 0; i < parameters.Length; i++)
{
if (arguments[i] is object arg)
@@ -211,7 +219,13 @@ private bool ParameterTypesCompatible(ParameterInfo[] parameters, object?[]? arg
/// Throw when does not conform to requirements or does not produce values assignable to .
private void EnsureValidMemberDataType(Type enumerableType, Type declaringType, ParameterInfo parameterInfo)
{
+ if (typeof(IEnumerable).IsAssignableFrom(enumerableType))
+ {
+ return;
+ }
+
TypeInfo? enumeratedType = GetEnumeratedType(enumerableType);
+
if (enumeratedType is null)
{
throw new ArgumentException($"Member {this.MemberName} on {declaringType.FullName} must return a type that implements IEnumerable.");
@@ -229,6 +243,11 @@ private void EnsureValidMemberDataType(Type enumerableType, Type declaringType,
$"Member {this.MemberName} on {declaringType.FullName} returned an IEnumerable>, which is not supported.");
}
+ if (TheoryDataHelper.IsTheoryDataRowType(enumeratedType))
+ {
+ return;
+ }
+
if (!enumeratedType.IsAssignableFrom(parameterInfo.ParameterType.GetTypeInfo()))
{
throw new ArgumentException(
diff --git a/src/Xunit.Combinatorial/CombinatorialRandomDataAttribute.cs b/src/Xunit.Combinatorial/CombinatorialRandomDataAttribute.cs
index a5d7c0b..86cc085 100644
--- a/src/Xunit.Combinatorial/CombinatorialRandomDataAttribute.cs
+++ b/src/Xunit.Combinatorial/CombinatorialRandomDataAttribute.cs
@@ -1,7 +1,8 @@
-// Copyright (c) Andrew Arnott. All rights reserved.
+// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
using System.Globalization;
+using System.Reflection;
namespace Xunit;
@@ -9,7 +10,7 @@ namespace Xunit;
/// Specifies which range of values for this parameter should be used for running the test method.
///
[AttributeUsage(AttributeTargets.Parameter)]
-public class CombinatorialRandomDataAttribute : Attribute
+public class CombinatorialRandomDataAttribute : Attribute, ICombinatorialValuesProvider
{
///
/// Special seed value to create System.Random class without seed.
@@ -42,11 +43,11 @@ public class CombinatorialRandomDataAttribute : Attribute
/// The default value of allows for a new seed to be used each time.
public int Seed { get; set; } = NoSeed;
- ///
- /// Gets the values that should be passed to this parameter on the test method.
- ///
- /// An array of values.
- public object[] Values => this.values ??= this.GenerateValues();
+ ///
+ public object[] GetValues(ParameterInfo parameter)
+ {
+ return this.values ??= this.GenerateValues();
+ }
private object[] GenerateValues()
{
diff --git a/src/Xunit.Combinatorial/CombinatorialRangeAttribute.cs b/src/Xunit.Combinatorial/CombinatorialRangeAttribute.cs
index c2e6ea5..ff329ae 100644
--- a/src/Xunit.Combinatorial/CombinatorialRangeAttribute.cs
+++ b/src/Xunit.Combinatorial/CombinatorialRangeAttribute.cs
@@ -1,14 +1,18 @@
-// Copyright (c) Andrew Arnott. All rights reserved.
+// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
+using System.Reflection;
+
namespace Xunit;
///
/// Specifies which range of values for this parameter should be used for running the test method.
///
[AttributeUsage(AttributeTargets.Parameter)]
-public class CombinatorialRangeAttribute : Attribute
+public class CombinatorialRangeAttribute : Attribute, ICombinatorialValuesProvider
{
+ private readonly object[] values;
+
///
/// Initializes a new instance of the class.
///
@@ -30,7 +34,7 @@ public CombinatorialRangeAttribute(int from, int count)
values[i] = from + i;
}
- this.Values = values;
+ this.values = values;
}
///
@@ -75,7 +79,7 @@ public CombinatorialRangeAttribute(int from, int to, int step)
values[i] = from + (i * step);
}
- this.Values = values;
+ this.values = values;
}
///
@@ -99,7 +103,7 @@ public CombinatorialRangeAttribute(uint from, uint count)
values[i] = from + i;
}
- this.Values = values;
+ this.values = values;
}
///
@@ -140,12 +144,12 @@ public CombinatorialRangeAttribute(uint from, uint to, uint step)
}
}
- this.Values = values.Cast().ToArray();
+ this.values = values.Cast().ToArray();
}
- ///
- /// Gets the values that should be passed to this parameter on the test method.
- ///
- /// An array of values.
- public object[] Values { get; }
+ ///
+ public object[] GetValues(ParameterInfo parameter)
+ {
+ return this.values;
+ }
}
diff --git a/src/Xunit.Combinatorial/CombinatorialValuesAttribute.cs b/src/Xunit.Combinatorial/CombinatorialValuesAttribute.cs
index 889067a..7eb613a 100644
--- a/src/Xunit.Combinatorial/CombinatorialValuesAttribute.cs
+++ b/src/Xunit.Combinatorial/CombinatorialValuesAttribute.cs
@@ -1,14 +1,18 @@
-// Copyright (c) Andrew Arnott. All rights reserved.
+// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
+using System.Reflection;
+
namespace Xunit;
///
/// Specifies which values for this parameter should be used for running the test method.
///
[AttributeUsage(AttributeTargets.Parameter)]
-public class CombinatorialValuesAttribute : Attribute
+public class CombinatorialValuesAttribute : Attribute, ICombinatorialValuesProvider
{
+ private readonly object?[] values;
+
///
/// Initializes a new instance of the class.
///
@@ -17,12 +21,12 @@ public CombinatorialValuesAttribute(params object?[]? values)
{
// When values is `null`, it's because the user passed in `null` as the only value and C# interpreted it as a null array.
// Re-interpret that.
- this.Values = values ?? new object?[] { null };
+ this.values = values ?? new object?[] { null };
}
- ///
- /// Gets the values that should be passed to this parameter on the test method.
- ///
- /// An array of values.
- public object?[] Values { get; }
+ ///
+ public object?[] GetValues(ParameterInfo parameter)
+ {
+ return this.values;
+ }
}
diff --git a/src/Xunit.Combinatorial/ICombinatorialValuesProvider.cs b/src/Xunit.Combinatorial/ICombinatorialValuesProvider.cs
new file mode 100644
index 0000000..893dd73
--- /dev/null
+++ b/src/Xunit.Combinatorial/ICombinatorialValuesProvider.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Andrew Arnott. All rights reserved.
+// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
+
+using System.Reflection;
+
+namespace Xunit;
+
+///
+/// An interface that provides values for a parameter on a test method.
+///
+public interface ICombinatorialValuesProvider
+{
+ ///
+ /// Gets the values that should be passed to this parameter on the test method.
+ ///
+ /// The parameter to get values for.
+ /// An array of values.
+ object?[] GetValues(ParameterInfo parameter);
+}
diff --git a/src/Xunit.Combinatorial/TheoryDataHelper.cs b/src/Xunit.Combinatorial/TheoryDataHelper.cs
new file mode 100644
index 0000000..28e238a
--- /dev/null
+++ b/src/Xunit.Combinatorial/TheoryDataHelper.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Andrew Arnott. All rights reserved.
+// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
+
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Xunit;
+
+///
+/// Helper class for retrieving data from theory data instances and types.
+///
+internal static class TheoryDataHelper
+{
+ ///
+ /// Tries to get the data from a theory data instance, if it is of such a type.
+ ///
+ /// The potential TheoryData source instance.
+ /// The data as an object array.
+ /// if the instance was TheoryData, otherwise .
+ internal static bool TryGetTheoryDataValues(IEnumerable source, [NotNullWhen(true)] out object?[]? theoryDataValues)
+ {
+ if (IsTheoryDataType(source.GetType()))
+ {
+ theoryDataValues = source.Cast().SelectMany(row => row.GetData()).ToArray();
+ return true;
+ }
+
+ theoryDataValues = [];
+ return false;
+ }
+
+ ///
+ /// Checks if the source type is an implementation of an IEnumerable of .
+ ///
+ /// The type to check.
+ /// if the type is an implementation of IEnumerable of .
+ internal static bool IsTheoryDataType(Type sourceType)
+ {
+ return sourceType.GetInterfaces()
+ .Any(interfaceType => interfaceType.IsGenericType &&
+ interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>) &&
+ IsTheoryDataRowType(interfaceType.GetGenericArguments()[0]));
+ }
+
+ ///
+ /// Checks if the source type is a .
+ ///
+ /// The type to check.
+ /// if the type is a , otherwise .
+ internal static bool IsTheoryDataRowType(Type? sourceType)
+ {
+ return sourceType is not null && typeof(ITheoryDataRow).IsAssignableFrom(sourceType);
+ }
+}
diff --git a/src/Xunit.Combinatorial/ValuesUtilities.cs b/src/Xunit.Combinatorial/ValuesUtilities.cs
index 293c69d..82552e4 100644
--- a/src/Xunit.Combinatorial/ValuesUtilities.cs
+++ b/src/Xunit.Combinatorial/ValuesUtilities.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Andrew Arnott. All rights reserved.
+// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
using System.Diagnostics.CodeAnalysis;
@@ -19,36 +19,11 @@ internal static class ValuesUtilities
internal static IEnumerable GetValuesFor(ParameterInfo parameter)
{
Requires.NotNull(parameter, nameof(parameter));
- {
- CombinatorialValuesAttribute? attribute = parameter.GetCustomAttribute();
- if (attribute is not null)
- {
- return attribute.Values;
- }
- }
-
- {
- CombinatorialRangeAttribute? attribute = parameter.GetCustomAttribute();
- if (attribute is not null)
- {
- return attribute.Values;
- }
- }
-
- {
- CombinatorialRandomDataAttribute? attribute = parameter.GetCustomAttribute();
- if (attribute is not null)
- {
- return attribute.Values;
- }
- }
+ ICombinatorialValuesProvider? valuesSource = parameter.GetCustomAttributes().OfType().SingleOrDefault();
+ if (valuesSource is not null)
{
- CombinatorialMemberDataAttribute? attribute = parameter.GetCustomAttribute();
- if (attribute is not null)
- {
- return attribute.GetValues(parameter);
- }
+ return valuesSource.GetValues(parameter);
}
return GetValuesFor(parameter.ParameterType);
diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialClassDataAttributeTests.cs b/test/Xunit.Combinatorial.Tests/CombinatorialClassDataAttributeTests.cs
new file mode 100644
index 0000000..f692ee9
--- /dev/null
+++ b/test/Xunit.Combinatorial.Tests/CombinatorialClassDataAttributeTests.cs
@@ -0,0 +1,138 @@
+// Copyright (c) Andrew Arnott. All rights reserved.
+// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
+
+using System.Collections;
+using Xunit;
+
+public class CombinatorialClassDataAttributeTests(ITestOutputHelper logger)
+{
+ private static readonly object?[] ExpectedItems =
+ {
+ new ValueSourceItem(1, "Foo"), new ValueSourceItem(2, "Bar"), new ValueSourceItem(3, "Baz"),
+ };
+
+ [Fact]
+ public void Ctor_IncompatibleType_Throws()
+ {
+ Action ctor = () => new CombinatorialClassDataAttribute(typeof(object));
+ InvalidOperationException exception = Assert.Throws(ctor);
+ Assert.Equal($"The values source {typeof(object)} must be assignable to {typeof(IEnumerable)}), {typeof(TheoryData<>)} or {typeof(TheoryDataBase<,>)}.", exception.Message);
+ }
+
+ [Fact]
+ public void Ctor_TheoryData_MissingClassDataArguments_Throws()
+ {
+ Action ctor = () => new CombinatorialClassDataAttribute(typeof(MyTheoryDataValuesSourceWithParameters));
+ InvalidOperationException exception = Assert.Throws(ctor);
+ logger.WriteLine(exception.Message);
+ Assert.Equal(
+ $"Failed to create an instance of {typeof(MyTheoryDataValuesSourceWithParameters)}. " +
+ "Please make sure the type has a public constructor and the arguments match.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void Ctor_TheoryData_WithArgument_ReturnsCorrectData()
+ {
+ var argumentValue = 10;
+ var attribute =
+ new CombinatorialClassDataAttribute(typeof(MyTheoryDataValuesSourceWithParameters), argumentValue);
+ var values = attribute.GetValues(null!);
+ IEnumerable expected = Enumerable.Range(0, argumentValue);
+ Assert.Equal(values, expected);
+ }
+
+ [Fact]
+ public void Ctor_TheoryData_SetsProperty()
+ {
+ var attribute = new CombinatorialClassDataAttribute(typeof(MyTheoryDataValuesSource));
+ Assert.Equal(ExpectedItems, attribute.GetValues(null!));
+ }
+
+ [Fact]
+ public void Ctor_IEnumerable_SetsProperty()
+ {
+ var attribute = new CombinatorialClassDataAttribute(typeof(MyEnumerableDataValuesSource));
+ Assert.Equal(ExpectedItems, attribute.GetValues(null!));
+ }
+
+#if NETSTANDARD2_0_OR_GREATER
+ [Fact]
+ public void Generic_Ctor_TheoryData_SetsProperty()
+ {
+ var attribute = new CombinatorialClassDataAttribute();
+ Assert.Equal(expectedItems, attribute.GetValues(null!));
+ }
+
+ [Fact]
+ public void Generic_Ctor_IEnumerable_SetsProperty()
+ {
+ var attribute = new CombinatorialClassDataAttribute();
+ Assert.Equal(expectedItems, attribute.GetValues(null!));
+ }
+#endif
+
+ private class MyTheoryDataValuesSource : TheoryData
+ {
+ public MyTheoryDataValuesSource()
+ {
+ foreach (ValueSourceItem? item in ExpectedItems.Cast())
+ {
+ this.Add(item);
+ }
+ }
+ }
+
+ private class MyTheoryDataValuesSourceWithParameters : TheoryData
+ {
+ public MyTheoryDataValuesSourceWithParameters(int value)
+ {
+ for (int i = 0; i < value; i++)
+ {
+ this.Add(i);
+ }
+ }
+ }
+
+ private class MyEnumerableDataValuesSource : IEnumerable
+ {
+ public IEnumerator GetEnumerator()
+ {
+ return ExpectedItems
+ .Cast()
+ .Select(item => new object?[] { item })
+ .GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+ }
+
+ private class ValueSourceItem
+ {
+ public ValueSourceItem(int number, string text)
+ {
+ this.Number = number;
+ this.Text = text;
+ }
+
+ public int Number { get; }
+
+ public string Text { get; }
+
+ public override bool Equals(object? obj)
+ {
+ // Implemented to simplify comparison in tests
+ return obj is ValueSourceItem item &&
+ this.Number == item.Number &&
+ this.Text == item.Text;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.Number.GetHashCode() ^ this.Text.GetHashCode();
+ }
+ }
+}
diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialDataAttributeTests.cs b/test/Xunit.Combinatorial.Tests/CombinatorialDataAttributeTests.cs
index 285d3cf..dde7bd4 100644
--- a/test/Xunit.Combinatorial.Tests/CombinatorialDataAttributeTests.cs
+++ b/test/Xunit.Combinatorial.Tests/CombinatorialDataAttributeTests.cs
@@ -169,6 +169,10 @@ private static void Suppose_string_int_bool_Values([CombinatorialValues("a", "b"
{
}
+ private static void Suppose_type([CombinatorialValues(typeof(CombinatorialDataAttributeTests))] Type p1, [CombinatorialValues(2, 4, 6)] int p2, bool p3)
+ {
+ }
+
private static void Suppose_DateTimeKind(DateTimeKind p1)
{
}
diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataAttributeTests.cs b/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataAttributeTests.cs
index 932666a..9364429 100644
--- a/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataAttributeTests.cs
+++ b/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataAttributeTests.cs
@@ -99,6 +99,15 @@ public void IncompatibleMemberDataTypeThrows()
Assert.Equal("Parameter type System.Int32 is not compatible with returned member type System.Guid.", exception.Message);
}
+ [Fact]
+ public void MemberDataReturnsTheoryDataReturnsValues()
+ {
+ var attribute = new CombinatorialMemberDataAttribute(nameof(GetValuesAsTheoryData));
+ ParameterInfo parameter = StubIntMethodInfo.GetParameters()[0];
+ object?[]? values = attribute.GetValues(parameter);
+ Assert.Equal(new object[] { 1, 2, 3, 4 }, values);
+ }
+
private static IEnumerable GetValuesAsEnumerableOfInt()
{
yield return 1;
@@ -113,6 +122,8 @@ private static IEnumerable GetValuesAsEnumerableOfInt()
private static ImplementsOnlyNonGenericIEnumerable GetValuesAsTypeThatImplementsNonGenericIEnumerable() => new();
+ private static TheoryData GetValuesAsTheoryData() => new() { 1, 2, 3, 4 };
+
private static IEnumerable GetValuesAsEnumerableOfIntArray()
{
yield return new[] { 1 };
diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataSampleUses.cs b/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataSampleUses.cs
index f91320a..448f390 100644
--- a/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataSampleUses.cs
+++ b/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataSampleUses.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Andrew Arnott. All rights reserved.
+// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
using Xunit;
@@ -10,6 +10,7 @@ public class CombinatorialMemberDataSampleUses
#pragma warning disable SA1202 // Elements should be ordered by access
public static readonly IEnumerable IntFieldValues = Enumerable.Range(0, 5).Select(_ => Random.Next());
public static readonly IEnumerable GuidFieldValues = Enumerable.Range(0, 5).Select(_ => Guid.NewGuid());
+ public static readonly TheoryData IntFieldTheoryData = new() { 1, 2, 3, 4, 5 };
#pragma warning restore SA1202 // Elements should be ordered by access
public static readonly TheoryData MyTestCases = new(
@@ -89,6 +90,29 @@ public void CombinatorialMemberDataFromFields(
Assert.True(true);
}
+ [Theory, CombinatorialData]
+ public void CombinatorialMemberDataFromTheoryDataField(
+ [CombinatorialMemberData(nameof(IntFieldTheoryData))] int p1)
+ {
+ Assert.True(true);
+ }
+
+ [Theory, CombinatorialData]
+ public void CombinatorialMemberDataFromTheoryClass(
+ [CombinatorialMemberData(nameof(MyValueSource.GetValues), MemberType = typeof(MyValueSource))] MyValueSourceItem p1)
+ {
+ Assert.True(true);
+ }
+
+#if NETSTANDARD2_0_OR_GREATER
+ [Theory, CombinatorialData]
+ public void GenericCombinatorialMemberDataFromTheoryClass(
+ [CombinatorialMemberData(nameof(MyValueSource.GetValues))] MyValueSourceItem p1)
+ {
+ Assert.True(true);
+ }
+#endif
+
[Theory, CombinatorialData]
public void TheoryDataOfT([CombinatorialMemberData(nameof(MyTestCases))] MyTestCase testCase, bool flag)
{
@@ -101,5 +125,31 @@ public void TheoryDataOfT([CombinatorialMemberData(nameof(MyTestCases))] MyTestC
*/
}
+ public class MyValueSourceItem
+ {
+ public MyValueSourceItem(int number, string text)
+ {
+ this.Number = number;
+ this.Text = text;
+ }
+
+ public string Text { get; }
+
+ public int Number { get; }
+ }
+
+ private class MyValueSource
+ {
+ public static TheoryData GetValues()
+ {
+ return new TheoryData
+ {
+ new MyValueSourceItem(1, "Foo"),
+ new MyValueSourceItem(2, "Bar"),
+ new MyValueSourceItem(3, "Baz"),
+ };
+ }
+ }
+
public record MyTestCase(int Number, string Text);
}
diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialRandomDataAttributeTests.cs b/test/Xunit.Combinatorial.Tests/CombinatorialRandomDataAttributeTests.cs
index ab7f972..491af16 100644
--- a/test/Xunit.Combinatorial.Tests/CombinatorialRandomDataAttributeTests.cs
+++ b/test/Xunit.Combinatorial.Tests/CombinatorialRandomDataAttributeTests.cs
@@ -43,24 +43,24 @@ public void ConstructorCountMinMaxValuesSeed(int count, int minValue, int maxVal
[InlineData(int.MinValue)]
public void ConstructorOutOfRangeCount(int count)
{
- Assert.Throws(() => new CombinatorialRandomDataAttribute { Count = count }.Values);
+ Assert.Throws(() => new CombinatorialRandomDataAttribute { Count = count }.GetValues(null!));
}
[Fact]
public void CountHigherThanRangeSize()
{
- Assert.Throws(() => new CombinatorialRandomDataAttribute { Count = 6, Minimum = 1, Maximum = 5 }.Values);
- Assert.Throws(() => new CombinatorialRandomDataAttribute { Count = 4, Minimum = -3, Maximum = -1 }.Values);
- _ = new CombinatorialRandomDataAttribute { Count = 3, Minimum = 1, Maximum = 3 }.Values;
- _ = new CombinatorialRandomDataAttribute { Count = 3, Minimum = -3, Maximum = -1 }.Values;
+ Assert.Throws(() => new CombinatorialRandomDataAttribute { Count = 6, Minimum = 1, Maximum = 5 }.GetValues(null!));
+ Assert.Throws(() => new CombinatorialRandomDataAttribute { Count = 4, Minimum = -3, Maximum = -1 }.GetValues(null!));
+ _ = new CombinatorialRandomDataAttribute { Count = 3, Minimum = 1, Maximum = 3 }.GetValues(null!);
+ _ = new CombinatorialRandomDataAttribute { Count = 3, Minimum = -3, Maximum = -1 }.GetValues(null!);
}
internal static void Check(CombinatorialRandomDataAttribute attribute)
{
- Assert.NotNull(attribute.Values);
- Assert.Equal(attribute.Count, attribute.Values.Length);
+ Assert.NotNull(attribute.GetValues(null!));
+ Assert.Equal(attribute.Count, attribute.GetValues(null!).Length);
- Assert.All(attribute.Values, value =>
+ Assert.All(attribute.GetValues(null!), value =>
{
Assert.IsType(value);
int intValue = (int)value;
@@ -69,7 +69,7 @@ internal static void Check(CombinatorialRandomDataAttribute attribute)
if (attribute.Seed != CombinatorialRandomDataAttribute.NoSeed)
{
- Assert.Equal(RandomIterator(new Random(attribute.Seed), attribute.Minimum, attribute.Maximum).Distinct().Take(attribute.Count).ToArray(), attribute.Values.Cast());
+ Assert.Equal(RandomIterator(new Random(attribute.Seed), attribute.Minimum, attribute.Maximum).Distinct().Take(attribute.Count).ToArray(), attribute.GetValues(null!).Cast());
}
}
diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialRangeAttributeTests.cs b/test/Xunit.Combinatorial.Tests/CombinatorialRangeAttributeTests.cs
index 0e3c0c2..1080dee 100644
--- a/test/Xunit.Combinatorial.Tests/CombinatorialRangeAttributeTests.cs
+++ b/test/Xunit.Combinatorial.Tests/CombinatorialRangeAttributeTests.cs
@@ -11,7 +11,7 @@ public void CountOfIntegers_HappyPath_SetsAttributeWithRange(int from, int count
{
object[] values = Enumerable.Range(from, count).Cast().ToArray();
var attribute = new CombinatorialRangeAttribute(from, count);
- Assert.Equal(values, attribute.Values);
+ Assert.Equal(values, attribute.GetValues(null!));
}
[Theory]
@@ -31,7 +31,7 @@ public void IntegerStep_HappyPath_SetsAttributeWithRange(int from, int to, int s
object[] expectedValues = Sequence(from, to, step).Cast().ToArray();
var attribute = new CombinatorialRangeAttribute(from, to, step);
- Assert.Equal(expectedValues, attribute.Values);
+ Assert.Equal(expectedValues, attribute.GetValues(null!));
}
[Theory]
@@ -48,7 +48,7 @@ public void CountOfUnsignedIntegers_HappyPath_SetsAttributeWithRange(uint from,
{
object[] values = UnsignedSequence(from, from + count - 1u, 1u).Cast().ToArray();
var attribute = new CombinatorialRangeAttribute(from, count);
- Assert.Equal(values, attribute.Values);
+ Assert.Equal(values, attribute.GetValues(null!));
}
[Theory]
@@ -67,7 +67,7 @@ public void UnsignedIntegerStep_HappyPath_SetsAttributeWithRange(uint from, uint
object[] expectedValues = UnsignedSequence(from, to, step).Cast().ToArray();
var attribute = new CombinatorialRangeAttribute(from, to, step);
- Assert.Equal(expectedValues, attribute.Values);
+ Assert.Equal(expectedValues, attribute.GetValues(null!));
}
internal static IEnumerable Sequence(int from, int to, int step)
diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialValuesAttributeTests.cs b/test/Xunit.Combinatorial.Tests/CombinatorialValuesAttributeTests.cs
index bc0b7e5..14bd106 100644
--- a/test/Xunit.Combinatorial.Tests/CombinatorialValuesAttributeTests.cs
+++ b/test/Xunit.Combinatorial.Tests/CombinatorialValuesAttributeTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Andrew Arnott. All rights reserved.
+// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.
using Xunit;
@@ -10,20 +10,20 @@ public void Ctor_SetsProperty()
{
object[] values = new object[1];
var attribute = new CombinatorialValuesAttribute(values);
- Assert.Same(values, attribute.Values);
+ Assert.Same(values, attribute.GetValues(null!));
}
[Fact]
public void NullArg()
{
var attribute = new CombinatorialValuesAttribute(null);
- Assert.Equal([null], attribute.Values);
+ Assert.Equal([null], attribute.GetValues(null!));
}
[Fact]
public void NullArgInArray()
{
var attribute = new CombinatorialValuesAttribute([null]);
- Assert.Equal([null], attribute.Values);
+ Assert.Equal([null], attribute.GetValues(null!));
}
}
diff --git a/test/Xunit.Combinatorial.Tests/SampleUses.cs b/test/Xunit.Combinatorial.Tests/SampleUses.cs
index 4eb7524..80466ee 100644
--- a/test/Xunit.Combinatorial.Tests/SampleUses.cs
+++ b/test/Xunit.Combinatorial.Tests/SampleUses.cs
@@ -7,6 +7,12 @@
public class SampleUses
{
+ public static TheoryData GetCustomCombinatorialMemberData(
+ int seed /* Optional parameters are currently not supported on member data accessors */)
+ {
+ return CustomCombinatorialDataSource.GetMemberData(seed);
+ }
+
[Theory, CombinatorialData]
public void CombinatorialBoolean(bool v1, bool v2, bool v3)
{
@@ -83,17 +89,112 @@ public void CombinatorialRandomValuesCountMaxValue([CombinatorialRandomData(Coun
}
[Theory, CombinatorialData]
- public void CombinatorialRandomValuesCountMinMaxValues([CombinatorialRandomData(Count = 10, Minimum = -20, Maximum = -5)] int p1)
+ public void CombinatorialRandomValuesCountMinMaxValues(
+ [CombinatorialRandomData(Count = 10, Minimum = -20, Maximum = -5)]
+ int p1)
{
Assert.InRange(p1, -20, -5);
}
[Theory, CombinatorialData]
- public void CombinatorialRandomValuesCountMinMaxValuesSeed([CombinatorialRandomData(Count = 10, Minimum = -5, Maximum = 6, Seed = 567)] int p1)
+ public void CombinatorialRandomValuesCountMinMaxValuesSeed(
+ [CombinatorialRandomData(Count = 10, Minimum = -5, Maximum = 6, Seed = 567)]
+ int p1)
{
Assert.InRange(p1, -5, 6);
}
+ [Theory, CombinatorialData]
+ public void CombinatorialMemberDataAttributeTest(
+ [CombinatorialMemberData(
+ nameof(GetCustomCombinatorialMemberData),
+ /* seed */ 0)]
+ CustomCombinatorialDataSourceTestCase testCase,
+ bool flag)
+ {
+ Assert.InRange(testCase.Value, 1, 3);
+ }
+
+#if NETSTANDARD2_0_OR_GREATER
+ [Theory, CombinatorialData]
+ public void GenericCombinatorialMemberDataAttributeTest(
+ [CombinatorialMemberData(
+ nameof(CustomCombinatorialDataSource.GetMemberData),
+ /* seed */ 0)]
+ CustomCombinatorialDataSourceTestCase testCase,
+ bool flag)
+ {
+ Assert.InRange(testCase.Value, 1, 3);
+ }
+#endif
+
+ [Theory, CombinatorialData]
+ public void CombinatorialClassDataAttributeTest(
+ [CombinatorialClassData(typeof(CustomCombinatorialDataSource))]
+ CustomCombinatorialDataSourceTestCase testCase,
+ bool flag)
+ {
+ Assert.InRange(testCase.Value, 1, 3);
+ }
+
+#if NETSTANDARD2_0_OR_GREATER
+ [Theory, CombinatorialData]
+ public void GenericCombinatorialClassDataAttributeTest(
+ [CombinatorialClassData]
+ CustomCombinatorialDataSourceTestCase testCase,
+ bool flag)
+ {
+ Assert.InRange(testCase.Value, 1, 3);
+ }
+#endif
+
+ [Theory, CombinatorialData]
+ public void CombinatorialClassDataAttributeTestWithArgument(
+ [CombinatorialClassData(typeof(CustomCombinatorialDataSource), 42)]
+ CustomCombinatorialDataSourceTestCase testCase,
+ bool flag)
+ {
+ var argumentValue = 42;
+ Assert.InRange(testCase.Value, argumentValue + 1, argumentValue + 3);
+ }
+
+#if NETSTANDARD2_0_OR_GREATER
+ [Theory, CombinatorialData]
+ public void GenericCombinatorialClassDataAttributeTestWithArgument(
+ [CombinatorialClassData(42)]
+ CustomCombinatorialDataSourceTestCase testCase,
+ bool flag)
+ {
+ var argumentValue = 42;
+ Assert.InRange(testCase.Value, argumentValue + 1, argumentValue + 3);
+ }
+#endif
+
+ public class CustomCombinatorialDataSourceTestCase
+ {
+ public CustomCombinatorialDataSourceTestCase(int value)
+ {
+ this.Value = value;
+ }
+
+ public int Value { get; }
+ }
+
+ private class CustomCombinatorialDataSource : TheoryData
+ {
+ public CustomCombinatorialDataSource(int seed = 0)
+ {
+ this.Add(new CustomCombinatorialDataSourceTestCase(seed + 1));
+ this.Add(new CustomCombinatorialDataSourceTestCase(seed + 2));
+ this.Add(new CustomCombinatorialDataSourceTestCase(seed + 3));
+ }
+
+ public static TheoryData GetMemberData(int seed = 0)
+ {
+ return new CustomCombinatorialDataSource(seed);
+ }
+ }
+
[AttributeUsage(AttributeTargets.Parameter)]
private class CustomValuesAttribute : CombinatorialValuesAttribute
{
diff --git a/test/Xunit.Combinatorial.Tests/Xunit.Combinatorial.Tests.csproj b/test/Xunit.Combinatorial.Tests/Xunit.Combinatorial.Tests.csproj
index 0aa4a8d..cfad742 100644
--- a/test/Xunit.Combinatorial.Tests/Xunit.Combinatorial.Tests.csproj
+++ b/test/Xunit.Combinatorial.Tests/Xunit.Combinatorial.Tests.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
$(TargetFrameworks);net472