Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge v1.7 for main #124

Merged
merged 15 commits into from
Dec 31, 2024
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,6 @@ MigrationBackup/

# Analysis results
*.sarif

# JetBrains Rider
.idea
80 changes: 80 additions & 0 deletions src/Xunit.Combinatorial/CombinatorialClassDataAttribute.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Specifies a class that provides the values for a combinatorial test.
/// </summary>
public class CombinatorialClassDataAttribute : Attribute, ICombinatorialValuesProvider
{
private readonly object?[] values;

/// <summary>
/// Initializes a new instance of the <see cref="CombinatorialClassDataAttribute" /> class.
/// </summary>
/// <param name="valuesSourceType">The type of the class that provides the values for a combinatorial test.</param>
/// <param name="arguments">The arguments to pass to the constructor of <paramref name="valuesSourceType" />.</param>
public CombinatorialClassDataAttribute(Type valuesSourceType, params object[]? arguments)
{
this.values = GetValues(valuesSourceType, arguments);
}

/// <inheritdoc />
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<object[]>().SelectMany(rows => rows).ToArray();
}

private static void EnsureValidValuesSourceType(Type valuesSourceType)
{
if (typeof(IEnumerable<object[]>).IsAssignableFrom(valuesSourceType))
{
return;
}

if (TheoryDataHelper.IsTheoryDataType(valuesSourceType))
{
return;
}

throw new InvalidOperationException($"The values source {valuesSourceType} must be assignable to {typeof(IEnumerable<object[]>)}), {typeof(TheoryData<>)} or {typeof(TheoryDataBase<,>)}.");
}
}
49 changes: 34 additions & 15 deletions src/Xunit.Combinatorial/CombinatorialMemberDataAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ namespace Xunit;
/// Specifies which member should provide data for this parameter used for running the test method.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public class CombinatorialMemberDataAttribute : Attribute
public class CombinatorialMemberDataAttribute : Attribute, ICombinatorialValuesProvider
{
/// <summary>
/// Initializes a new instance of the <see cref="CombinatorialMemberDataAttribute"/> class.
/// </summary>
/// <param name="memberName">The name of the public static member on the test class that will provide the test data.</param>
/// <param name="arguments">The arguments for the member (only supported for methods; ignored for everything else).</param>
/// <remarks>Optional parameters on methods are not supported.</remarks>
public CombinatorialMemberDataAttribute(string memberName, params object?[]? arguments)
{
this.MemberName = memberName ?? throw new ArgumentNullException(nameof(memberName));
Expand Down Expand Up @@ -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<object>().ToArray();
var values = (IEnumerable)accessor();

if (values is IEnumerable<object[]> untypedValues)
{
return untypedValues.SelectMany(rows => rows).ToArray();
}

if (TheoryDataHelper.TryGetTheoryDataValues(values, out object?[]? theoryDataValues))
{
return theoryDataValues;
}

return values.Cast<object>().ToArray();
}

/// <summary>
Expand All @@ -75,19 +87,10 @@ public CombinatorialMemberDataAttribute(string memberName, params object?[]? arg
/// <returns>The generic type argument for (one of) the <see cref="IEnumerable{T}"/> interface)s) implemented by the <paramref name="enumerableType"/>.</returns>
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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -211,7 +219,13 @@ private bool ParameterTypesCompatible(ParameterInfo[] parameters, object?[]? arg
/// <exception cref="ArgumentException">Throw when <paramref name="enumerableType"/> does not conform to requirements or does not produce values assignable to <paramref name="parameterInfo"/>.</exception>
private void EnsureValidMemberDataType(Type enumerableType, Type declaringType, ParameterInfo parameterInfo)
{
if (typeof(IEnumerable<object[]>).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<T>.");
Expand All @@ -229,6 +243,11 @@ private void EnsureValidMemberDataType(Type enumerableType, Type declaringType,
$"Member {this.MemberName} on {declaringType.FullName} returned an IEnumerable<IEnumerable<{enumeratedType.GetGenericArguments()[0].Name}>>, which is not supported.");
}

if (TheoryDataHelper.IsTheoryDataRowType(enumeratedType))
{
return;
}

if (!enumeratedType.IsAssignableFrom(parameterInfo.ParameterType.GetTypeInfo()))
{
throw new ArgumentException(
Expand Down
15 changes: 8 additions & 7 deletions src/Xunit.Combinatorial/CombinatorialRandomDataAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// 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;

/// <summary>
/// Specifies which range of values for this parameter should be used for running the test method.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class CombinatorialRandomDataAttribute : Attribute
public class CombinatorialRandomDataAttribute : Attribute, ICombinatorialValuesProvider
{
/// <summary>
/// Special seed value to create System.Random class without seed.
Expand Down Expand Up @@ -42,11 +43,11 @@ public class CombinatorialRandomDataAttribute : Attribute
/// <value>The default value of <see cref="NoSeed"/> allows for a new seed to be used each time.</value>
public int Seed { get; set; } = NoSeed;

/// <summary>
/// Gets the values that should be passed to this parameter on the test method.
/// </summary>
/// <value>An array of values.</value>
public object[] Values => this.values ??= this.GenerateValues();
/// <inheritdoc />
public object[] GetValues(ParameterInfo parameter)
{
return this.values ??= this.GenerateValues();
}

private object[] GenerateValues()
{
Expand Down
26 changes: 15 additions & 11 deletions src/Xunit.Combinatorial/CombinatorialRangeAttribute.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Specifies which range of values for this parameter should be used for running the test method.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class CombinatorialRangeAttribute : Attribute
public class CombinatorialRangeAttribute : Attribute, ICombinatorialValuesProvider
{
private readonly object[] values;

/// <summary>
/// Initializes a new instance of the <see cref="CombinatorialRangeAttribute"/> class.
/// </summary>
Expand All @@ -30,7 +34,7 @@ public CombinatorialRangeAttribute(int from, int count)
values[i] = from + i;
}

this.Values = values;
this.values = values;
}

/// <summary>
Expand Down Expand Up @@ -75,7 +79,7 @@ public CombinatorialRangeAttribute(int from, int to, int step)
values[i] = from + (i * step);
}

this.Values = values;
this.values = values;
}

/// <summary>
Expand All @@ -99,7 +103,7 @@ public CombinatorialRangeAttribute(uint from, uint count)
values[i] = from + i;
}

this.Values = values;
this.values = values;
}

/// <summary>
Expand Down Expand Up @@ -140,12 +144,12 @@ public CombinatorialRangeAttribute(uint from, uint to, uint step)
}
}

this.Values = values.Cast<object>().ToArray();
this.values = values.Cast<object>().ToArray();
}

