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