Skip to content

Commit

Permalink
Merge pull request #124 from AArnott/v1.7_for_main
Browse files Browse the repository at this point in the history
Merge v1.7 for main
  • Loading branch information
AArnott authored Dec 31, 2024
2 parents d066add + 28b993c commit a05eb25
Show file tree
Hide file tree
Showing 18 changed files with 554 additions and 91 deletions.
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

0 comments on commit a05eb25

Please sign in to comment.