/// <summary>
/// Gets the values that should be passed to this parameter on the test method.
/// </summary>
/// <value>An array of values.</value>
public object[] Values { get; }
/// <inheritdoc />
public object[] GetValues(ParameterInfo parameter)
{
return this.values;
}
}
20 changes: 12 additions & 8 deletions src/Xunit.Combinatorial/CombinatorialValuesAttribute.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Specifies which values for this parameter should be used for running the test method.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class CombinatorialValuesAttribute : Attribute
public class CombinatorialValuesAttribute : Attribute, ICombinatorialValuesProvider
{
private readonly object?[] values;

/// <summary>
/// Initializes a new instance of the <see cref="CombinatorialValuesAttribute"/> class.
/// </summary>
Expand All @@ -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 };
}

/// <summary>
/// Gets the values that should be passed to this parameter on the test method.
/// </summary>
/// <value>An array of values.</value>
public object?[] Values { get; }
/// <inheritdoc />
public object?[] GetValues(ParameterInfo parameter)
{
return this.values;
}
}
19 changes: 19 additions & 0 deletions src/Xunit.Combinatorial/ICombinatorialValuesProvider.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// An interface that provides values for a parameter on a test method.
/// </summary>
public interface ICombinatorialValuesProvider
{
/// <summary>
/// Gets the values that should be passed to this parameter on the test method.
/// </summary>
/// <param name="parameter">The parameter to get values for.</param>
/// <returns>An array of values.</returns>
object?[] GetValues(ParameterInfo parameter);
}
Loading