diff --git a/src/Xunit.Combinatorial/CombinatorialDataAttribute.cs b/src/Xunit.Combinatorial/CombinatorialDataAttribute.cs index b67fdf0..6c10c6a 100644 --- a/src/Xunit.Combinatorial/CombinatorialDataAttribute.cs +++ b/src/Xunit.Combinatorial/CombinatorialDataAttribute.cs @@ -4,82 +4,81 @@ using System.Reflection; using Xunit.Sdk; -namespace Xunit +namespace Xunit; + +/// +/// Provides a test method decorated with a +/// with arguments to run every possible combination of values for the +/// parameters taken by the test method. +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] +public class CombinatorialDataAttribute : DataAttribute { /// - /// Provides a test method decorated with a - /// with arguments to run every possible combination of values for the - /// parameters taken by the test method. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public class CombinatorialDataAttribute : DataAttribute + public CombinatorialDataAttribute() + { + } + + /// + public override IEnumerable GetData(MethodInfo testMethod) { - /// - /// Initializes a new instance of the class. - /// - public CombinatorialDataAttribute() + Requires.NotNull(testMethod, nameof(testMethod)); + + ParameterInfo[]? parameters = testMethod.GetParameters(); + if (parameters.Length == 0) { + return Enumerable.Empty(); } - /// - public override IEnumerable GetData(MethodInfo testMethod) + var values = new List[parameters.Length]; + for (int i = 0; i < parameters.Length; i++) { - Requires.NotNull(testMethod, nameof(testMethod)); - - ParameterInfo[]? parameters = testMethod.GetParameters(); - if (parameters.Length == 0) - { - return Enumerable.Empty(); - } + values[i] = ValuesUtilities.GetValuesFor(parameters[i]).ToList(); + } - var values = new List[parameters.Length]; - for (int i = 0; i < parameters.Length; i++) - { - values[i] = ValuesUtilities.GetValuesFor(parameters[i]).ToList(); - } + object[]? currentValues = new object[parameters.Length]; + return this.FillCombinations(parameters, values, currentValues, 0); + } - object[]? currentValues = new object[parameters.Length]; - return this.FillCombinations(parameters, values, currentValues, 0); - } + /// + /// Produces a sequence of argument arrays that capture every possible + /// combination of values. + /// + /// The parameters taken by the test method. + /// An array of each argument's list of possible values. + /// An array that is being recursively initialized with a set of arguments to pass to the test method. + /// The index into that this particular invocation should rotate through for. + /// A sequence of all combinations of arguments from , starting at . + private IEnumerable FillCombinations(ParameterInfo[] parameters, List[] candidateValues, object?[] currentValues, int index) + { + Requires.NotNull(parameters, nameof(parameters)); + Requires.NotNull(candidateValues, nameof(candidateValues)); + Requires.NotNull(currentValues, nameof(currentValues)); + Requires.Argument(parameters.Length == candidateValues.Length, nameof(candidateValues), $"Expected to have same array length as {nameof(parameters)}"); + Requires.Argument(parameters.Length == currentValues.Length, nameof(currentValues), $"Expected to have same array length as {nameof(parameters)}"); + Requires.Range(index >= 0 && index < parameters.Length, nameof(index)); - /// - /// Produces a sequence of argument arrays that capture every possible - /// combination of values. - /// - /// The parameters taken by the test method. - /// An array of each argument's list of possible values. - /// An array that is being recursively initialized with a set of arguments to pass to the test method. - /// The index into that this particular invocation should rotate through for. - /// A sequence of all combinations of arguments from , starting at . - private IEnumerable FillCombinations(ParameterInfo[] parameters, List[] candidateValues, object?[] currentValues, int index) + foreach (object? value in candidateValues[index]) { - Requires.NotNull(parameters, nameof(parameters)); - Requires.NotNull(candidateValues, nameof(candidateValues)); - Requires.NotNull(currentValues, nameof(currentValues)); - Requires.Argument(parameters.Length == candidateValues.Length, nameof(candidateValues), $"Expected to have same array length as {nameof(parameters)}"); - Requires.Argument(parameters.Length == currentValues.Length, nameof(currentValues), $"Expected to have same array length as {nameof(parameters)}"); - Requires.Range(index >= 0 && index < parameters.Length, nameof(index)); + currentValues[index] = value; - foreach (object? value in candidateValues[index]) + if (index + 1 < parameters.Length) { - currentValues[index] = value; - - if (index + 1 < parameters.Length) - { - foreach (object?[] result in this.FillCombinations(parameters, candidateValues, currentValues, index + 1)) - { - yield return result; - } - } - else + foreach (object?[] result in this.FillCombinations(parameters, candidateValues, currentValues, index + 1)) { - // We're the tail end, so just produce the value. - // Copy the array before returning since we're about to mutate currentValues - object[] finalSet = new object[currentValues.Length]; - Array.Copy(currentValues, finalSet, currentValues.Length); - yield return finalSet; + yield return result; } } + else + { + // We're the tail end, so just produce the value. + // Copy the array before returning since we're about to mutate currentValues + object[] finalSet = new object[currentValues.Length]; + Array.Copy(currentValues, finalSet, currentValues.Length); + yield return finalSet; + } } } } diff --git a/src/Xunit.Combinatorial/CombinatorialMemberDataAttribute.cs b/src/Xunit.Combinatorial/CombinatorialMemberDataAttribute.cs index 8731d9f..c3058e1 100644 --- a/src/Xunit.Combinatorial/CombinatorialMemberDataAttribute.cs +++ b/src/Xunit.Combinatorial/CombinatorialMemberDataAttribute.cs @@ -4,231 +4,226 @@ using System.Collections; using System.Reflection; -namespace Xunit +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 { /// - /// Specifies which member should provide data for this parameter used for running the test method. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)] - public class CombinatorialMemberDataAttribute : Attribute + /// 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). + public CombinatorialMemberDataAttribute(string memberName, params object?[]? arguments) { - /// - /// 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). - public CombinatorialMemberDataAttribute(string memberName, params object?[]? arguments) - { - this.MemberName = memberName ?? throw new ArgumentNullException(nameof(memberName)); - this.Arguments = arguments; - } + this.MemberName = memberName ?? throw new ArgumentNullException(nameof(memberName)); + this.Arguments = arguments; + } - /// - /// Gets the member name. - /// - public string MemberName { get; } + /// + /// Gets the member name. + /// + public string MemberName { get; } - /// - /// Gets or sets the type to retrieve the member from. If not set, then the property will be - /// retrieved from the unit test class. - /// - public Type? MemberType { get; set; } + /// + /// Gets or sets the type to retrieve the member from. If not set, then the property will be + /// retrieved from the unit test class. + /// + public Type? MemberType { get; set; } - /// - /// Gets the arguments passed to the member. Only supported for static methods. - /// - public object?[]? Arguments { get; } + /// + /// Gets the arguments passed to the member. Only supported for static methods. + /// + public object?[]? Arguments { get; } - /// - /// Gets the values that should be passed to this parameter on the test method. - /// - /// The parameter for which the data should be provided. - /// An array of values. - public object?[] GetValues(ParameterInfo parameterInfo) - { - Requires.NotNull(parameterInfo, nameof(parameterInfo)); + /// + /// Gets the values that should be passed to this parameter on the test method. + /// + /// The parameter for which the data should be provided. + /// An array of values. + public object?[] GetValues(ParameterInfo parameterInfo) + { + Requires.NotNull(parameterInfo, nameof(parameterInfo)); - MemberInfo? testMethod = parameterInfo.Member; + MemberInfo? testMethod = parameterInfo.Member; - Type? type = this.MemberType ?? testMethod?.DeclaringType; + Type? type = this.MemberType ?? testMethod?.DeclaringType; - if (type is null) - { -#if NETSTANDARD - return Array.Empty(); -#else - return new object[0]; -#endif - } + if (type is null) + { + return Array.Empty(); + } - Func? accessor = this.GetPropertyAccessor(type, parameterInfo) ?? this.GetMethodAccessor(type, parameterInfo) ?? this.GetFieldAccessor(type, parameterInfo); - if (accessor is null) - { - string? parameterText = this.Arguments?.Length > 0 ? $" with parameter types: {string.Join(", ", this.Arguments.Select(p => p?.GetType().FullName ?? "(null)"))}" : string.Empty; - throw new ArgumentException($"Could not find public static member (property, field, or method) named '{this.MemberName}' on {type.FullName}{parameterText}."); - } + Func? accessor = this.GetPropertyAccessor(type, parameterInfo) ?? this.GetMethodAccessor(type, parameterInfo) ?? this.GetFieldAccessor(type, parameterInfo); + if (accessor is null) + { + string? parameterText = this.Arguments?.Length > 0 ? $" with parameter types: {string.Join(", ", this.Arguments.Select(p => p?.GetType().FullName ?? "(null)"))}" : string.Empty; + 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 obj = (IEnumerable)accessor(); + return obj.Cast().ToArray(); + } + + /// + /// Gets the type of the value enumerated by a given type that is or implements . + /// + /// An type or a type that implements . + /// The generic type argument for (one of) the interface)s) implemented by the . + private static TypeInfo? GetEnumeratedType(Type enumerableType) + { + if (enumerableType.IsGenericType && enumerableType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + Type[] enumerableGenericTypeArgs = enumerableType.GetTypeInfo().GetGenericArguments(); + return enumerableGenericTypeArgs[0].GetTypeInfo(); } - /// - /// Gets the type of the value enumerated by a given type that is or implements . - /// - /// An type or a type that implements . - /// The generic type argument for (one of) the interface)s) implemented by the . - private static TypeInfo? GetEnumeratedType(Type enumerableType) + foreach (Type implementedInterface in enumerableType.GetTypeInfo().ImplementedInterfaces) { - if (enumerableType.IsGenericType && enumerableType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + TypeInfo interfaceTypeInfo = implementedInterface.GetTypeInfo(); + if (interfaceTypeInfo.IsGenericType && interfaceTypeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { - Type[] enumerableGenericTypeArgs = enumerableType.GetTypeInfo().GetGenericArguments(); - return enumerableGenericTypeArgs[0].GetTypeInfo(); + return interfaceTypeInfo.GetGenericArguments()[0].GetTypeInfo(); } + } - foreach (Type implementedInterface in enumerableType.GetTypeInfo().ImplementedInterfaces) + return null; + } + + private Func? GetPropertyAccessor(Type type, ParameterInfo parameterInfo) + { + PropertyInfo? propInfo = null; + for (Type? reflectionType = type; reflectionType is not null; reflectionType = reflectionType.GetTypeInfo().BaseType) + { + propInfo = reflectionType.GetRuntimeProperty(this.MemberName); + if (propInfo is not null) { - TypeInfo interfaceTypeInfo = implementedInterface.GetTypeInfo(); - if (interfaceTypeInfo.IsGenericType && interfaceTypeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - { - return interfaceTypeInfo.GetGenericArguments()[0].GetTypeInfo(); - } + break; } + } + if (propInfo?.GetMethod is null || !propInfo.GetMethod.IsStatic) + { return null; } - private Func? GetPropertyAccessor(Type type, ParameterInfo parameterInfo) + this.EnsureValidMemberDataType(propInfo.PropertyType, propInfo.DeclaringType, parameterInfo); + + return () => propInfo.GetValue(null, null); + } + + private Func? GetMethodAccessor(Type type, ParameterInfo parameterInfo) + { + MethodInfo? methodInfo = null; + for (Type? reflectionType = type; reflectionType is not null; reflectionType = reflectionType.GetTypeInfo().BaseType) { - PropertyInfo? propInfo = null; - for (Type? reflectionType = type; reflectionType is not null; reflectionType = reflectionType.GetTypeInfo().BaseType) + methodInfo = reflectionType.GetRuntimeMethods().FirstOrDefault(m => m.Name == this.MemberName && this.ParameterTypesCompatible(m.GetParameters(), this.Arguments)); + if (methodInfo is not null) { - propInfo = reflectionType.GetRuntimeProperty(this.MemberName); - if (propInfo is not null) - { - break; - } + break; } + } - if (propInfo?.GetMethod is null || !propInfo.GetMethod.IsStatic) - { - return null; - } + if (methodInfo is null || !methodInfo.IsStatic) + { + return null; + } - this.EnsureValidMemberDataType(propInfo.PropertyType, propInfo.DeclaringType, parameterInfo); + this.EnsureValidMemberDataType(methodInfo.ReturnType, methodInfo.DeclaringType, parameterInfo); - return () => propInfo.GetValue(null, null); + return () => methodInfo.Invoke(null, this.Arguments); + } + + private bool ParameterTypesCompatible(ParameterInfo[] parameters, object?[]? arguments) + { + if (arguments is null) + { + return parameters.Length == 0; + } + else if (parameters.Length != arguments.Length) + { + return false; } - private Func? GetMethodAccessor(Type type, ParameterInfo parameterInfo) + for (int i = 0; i < parameters.Length; i++) { - MethodInfo? methodInfo = null; - for (Type? reflectionType = type; reflectionType is not null; reflectionType = reflectionType.GetTypeInfo().BaseType) + if (arguments[i] is object arg) { - methodInfo = reflectionType.GetRuntimeMethods().FirstOrDefault(m => m.Name == this.MemberName && this.ParameterTypesCompatible(m.GetParameters(), this.Arguments)); - if (methodInfo is not null) + if (!parameters[i].ParameterType.GetTypeInfo().IsAssignableFrom(arg.GetType().GetTypeInfo())) { - break; + return false; } } - - if (methodInfo is null || !methodInfo.IsStatic) + else { - return null; + if (parameters[i].ParameterType.IsValueType) + { + // Cannot assign null to a value type parameter. + return false; + } } - - this.EnsureValidMemberDataType(methodInfo.ReturnType, methodInfo.DeclaringType, parameterInfo); - - return () => methodInfo.Invoke(null, this.Arguments); } - private bool ParameterTypesCompatible(ParameterInfo[] parameters, object?[]? arguments) + return true; + } + + private Func? GetFieldAccessor(Type type, ParameterInfo parameterInfo) + { + FieldInfo? fieldInfo = null; + for (Type? reflectionType = type; reflectionType is not null; reflectionType = reflectionType.GetTypeInfo().BaseType) { - if (arguments is null) - { - return parameters.Length == 0; - } - else if (parameters.Length != arguments.Length) - { - return false; - } + fieldInfo = reflectionType.GetRuntimeField(this.MemberName); - for (int i = 0; i < parameters.Length; i++) + if (fieldInfo is not null) { - if (arguments[i] is object arg) - { - if (!parameters[i].ParameterType.GetTypeInfo().IsAssignableFrom(arg.GetType().GetTypeInfo())) - { - return false; - } - } - else - { - if (parameters[i].ParameterType.IsValueType) - { - // Cannot assign null to a value type parameter. - return false; - } - } + break; } - - return true; } - private Func? GetFieldAccessor(Type type, ParameterInfo parameterInfo) + if (fieldInfo is null || !fieldInfo.IsStatic) { - FieldInfo? fieldInfo = null; - for (Type? reflectionType = type; reflectionType is not null; reflectionType = reflectionType.GetTypeInfo().BaseType) - { - fieldInfo = reflectionType.GetRuntimeField(this.MemberName); - - if (fieldInfo is not null) - { - break; - } - } + return null; + } - if (fieldInfo is null || !fieldInfo.IsStatic) - { - return null; - } + this.EnsureValidMemberDataType(fieldInfo.FieldType, fieldInfo.DeclaringType, parameterInfo); - this.EnsureValidMemberDataType(fieldInfo.FieldType, fieldInfo.DeclaringType, parameterInfo); + return () => fieldInfo.GetValue(null); + } - return () => fieldInfo.GetValue(null); + /// + /// Throws if a given type will not generate values that are compatible with a given parameter. + /// + /// The type of value stored by the field or property. + /// The type on which the member is declared. + /// The parameter that must receive the values generated by . + /// Throw when does not conform to requirements or does not produce values assignable to . + private void EnsureValidMemberDataType(Type enumerableType, Type declaringType, ParameterInfo parameterInfo) + { + TypeInfo? enumeratedType = GetEnumeratedType(enumerableType); + if (enumeratedType is null) + { + throw new ArgumentException($"Member {this.MemberName} on {declaringType.FullName} must return a type that implements IEnumerable."); } - /// - /// Throws if a given type will not generate values that are compatible with a given parameter. - /// - /// The type of value stored by the field or property. - /// The type on which the member is declared. - /// The parameter that must receive the values generated by . - /// Throw when does not conform to requirements or does not produce values assignable to . - private void EnsureValidMemberDataType(Type enumerableType, Type declaringType, ParameterInfo parameterInfo) + if (enumeratedType.IsArray) { - TypeInfo? enumeratedType = GetEnumeratedType(enumerableType); - if (enumeratedType is null) - { - throw new ArgumentException($"Member {this.MemberName} on {declaringType.FullName} must return a type that implements IEnumerable."); - } - - if (enumeratedType.IsArray) - { - throw new ArgumentException( - $"Member {this.MemberName} on {declaringType.FullName} returned an IEnumerable<{enumeratedType.Name}>, which is not supported."); - } + throw new ArgumentException( + $"Member {this.MemberName} on {declaringType.FullName} returned an IEnumerable<{enumeratedType.Name}>, which is not supported."); + } - if (enumeratedType.IsGenericType && enumeratedType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - { - throw new ArgumentException( - $"Member {this.MemberName} on {declaringType.FullName} returned an IEnumerable>, which is not supported."); - } + if (enumeratedType.IsGenericType && enumeratedType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + throw new ArgumentException( + $"Member {this.MemberName} on {declaringType.FullName} returned an IEnumerable>, which is not supported."); + } - if (!enumeratedType.IsAssignableFrom(parameterInfo.ParameterType.GetTypeInfo())) - { - throw new ArgumentException( - $"Parameter type {parameterInfo.ParameterType.FullName} is not compatible with returned member type {enumeratedType.FullName}."); - } + if (!enumeratedType.IsAssignableFrom(parameterInfo.ParameterType.GetTypeInfo())) + { + throw new ArgumentException( + $"Parameter type {parameterInfo.ParameterType.FullName} is not compatible with returned member type {enumeratedType.FullName}."); } } } diff --git a/src/Xunit.Combinatorial/CombinatorialRandomDataAttribute.cs b/src/Xunit.Combinatorial/CombinatorialRandomDataAttribute.cs index b88e965..a5d7c0b 100644 --- a/src/Xunit.Combinatorial/CombinatorialRandomDataAttribute.cs +++ b/src/Xunit.Combinatorial/CombinatorialRandomDataAttribute.cs @@ -3,96 +3,95 @@ using System.Globalization; -namespace Xunit +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 { /// - /// Specifies which range of values for this parameter should be used for running the test method. + /// Special seed value to create System.Random class without seed. + /// + public const int NoSeed = 0; + + private object[]? values; + + /// + /// Gets or sets the number of values to generate. Must be positive. + /// + /// The default value is 5. + public int Count { get; set; } = 5; + + /// + /// Gets or sets the minimum value (inclusive) that may be randomly generated. + /// + /// The default value is 0. + public int Minimum { get; set; } + + /// + /// Gets or sets the maximum value (inclusive) that may be randomly generated. + /// + /// The default value is - 1, which is the maximum allowable value. + public int Maximum { get; set; } = int.MaxValue - 1; + + /// + /// Gets or sets the seed to use for random number generation. + /// + /// 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. /// - [AttributeUsage(AttributeTargets.Parameter)] - public class CombinatorialRandomDataAttribute : Attribute + /// An array of values. + public object[] Values => this.values ??= this.GenerateValues(); + + private object[] GenerateValues() { - /// - /// Special seed value to create System.Random class without seed. - /// - public const int NoSeed = 0; - - private object[]? values; - - /// - /// Gets or sets the number of values to generate. Must be positive. - /// - /// The default value is 5. - public int Count { get; set; } = 5; - - /// - /// Gets or sets the minimum value (inclusive) that may be randomly generated. - /// - /// The default value is 0. - public int Minimum { get; set; } - - /// - /// Gets or sets the maximum value (inclusive) that may be randomly generated. - /// - /// The default value is - 1, which is the maximum allowable value. - public int Maximum { get; set; } = int.MaxValue - 1; - - /// - /// Gets or sets the seed to use for random number generation. - /// - /// 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(); - - private object[] GenerateValues() + if (this.Count < 1) { - if (this.Count < 1) - { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ValueMustBePositive, nameof(this.Count))); - } + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ValueMustBePositive, nameof(this.Count))); + } + + if (this.Minimum > this.Maximum) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.XMustNotBeGreaterThanY, nameof(this.Minimum), nameof(this.Maximum))); + } + + int maxPossibleValues = this.Maximum - this.Minimum + 1; + if (this.Count > maxPossibleValues) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.MoreRandomValuesRequestedThanPossibleOnes, nameof(this.Count), nameof(this.Minimum), nameof(this.Maximum))); + } - if (this.Minimum > this.Maximum) + Random random = this.Seed != NoSeed ? new Random(this.Seed) : new Random(); + + HashSet collisionChecker = new HashSet(); + object[] values = new object[this.Count]; + int collisionCount = 0; + int i = 0; + while (collisionChecker.Count < this.Count) + { + int value = random.Next(this.Minimum, this.Maximum + 1); + if (collisionChecker.Add(value)) { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.XMustNotBeGreaterThanY, nameof(this.Minimum), nameof(this.Maximum))); + values[i++] = value; } - - int maxPossibleValues = this.Maximum - this.Minimum + 1; - if (this.Count > maxPossibleValues) + else { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.MoreRandomValuesRequestedThanPossibleOnes, nameof(this.Count), nameof(this.Minimum), nameof(this.Maximum))); + collisionCount++; } - Random random = this.Seed != NoSeed ? new Random(this.Seed) : new Random(); - - HashSet collisionChecker = new HashSet(); - object[] values = new object[this.Count]; - int collisionCount = 0; - int i = 0; - while (collisionChecker.Count < this.Count) + if (collisionCount > collisionChecker.Count * 5) { - int value = random.Next(this.Minimum, this.Maximum + 1); - if (collisionChecker.Add(value)) - { - values[i++] = value; - } - else - { - collisionCount++; - } - - if (collisionCount > collisionChecker.Count * 5) - { - // We have collided in random values far more than we have successfully generated values. - // Rather than spin in this loop, throw. - throw new InvalidOperationException(Strings.TooManyRandomCollisions); - } + // We have collided in random values far more than we have successfully generated values. + // Rather than spin in this loop, throw. + throw new InvalidOperationException(Strings.TooManyRandomCollisions); } - - return values; } + + return values; } } diff --git a/src/Xunit.Combinatorial/CombinatorialRangeAttribute.cs b/src/Xunit.Combinatorial/CombinatorialRangeAttribute.cs index b21295f..c2e6ea5 100644 --- a/src/Xunit.Combinatorial/CombinatorialRangeAttribute.cs +++ b/src/Xunit.Combinatorial/CombinatorialRangeAttribute.cs @@ -1,152 +1,151 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the Ms-PL license. See LICENSE file in the project root for full license information. -namespace Xunit +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 { /// - /// Specifies which range of values for this parameter should be used for running the test method. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Parameter)] - public class CombinatorialRangeAttribute : Attribute + /// The value at the beginning of the range. + /// + /// The quantity of consecutive integer values to include. + /// Cannot be less than 1, which would conceptually result in zero test cases. + /// + public CombinatorialRangeAttribute(int from, int count) { - /// - /// Initializes a new instance of the class. - /// - /// The value at the beginning of the range. - /// - /// The quantity of consecutive integer values to include. - /// Cannot be less than 1, which would conceptually result in zero test cases. - /// - public CombinatorialRangeAttribute(int from, int count) + if (count < 1) { - if (count < 1) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - object[] values = new object[count]; - for (int i = 0; i < count; i++) - { - values[i] = from + i; - } + throw new ArgumentOutOfRangeException(nameof(count)); + } - this.Values = values; + object[] values = new object[count]; + for (int i = 0; i < count; i++) + { + values[i] = from + i; } - /// - /// Initializes a new instance of the class. - /// - /// The value at the beginning of the range. - /// - /// The value at the end of the range. - /// Cannot be less than "from" parameter. - /// When "to" and "from" are equal, CombinatorialValues is more appropriate. - /// - /// - /// The number of integers to step for each value in result. - /// Cannot be less than one. Stepping zero or backwards is not useful. - /// Stepping over "to" does not add another value to the range. - /// - public CombinatorialRangeAttribute(int from, int to, int step) + this.Values = values; + } + + /// + /// Initializes a new instance of the class. + /// + /// The value at the beginning of the range. + /// + /// The value at the end of the range. + /// Cannot be less than "from" parameter. + /// When "to" and "from" are equal, CombinatorialValues is more appropriate. + /// + /// + /// The number of integers to step for each value in result. + /// Cannot be less than one. Stepping zero or backwards is not useful. + /// Stepping over "to" does not add another value to the range. + /// + public CombinatorialRangeAttribute(int from, int to, int step) + { + if (step > 0) { - if (step > 0) + if (to < from) { - if (to < from) - { - throw new ArgumentOutOfRangeException(nameof(to)); - } + throw new ArgumentOutOfRangeException(nameof(to)); } - else if (step < 0) - { - if (to > from) - { - throw new ArgumentOutOfRangeException(nameof(to)); - } - } - else - { - throw new ArgumentOutOfRangeException(nameof(step)); - } - - int count = ((to - from) / step) + 1; - object[] values = new object[count]; - for (int i = 0; i < count; i++) + } + else if (step < 0) + { + if (to > from) { - values[i] = from + (i * step); + throw new ArgumentOutOfRangeException(nameof(to)); } - - this.Values = values; + } + else + { + throw new ArgumentOutOfRangeException(nameof(step)); } - /// - /// Initializes a new instance of the class. - /// - /// The value at the beginning of the range. - /// - /// The quantity of consecutive integer values to include. - /// Cannot be less than 1, which would conceptually result in zero test cases. - /// - public CombinatorialRangeAttribute(uint from, uint count) + int count = ((to - from) / step) + 1; + object[] values = new object[count]; + for (int i = 0; i < count; i++) { - if (count < 1) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } + values[i] = from + (i * step); + } - object[] values = new object[count]; - for (uint i = 0; i < count; i++) - { - values[i] = from + i; - } + this.Values = values; + } - this.Values = values; + /// + /// Initializes a new instance of the class. + /// + /// The value at the beginning of the range. + /// + /// The quantity of consecutive integer values to include. + /// Cannot be less than 1, which would conceptually result in zero test cases. + /// + public CombinatorialRangeAttribute(uint from, uint count) + { + if (count < 1) + { + throw new ArgumentOutOfRangeException(nameof(count)); } - /// - /// Initializes a new instance of the class. - /// - /// The value at the beginning of the range. - /// - /// The value at the end of the range. - /// Cannot be less than "from" parameter. - /// When "to" and "from" are equal, CombinatorialValues is more appropriate. - /// - /// - /// The number of unsigned integers to step for each value in result. - /// Cannot be less than one. Stepping zero is not useful. - /// Stepping over "to" does not add another value to the range. - /// - public CombinatorialRangeAttribute(uint from, uint to, uint step) + object[] values = new object[count]; + for (uint i = 0; i < count; i++) { - if (step == 0) - { - throw new ArgumentOutOfRangeException(nameof(step)); - } + values[i] = from + i; + } - var values = new List(); + this.Values = values; + } - if (from < to) + /// + /// Initializes a new instance of the class. + /// + /// The value at the beginning of the range. + /// + /// The value at the end of the range. + /// Cannot be less than "from" parameter. + /// When "to" and "from" are equal, CombinatorialValues is more appropriate. + /// + /// + /// The number of unsigned integers to step for each value in result. + /// Cannot be less than one. Stepping zero is not useful. + /// Stepping over "to" does not add another value to the range. + /// + public CombinatorialRangeAttribute(uint from, uint to, uint step) + { + if (step == 0) + { + throw new ArgumentOutOfRangeException(nameof(step)); + } + + var values = new List(); + + if (from < to) + { + for (uint i = from; i <= to; i += step) { - for (uint i = from; i <= to; i += step) - { - values.Add(i); - } + values.Add(i); } - else + } + else + { + for (uint i = from; i >= to && i <= from; i -= step) { - for (uint i = from; i >= to && i <= from; i -= step) - { - values.Add(i); - } + values.Add(i); } - - 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; } + 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; } } diff --git a/src/Xunit.Combinatorial/CombinatorialValuesAttribute.cs b/src/Xunit.Combinatorial/CombinatorialValuesAttribute.cs index 1367ef9..889067a 100644 --- a/src/Xunit.Combinatorial/CombinatorialValuesAttribute.cs +++ b/src/Xunit.Combinatorial/CombinatorialValuesAttribute.cs @@ -1,29 +1,28 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the Ms-PL license. See LICENSE file in the project root for full license information. -namespace Xunit +namespace Xunit; + +/// +/// Specifies which values for this parameter should be used for running the test method. +/// +[AttributeUsage(AttributeTargets.Parameter)] +public class CombinatorialValuesAttribute : Attribute { /// - /// Specifies which values for this parameter should be used for running the test method. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Parameter)] - public class CombinatorialValuesAttribute : Attribute + /// The values to pass to this parameter. + public CombinatorialValuesAttribute(params object?[]? values) { - /// - /// Initializes a new instance of the class. - /// - /// The values to pass to this parameter. - 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 }; - } - - /// - /// Gets the values that should be passed to this parameter on the test method. - /// - /// An array of values. - public object?[] Values { get; } + // 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 }; } + + /// + /// Gets the values that should be passed to this parameter on the test method. + /// + /// An array of values. + public object?[] Values { get; } } diff --git a/src/Xunit.Combinatorial/PairwiseDataAttribute.cs b/src/Xunit.Combinatorial/PairwiseDataAttribute.cs index 1ae126f..f7b47bf 100644 --- a/src/Xunit.Combinatorial/PairwiseDataAttribute.cs +++ b/src/Xunit.Combinatorial/PairwiseDataAttribute.cs @@ -4,35 +4,34 @@ using System.Reflection; using Xunit.Sdk; -namespace Xunit +namespace Xunit; + +/// +/// Provides a test method decorated with a +/// with arguments to run various combination of values for the +/// parameters taken by the test method using a pairwise strategy. +/// +public class PairwiseDataAttribute : DataAttribute { - /// - /// Provides a test method decorated with a - /// with arguments to run various combination of values for the - /// parameters taken by the test method using a pairwise strategy. - /// - public class PairwiseDataAttribute : DataAttribute + /// + public override IEnumerable GetData(MethodInfo testMethod) { - /// - public override IEnumerable GetData(MethodInfo testMethod) - { - Requires.NotNull(testMethod, nameof(testMethod)); + Requires.NotNull(testMethod, nameof(testMethod)); - ParameterInfo[]? parameters = testMethod.GetParameters(); - if (parameters.Length == 0) - { - return Enumerable.Empty(); - } - - var values = new List[parameters.Length]; - for (int i = 0; i < parameters.Length; i++) - { - values[i] = ValuesUtilities.GetValuesFor(parameters[i]).ToList(); - } + ParameterInfo[]? parameters = testMethod.GetParameters(); + if (parameters.Length == 0) + { + return Enumerable.Empty(); + } - List? testCaseInfo = PairwiseStrategy.GetTestCases(values.Select(v => v.Count).ToArray()); - return from testCase in testCaseInfo - select testCase.Select((j, i) => values[i][j]).ToArray(); + var values = new List[parameters.Length]; + for (int i = 0; i < parameters.Length; i++) + { + values[i] = ValuesUtilities.GetValuesFor(parameters[i]).ToList(); } + + List? testCaseInfo = PairwiseStrategy.GetTestCases(values.Select(v => v.Count).ToArray()); + return from testCase in testCaseInfo + select testCase.Select((j, i) => values[i][j]).ToArray(); } } diff --git a/src/Xunit.Combinatorial/PairwiseStrategy.cs b/src/Xunit.Combinatorial/PairwiseStrategy.cs index 8e118d7..1625516 100644 --- a/src/Xunit.Combinatorial/PairwiseStrategy.cs +++ b/src/Xunit.Combinatorial/PairwiseStrategy.cs @@ -24,566 +24,565 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // *********************************************************************** -namespace Xunit +namespace Xunit; + +/// +/// PairwiseStrategy creates test cases by combining the parameter +/// data so that all possible pairs of data items are used. +/// +/// +/// +/// The number of test cases that cover all possible pairs of test function +/// parameters values is significantly less than the number of test cases +/// that cover all possible combination of test function parameters values. +/// And because different studies show that most of software failures are +/// caused by combination of no more than two parameters, pairwise testing +/// can be an effective ways to test the system when it's impossible to test +/// all combinations of parameters. +/// +/// +/// The PairwiseStrategy code is based on "jenny" tool by Bob Jenkins: +/// . +/// +/// +internal static class PairwiseStrategy { + // NOTE: Terminology in this class is based on the literature + // relating to strategies for combining variable features when + // creating tests. This terminology is more closely related to + // higher level testing than it is to unit tests. See + // comments in the code for further explanations. + /// - /// PairwiseStrategy creates test cases by combining the parameter - /// data so that all possible pairs of data items are used. + /// Creates a set of test cases for specified dimensions. /// - /// - /// - /// The number of test cases that cover all possible pairs of test function - /// parameters values is significantly less than the number of test cases - /// that cover all possible combination of test function parameters values. - /// And because different studies show that most of software failures are - /// caused by combination of no more than two parameters, pairwise testing - /// can be an effective ways to test the system when it's impossible to test - /// all combinations of parameters. - /// - /// - /// The PairwiseStrategy code is based on "jenny" tool by Bob Jenkins: - /// . - /// - /// - internal static class PairwiseStrategy + /// + /// An array which contains information about dimensions. Each element of + /// this array represents a number of features in the specific dimension. + /// + /// + /// A set of test cases. + /// + public static List GetTestCases(int[] dimensions) { - // NOTE: Terminology in this class is based on the literature - // relating to strategies for combining variable features when - // creating tests. This terminology is more closely related to - // higher level testing than it is to unit tests. See - // comments in the code for further explanations. - - /// - /// Creates a set of test cases for specified dimensions. - /// - /// - /// An array which contains information about dimensions. Each element of - /// this array represents a number of features in the specific dimension. - /// - /// - /// A set of test cases. - /// - public static List GetTestCases(int[] dimensions) - { - return (from testCase in new PairwiseTestCaseGenerator().GetTestCases(dimensions) - select testCase.Features).ToList(); - } + return (from testCase in new PairwiseTestCaseGenerator().GetTestCases(dimensions) + select testCase.Features).ToList(); + } - private static bool IsTupleCovered(this TestCaseInfo testCaseInfo, FeatureTuple tuple) + private static bool IsTupleCovered(this TestCaseInfo testCaseInfo, FeatureTuple tuple) + { + for (int i = 0; i < tuple.Length; i++) { - for (int i = 0; i < tuple.Length; i++) + if (testCaseInfo.Features[tuple[i].Dimension] != tuple[i].Feature) { - if (testCaseInfo.Features[tuple[i].Dimension] != tuple[i].Feature) - { - return false; - } + return false; } - - return true; } + return true; + } + + /// + /// FleaRand is a pseudo-random number generator developed by Bob Jenkins: + /// . + /// + private class FleaRand + { + private uint b; + private uint c; + private uint d; + private uint z; + private uint[] m; + private uint[] r; + private uint q; + /// - /// FleaRand is a pseudo-random number generator developed by Bob Jenkins: - /// . + /// Initializes a new instance of the class. /// - private class FleaRand + /// The seed. + public FleaRand(uint seed) { - private uint b; - private uint c; - private uint d; - private uint z; - private uint[] m; - private uint[] r; - private uint q; - - /// - /// Initializes a new instance of the class. - /// - /// The seed. - public FleaRand(uint seed) + this.b = seed; + this.c = seed; + this.d = seed; + this.z = seed; + this.m = new uint[256]; + this.r = new uint[256]; + + for (int i = 0; i < this.m.Length; i++) { - this.b = seed; - this.c = seed; - this.d = seed; - this.z = seed; - this.m = new uint[256]; - this.r = new uint[256]; - - for (int i = 0; i < this.m.Length; i++) - { - this.m[i] = seed; - } - - for (int i = 0; i < 10; i++) - { - this.Batch(); - } - - this.q = 0; + this.m[i] = seed; } - public uint Next() + for (int i = 0; i < 10; i++) { - if (this.q == 0) - { - this.Batch(); - this.q = (uint)this.r.Length - 1; - } - else - { - this.q--; - } - - return this.r[this.q]; + this.Batch(); } - private void Batch() + this.q = 0; + } + + public uint Next() + { + if (this.q == 0) { - uint a; - uint b = this.b; - uint c = this.c + (++this.z); - uint d = this.d; + this.Batch(); + this.q = (uint)this.r.Length - 1; + } + else + { + this.q--; + } - for (int i = 0; i < this.r.Length; i++) - { - a = this.m[b % this.m.Length]; - this.m[b % this.m.Length] = d; - d = (c << 19) + (c >> 13) + b; - c = b ^ this.m[i]; - b = a + d; - this.r[i] = c; - } + return this.r[this.q]; + } + + private void Batch() + { + uint a; + uint b = this.b; + uint c = this.c + (++this.z); + uint d = this.d; - this.b = b; - this.c = c; - this.d = d; + for (int i = 0; i < this.r.Length; i++) + { + a = this.m[b % this.m.Length]; + this.m[b % this.m.Length] = d; + d = (c << 19) + (c >> 13) + b; + c = b ^ this.m[i]; + b = a + d; + this.r[i] = c; } + + this.b = b; + this.c = c; + this.d = d; } + } + /// + /// FeatureInfo represents coverage of a single value of test function + /// parameter, represented as a pair of indices, Dimension and Feature. In + /// terms of unit testing, Dimension is the index of the test parameter and + /// Feature is the index of the supplied value in that parameter's list of + /// sources. + /// + private class FeatureInfo + { /// - /// FeatureInfo represents coverage of a single value of test function - /// parameter, represented as a pair of indices, Dimension and Feature. In - /// terms of unit testing, Dimension is the index of the test parameter and - /// Feature is the index of the supplied value in that parameter's list of - /// sources. + /// Initializes a new instance of the class. /// - private class FeatureInfo + /// Index of a dimension. + /// Index of a feature. + public FeatureInfo(int dimension, int feature) { - /// - /// Initializes a new instance of the class. - /// - /// Index of a dimension. - /// Index of a feature. - public FeatureInfo(int dimension, int feature) - { - this.Dimension = dimension; - this.Feature = feature; - } + this.Dimension = dimension; + this.Feature = feature; + } - public int Dimension { get; } + public int Dimension { get; } - public int Feature { get; } - } + public int Feature { get; } + } + + /// + /// A FeatureTuple represents a combination of features, one per test + /// parameter, which should be covered by a test case. In the + /// PairwiseStrategy, we are only trying to cover pairs of features, so the + /// tuples actually may contain only single feature or pair of features, but + /// the algorithm itself works with triplets, quadruples and so on. + /// + private class FeatureTuple + { + private readonly FeatureInfo[] features; /// - /// A FeatureTuple represents a combination of features, one per test - /// parameter, which should be covered by a test case. In the - /// PairwiseStrategy, we are only trying to cover pairs of features, so the - /// tuples actually may contain only single feature or pair of features, but - /// the algorithm itself works with triplets, quadruples and so on. + /// Initializes a new instance of the class + /// for a single feature. /// - private class FeatureTuple + /// Single feature. + public FeatureTuple(FeatureInfo feature1) { - private readonly FeatureInfo[] features; - - /// - /// Initializes a new instance of the class - /// for a single feature. - /// - /// Single feature. - public FeatureTuple(FeatureInfo feature1) - { - this.features = new FeatureInfo[] { feature1 }; - } + this.features = new FeatureInfo[] { feature1 }; + } - /// - /// Initializes a new instance of the class - /// for a pair of features. - /// - /// First feature. - /// Second feature. - public FeatureTuple(FeatureInfo feature1, FeatureInfo feature2) - { - this.features = new FeatureInfo[] { feature1, feature2 }; - } + /// + /// Initializes a new instance of the class + /// for a pair of features. + /// + /// First feature. + /// Second feature. + public FeatureTuple(FeatureInfo feature1, FeatureInfo feature2) + { + this.features = new FeatureInfo[] { feature1, feature2 }; + } - public int Length + public int Length + { + get { - get - { - return this.features.Length; - } + return this.features.Length; } + } - public FeatureInfo this[int index] + public FeatureInfo this[int index] + { + get { - get - { - return this.features[index]; - } + return this.features[index]; } } + } + /// + /// TestCase represents a single test case covering a list of features. + /// + private class TestCaseInfo + { /// - /// TestCase represents a single test case covering a list of features. + /// Initializes a new instance of the class. /// - private class TestCaseInfo + /// A number of features in the test case. + public TestCaseInfo(int length) { - /// - /// Initializes a new instance of the class. - /// - /// A number of features in the test case. - public TestCaseInfo(int length) - { - this.Features = new int[length]; - } - - public int[] Features { get; } + this.Features = new int[length]; } + public int[] Features { get; } + } + + /// + /// PairwiseTestCaseGenerator class implements an algorithm which generates + /// a set of test cases which covers all pairs of possible values of test + /// function. + /// + /// + /// + /// The algorithm starts with creating a set of all feature tuples which we + /// will try to cover (see method). This set + /// includes every single feature and all possible pairs of features. We + /// store feature tuples in the 3-D collection (where axes are "dimension", + /// "feature", and "all combinations which includes this feature"), and for + /// every two feature (e.g. "A" and "B") we generate both ("A", "B") and + /// ("B", "A") pairs. This data structure extremely reduces the amount of + /// time needed to calculate coverage for a single test case (this + /// calculation is the most time-consuming part of the algorithm). + /// + /// + /// Then the algorithm picks one tuple from the uncovered tuple, creates a + /// test case that covers this tuple, and then removes this tuple and all + /// other tuples covered by this test case from the collection of uncovered + /// tuples. + /// + /// + /// Picking a tuple to cover. + /// + /// + /// There are no any special rules defined for picking tuples to cover. We + /// just pick them one by one, in the order they were generated. + /// + /// + /// Test generation. + /// + /// + /// Test generation starts from creating a completely random test case which + /// covers, nevertheless, previously selected tuple. Then the algorithm + /// tries to maximize number of tuples which this test covers. + /// + /// + /// Test generation and maximization process repeats seven times for every + /// selected tuple and then the algorithm picks the best test case ("seven" + /// is a magic number which provides good results in acceptable time). + /// + /// Maximizing test coverage. + /// + /// To maximize tests coverage, the algorithm walks thru the list of mutable + /// dimensions (mutable dimension is a dimension that are not included in + /// the previously selected tuple). Then for every dimension, the algorithm + /// walks thru the list of features and checks if this feature provides + /// better coverage than randomly selected feature, and if yes keeps this + /// feature. + /// + /// + /// This process repeats while it shows progress. If the last iteration + /// doesn't improve coverage, the process ends. + /// + /// + /// In addition, for better results, before start every iteration, the + /// algorithm "scrambles" dimensions - so for every iteration dimension + /// probes in a different order. + /// + /// + private class PairwiseTestCaseGenerator + { + private FleaRand? prng; + + private int[]? dimensions; + + private List[][]? uncoveredTuples; + /// - /// PairwiseTestCaseGenerator class implements an algorithm which generates - /// a set of test cases which covers all pairs of possible values of test - /// function. + /// Creates a set of test cases for specified dimensions. /// - /// - /// - /// The algorithm starts with creating a set of all feature tuples which we - /// will try to cover (see method). This set - /// includes every single feature and all possible pairs of features. We - /// store feature tuples in the 3-D collection (where axes are "dimension", - /// "feature", and "all combinations which includes this feature"), and for - /// every two feature (e.g. "A" and "B") we generate both ("A", "B") and - /// ("B", "A") pairs. This data structure extremely reduces the amount of - /// time needed to calculate coverage for a single test case (this - /// calculation is the most time-consuming part of the algorithm). - /// - /// - /// Then the algorithm picks one tuple from the uncovered tuple, creates a - /// test case that covers this tuple, and then removes this tuple and all - /// other tuples covered by this test case from the collection of uncovered - /// tuples. - /// - /// - /// Picking a tuple to cover. - /// - /// - /// There are no any special rules defined for picking tuples to cover. We - /// just pick them one by one, in the order they were generated. - /// - /// - /// Test generation. - /// - /// - /// Test generation starts from creating a completely random test case which - /// covers, nevertheless, previously selected tuple. Then the algorithm - /// tries to maximize number of tuples which this test covers. - /// - /// - /// Test generation and maximization process repeats seven times for every - /// selected tuple and then the algorithm picks the best test case ("seven" - /// is a magic number which provides good results in acceptable time). - /// - /// Maximizing test coverage. - /// - /// To maximize tests coverage, the algorithm walks thru the list of mutable - /// dimensions (mutable dimension is a dimension that are not included in - /// the previously selected tuple). Then for every dimension, the algorithm - /// walks thru the list of features and checks if this feature provides - /// better coverage than randomly selected feature, and if yes keeps this - /// feature. - /// - /// - /// This process repeats while it shows progress. If the last iteration - /// doesn't improve coverage, the process ends. - /// - /// - /// In addition, for better results, before start every iteration, the - /// algorithm "scrambles" dimensions - so for every iteration dimension - /// probes in a different order. - /// - /// - private class PairwiseTestCaseGenerator + /// + /// An array which contains information about dimensions. Each element of + /// this array represents a number of features in the specific dimension. + /// + /// + /// A set of test cases. + /// + public List GetTestCases(int[] dimensions) { - private FleaRand? prng; - - private int[]? dimensions; - - private List[][]? uncoveredTuples; - - /// - /// Creates a set of test cases for specified dimensions. - /// - /// - /// An array which contains information about dimensions. Each element of - /// this array represents a number of features in the specific dimension. - /// - /// - /// A set of test cases. - /// - public List GetTestCases(int[] dimensions) - { - this.prng = new FleaRand(15485863); - this.dimensions = dimensions; + this.prng = new FleaRand(15485863); + this.dimensions = dimensions; - this.CreateAllTuples(); + this.CreateAllTuples(); - List testCases = new List(); + List testCases = new List(); + + while (true) + { + FeatureTuple? tuple = this.GetNextTuple(); - while (true) + if (tuple is null) { - FeatureTuple? tuple = this.GetNextTuple(); + break; + } - if (tuple is null) - { - break; - } + TestCaseInfo? testCase = this.CreateTestCase(tuple); - TestCaseInfo? testCase = this.CreateTestCase(tuple); + this.RemoveTuplesCoveredByTest(testCase); - this.RemoveTuplesCoveredByTest(testCase); + testCases.Add(testCase); + } - testCases.Add(testCase); - } + return testCases; + } - return testCases; - } + private int GetNextRandomNumber() + { + return (int)(this.prng!.Next() >> 1); + } - private int GetNextRandomNumber() - { - return (int)(this.prng!.Next() >> 1); - } + private void CreateAllTuples() + { + this.uncoveredTuples = new List[this.dimensions!.Length][]; - private void CreateAllTuples() + for (int d = 0; d < this.dimensions.Length; d++) { - this.uncoveredTuples = new List[this.dimensions!.Length][]; + this.uncoveredTuples[d] = new List[this.dimensions[d]]; - for (int d = 0; d < this.dimensions.Length; d++) + for (int f = 0; f < this.dimensions[d]; f++) { - this.uncoveredTuples[d] = new List[this.dimensions[d]]; - - for (int f = 0; f < this.dimensions[d]; f++) - { - this.uncoveredTuples[d][f] = this.CreateTuples(d, f); - } + this.uncoveredTuples[d][f] = this.CreateTuples(d, f); } } + } - private List CreateTuples(int dimension, int feature) - { - List result = new List(); - - result.Add(new FeatureTuple(new FeatureInfo(dimension, feature))); - - for (int d = 0; d < this.dimensions!.Length; d++) - { - if (d != dimension) - { - for (int f = 0; f < this.dimensions[d]; f++) - { - result.Add(new FeatureTuple(new FeatureInfo(dimension, feature), new FeatureInfo(d, f))); - } - } - } + private List CreateTuples(int dimension, int feature) + { + List result = new List(); - return result; - } + result.Add(new FeatureTuple(new FeatureInfo(dimension, feature))); - private FeatureTuple? GetNextTuple() + for (int d = 0; d < this.dimensions!.Length; d++) { - for (int d = 0; d < this.uncoveredTuples!.Length; d++) + if (d != dimension) { - for (int f = 0; f < this.uncoveredTuples[d].Length; f++) + for (int f = 0; f < this.dimensions[d]; f++) { - List tuples = this.uncoveredTuples[d][f]; - - if (tuples.Count > 0) - { - FeatureTuple tuple = tuples[0]; - tuples.RemoveAt(0); - return tuple; - } + result.Add(new FeatureTuple(new FeatureInfo(dimension, feature), new FeatureInfo(d, f))); } } - - return null; } - private TestCaseInfo CreateTestCase(FeatureTuple tuple) - { - TestCaseInfo? bestTestCase = null; - int bestCoverage = -1; + return result; + } - for (int i = 0; i < 7; i++) + private FeatureTuple? GetNextTuple() + { + for (int d = 0; d < this.uncoveredTuples!.Length; d++) + { + for (int f = 0; f < this.uncoveredTuples[d].Length; f++) { - TestCaseInfo testCase = this.CreateRandomTestCase(tuple); - - int coverage = this.MaximizeCoverage(testCase, tuple); + List tuples = this.uncoveredTuples[d][f]; - if (coverage > bestCoverage) + if (tuples.Count > 0) { - bestTestCase = testCase; - bestCoverage = coverage; + FeatureTuple tuple = tuples[0]; + tuples.RemoveAt(0); + return tuple; } } - - return bestTestCase!; } - private TestCaseInfo CreateRandomTestCase(FeatureTuple tuple) + return null; + } + + private TestCaseInfo CreateTestCase(FeatureTuple tuple) + { + TestCaseInfo? bestTestCase = null; + int bestCoverage = -1; + + for (int i = 0; i < 7; i++) { - TestCaseInfo result = new TestCaseInfo(this.dimensions!.Length); + TestCaseInfo testCase = this.CreateRandomTestCase(tuple); - for (int d = 0; d < this.dimensions.Length; d++) - { - result.Features[d] = this.GetNextRandomNumber() % this.dimensions[d]; - } + int coverage = this.MaximizeCoverage(testCase, tuple); - for (int i = 0; i < tuple.Length; i++) + if (coverage > bestCoverage) { - result.Features[tuple[i].Dimension] = tuple[i].Feature; + bestTestCase = testCase; + bestCoverage = coverage; } + } - return result; + return bestTestCase!; + } + + private TestCaseInfo CreateRandomTestCase(FeatureTuple tuple) + { + TestCaseInfo result = new TestCaseInfo(this.dimensions!.Length); + + for (int d = 0; d < this.dimensions.Length; d++) + { + result.Features[d] = this.GetNextRandomNumber() % this.dimensions[d]; } - private int MaximizeCoverage(TestCaseInfo testCase, FeatureTuple tuple) + for (int i = 0; i < tuple.Length; i++) { - // It starts with one because we always have one tuple which is covered by the test. - int totalCoverage = 1; - int[] mutableDimensions = this.GetMutableDimensions(tuple); + result.Features[tuple[i].Dimension] = tuple[i].Feature; + } - while (true) - { - bool progress = false; + return result; + } - this.ScrambleDimensions(mutableDimensions); + private int MaximizeCoverage(TestCaseInfo testCase, FeatureTuple tuple) + { + // It starts with one because we always have one tuple which is covered by the test. + int totalCoverage = 1; + int[] mutableDimensions = this.GetMutableDimensions(tuple); - for (int i = 0; i < mutableDimensions.Length; i++) - { - int d = mutableDimensions[i]; + while (true) + { + bool progress = false; - int bestCoverage = this.CountTuplesCoveredByTest(testCase, d, testCase.Features[d]); + this.ScrambleDimensions(mutableDimensions); - int newCoverage = this.MaximizeCoverageForDimension(testCase, d, bestCoverage); + for (int i = 0; i < mutableDimensions.Length; i++) + { + int d = mutableDimensions[i]; - totalCoverage += newCoverage; + int bestCoverage = this.CountTuplesCoveredByTest(testCase, d, testCase.Features[d]); - if (newCoverage > bestCoverage) - { - progress = true; - } - } + int newCoverage = this.MaximizeCoverageForDimension(testCase, d, bestCoverage); - if (!progress) + totalCoverage += newCoverage; + + if (newCoverage > bestCoverage) { - return totalCoverage; + progress = true; } } - } - - private int[] GetMutableDimensions(FeatureTuple tuple) - { - List result = new List(); - bool[] immutableDimensions = new bool[this.dimensions!.Length]; - - for (int i = 0; i < tuple.Length; i++) + if (!progress) { - immutableDimensions[tuple[i].Dimension] = true; + return totalCoverage; } + } + } - for (int d = 0; d < this.dimensions.Length; d++) - { - if (!immutableDimensions[d]) - { - result.Add(d); - } - } + private int[] GetMutableDimensions(FeatureTuple tuple) + { + List result = new List(); - return result.ToArray(); + bool[] immutableDimensions = new bool[this.dimensions!.Length]; + + for (int i = 0; i < tuple.Length; i++) + { + immutableDimensions[tuple[i].Dimension] = true; } - private void ScrambleDimensions(int[] dimensions) + for (int d = 0; d < this.dimensions.Length; d++) { - for (int i = 0; i < dimensions.Length; i++) + if (!immutableDimensions[d]) { - int j = this.GetNextRandomNumber() % dimensions.Length; - int t = dimensions[i]; - dimensions[i] = dimensions[j]; - dimensions[j] = t; + result.Add(d); } } - private int MaximizeCoverageForDimension(TestCaseInfo testCase, int dimension, int bestCoverage) + return result.ToArray(); + } + + private void ScrambleDimensions(int[] dimensions) + { + for (int i = 0; i < dimensions.Length; i++) { - List bestFeatures = new List(this.dimensions![dimension]); + int j = this.GetNextRandomNumber() % dimensions.Length; + int t = dimensions[i]; + dimensions[i] = dimensions[j]; + dimensions[j] = t; + } + } - for (int f = 0; f < this.dimensions[dimension]; f++) - { - testCase.Features[dimension] = f; + private int MaximizeCoverageForDimension(TestCaseInfo testCase, int dimension, int bestCoverage) + { + List bestFeatures = new List(this.dimensions![dimension]); - int coverage = this.CountTuplesCoveredByTest(testCase, dimension, f); + for (int f = 0; f < this.dimensions[dimension]; f++) + { + testCase.Features[dimension] = f; - if (coverage >= bestCoverage) - { - if (coverage > bestCoverage) - { - bestCoverage = coverage; - bestFeatures.Clear(); - } + int coverage = this.CountTuplesCoveredByTest(testCase, dimension, f); - bestFeatures.Add(f); + if (coverage >= bestCoverage) + { + if (coverage > bestCoverage) + { + bestCoverage = coverage; + bestFeatures.Clear(); } + + bestFeatures.Add(f); } + } - testCase.Features[dimension] = bestFeatures[this.GetNextRandomNumber() % bestFeatures.Count]; + testCase.Features[dimension] = bestFeatures[this.GetNextRandomNumber() % bestFeatures.Count]; - return bestCoverage; - } + return bestCoverage; + } - private int CountTuplesCoveredByTest(TestCaseInfo testCase, int dimension, int feature) - { - int result = 0; + private int CountTuplesCoveredByTest(TestCaseInfo testCase, int dimension, int feature) + { + int result = 0; - List tuples = this.uncoveredTuples![dimension][feature]; + List tuples = this.uncoveredTuples![dimension][feature]; - for (int i = 0; i < tuples.Count; i++) + for (int i = 0; i < tuples.Count; i++) + { + if (testCase.IsTupleCovered(tuples[i])) { - if (testCase.IsTupleCovered(tuples[i])) - { - result++; - } + result++; } - - return result; } - private void RemoveTuplesCoveredByTest(TestCaseInfo testCase) + return result; + } + + private void RemoveTuplesCoveredByTest(TestCaseInfo testCase) + { + for (int d = 0; d < this.uncoveredTuples!.Length; d++) { - for (int d = 0; d < this.uncoveredTuples!.Length; d++) + for (int f = 0; f < this.uncoveredTuples[d].Length; f++) { - for (int f = 0; f < this.uncoveredTuples[d].Length; f++) - { - List tuples = this.uncoveredTuples[d][f]; + List tuples = this.uncoveredTuples[d][f]; - for (int i = tuples.Count - 1; i >= 0; i--) + for (int i = tuples.Count - 1; i >= 0; i--) + { + if (testCase.IsTupleCovered(tuples[i])) { - if (testCase.IsTupleCovered(tuples[i])) - { - tuples.RemoveAt(i); - } + tuples.RemoveAt(i); } } } diff --git a/src/Xunit.Combinatorial/PrivateErrorHelpers.cs b/src/Xunit.Combinatorial/PrivateErrorHelpers.cs index 26698b9..d894014 100644 --- a/src/Xunit.Combinatorial/PrivateErrorHelpers.cs +++ b/src/Xunit.Combinatorial/PrivateErrorHelpers.cs @@ -4,43 +4,42 @@ using System.Globalization; using System.Reflection; -namespace Xunit +namespace Xunit; + +/// +/// Common utility methods used by the various error detection and reporting classes. +/// +internal static class PrivateErrorHelpers { /// - /// Common utility methods used by the various error detection and reporting classes. + /// Trims away a given surrounding type, returning just the generic type argument, + /// if the given type is in fact a generic type with just one type argument and + /// the generic type matches a given wrapper type. Otherwise, it returns the original type. /// - internal static class PrivateErrorHelpers + /// The type to trim, or return unmodified. + /// The SomeType<> generic type definition to trim away from if it is present. + /// , if it is not a generic type instance of ; otherwise the type argument. + internal static Type TrimGenericWrapper(Type type, Type wrapper) { - /// - /// Trims away a given surrounding type, returning just the generic type argument, - /// if the given type is in fact a generic type with just one type argument and - /// the generic type matches a given wrapper type. Otherwise, it returns the original type. - /// - /// The type to trim, or return unmodified. - /// The SomeType<> generic type definition to trim away from if it is present. - /// , if it is not a generic type instance of ; otherwise the type argument. - internal static Type TrimGenericWrapper(Type type, Type wrapper) + Type[] typeArgs; + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == wrapper && (typeArgs = type.GetTypeInfo().GetGenericArguments()).Length == 1) { - Type[] typeArgs; - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == wrapper && (typeArgs = type.GetTypeInfo().GetGenericArguments()).Length == 1) - { - return typeArgs[0]; - } - else - { - return type; - } + return typeArgs[0]; } - - /// - /// Helper method that formats string arguments. - /// - /// The unformatted string. - /// The formatting arguments. - /// The formatted string. - internal static string Format(string format, params object[] arguments) + else { - return string.Format(CultureInfo.CurrentCulture, format, arguments); + return type; } } + + /// + /// Helper method that formats string arguments. + /// + /// The unformatted string. + /// The formatting arguments. + /// The formatted string. + internal static string Format(string format, params object[] arguments) + { + return string.Format(CultureInfo.CurrentCulture, format, arguments); + } } diff --git a/src/Xunit.Combinatorial/Requires.cs b/src/Xunit.Combinatorial/Requires.cs index 36bbeea..15d0e40 100644 --- a/src/Xunit.Combinatorial/Requires.cs +++ b/src/Xunit.Combinatorial/Requires.cs @@ -7,409 +7,404 @@ #pragma warning disable SA1611 // Element parameters must be documented -namespace Xunit +namespace Xunit; + +/// +/// Common runtime checks that throw ArgumentExceptions upon failure. +/// +internal static class Requires { /// - /// Common runtime checks that throw ArgumentExceptions upon failure. + /// Throws an exception if the specified parameter's value is null. /// - internal static class Requires + /// The type of the parameter. + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// The value of the parameter. + /// Thrown if is . + [DebuggerStepThrough] + public static T NotNull(T value, string parameterName) + where T : class // ensures value-types aren't passed to a null checking method { - /// - /// Throws an exception if the specified parameter's value is null. - /// - /// The type of the parameter. - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// The value of the parameter. - /// Thrown if is . - [DebuggerStepThrough] - public static T NotNull(T value, string parameterName) - where T : class // ensures value-types aren't passed to a null checking method + if (value is null) { - if (value is null) - { - throw new ArgumentNullException(parameterName); - } + throw new ArgumentNullException(parameterName); + } - return value; + return value; + } + + /// + /// Throws an exception if the specified parameter's value is IntPtr.Zero. + /// + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// The value of the parameter. + /// Thrown if is IntPtr.Zero. + [DebuggerStepThrough] + public static IntPtr NotNull(IntPtr value, string parameterName) + { + if (value == IntPtr.Zero) + { + throw new ArgumentNullException(parameterName); } - /// - /// Throws an exception if the specified parameter's value is IntPtr.Zero. - /// - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// The value of the parameter. - /// Thrown if is IntPtr.Zero. - [DebuggerStepThrough] - public static IntPtr NotNull(IntPtr value, string parameterName) + return value; + } + + /// + /// Throws an exception if the specified parameter's value is null. + /// + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// Thrown if is . + /// + /// This method allows async methods to use Requires.NotNull without having to assign the result + /// to local variables to avoid C# warnings. + /// + [DebuggerStepThrough] + public static void NotNull(System.Threading.Tasks.Task value, string parameterName) + { + if (value is null) { - if (value == IntPtr.Zero) - { - throw new ArgumentNullException(parameterName); - } + throw new ArgumentNullException(parameterName); + } + } - return value; + /// + /// Throws an exception if the specified parameter's value is null. + /// + /// The type of the return value of the task. + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// Thrown if is . + /// + /// This method allows async methods to use Requires.NotNull without having to assign the result + /// to local variables to avoid C# warnings. + /// + [DebuggerStepThrough] + public static void NotNull(System.Threading.Tasks.Task value, string parameterName) + { + if (value is null) + { + throw new ArgumentNullException(parameterName); } + } -#if !NET35 - /// - /// Throws an exception if the specified parameter's value is null. - /// - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// Thrown if is . - /// - /// This method allows async methods to use Requires.NotNull without having to assign the result - /// to local variables to avoid C# warnings. - /// - [DebuggerStepThrough] - public static void NotNull(System.Threading.Tasks.Task value, string parameterName) + /// + /// Throws an exception if the specified parameter's value is null. + /// + /// The type of the parameter. + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// The value of the parameter. + /// Thrown if is . + /// + /// This method exists for callers who themselves only know the type as a generic parameter which + /// may or may not be a class, but certainly cannot be null. + /// + [DebuggerStepThrough] + public static T NotNullAllowStructs(T value, string parameterName) + { + if (value is null) { - if (value is null) - { - throw new ArgumentNullException(parameterName); - } + throw new ArgumentNullException(parameterName); } - /// - /// Throws an exception if the specified parameter's value is null. - /// - /// The type of the return value of the task. - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// Thrown if is . - /// - /// This method allows async methods to use Requires.NotNull without having to assign the result - /// to local variables to avoid C# warnings. - /// - [DebuggerStepThrough] - public static void NotNull(System.Threading.Tasks.Task value, string parameterName) + return value; + } + + /// + /// Throws an exception if the specified parameter's value is null or empty. + /// + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// Thrown if is or empty. + [DebuggerStepThrough] + public static void NotNullOrEmpty(string value, string parameterName) + { + // To the guy that is doing random code cleaning: + // Consider the perfomance when changing the code to delegate to NotNull. + // In general do not chain call to another function, check first and return as earlier as possible. + if (value is null) { - if (value is null) - { - throw new ArgumentNullException(parameterName); - } + throw new ArgumentNullException(parameterName); } -#endif - - /// - /// Throws an exception if the specified parameter's value is null. - /// - /// The type of the parameter. - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// The value of the parameter. - /// Thrown if is . - /// - /// This method exists for callers who themselves only know the type as a generic parameter which - /// may or may not be a class, but certainly cannot be null. - /// - [DebuggerStepThrough] - public static T NotNullAllowStructs(T value, string parameterName) + + if (value.Length == 0 || value[0] == '\0') { - if (value is null) - { - throw new ArgumentNullException(parameterName); - } + throw new ArgumentException(Format(Strings.Argument_EmptyString, parameterName), parameterName); + } + } - return value; + /// + /// Throws an exception if the specified parameter's value is null, empty, or whitespace. + /// + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// Thrown if is or empty. + [DebuggerStepThrough] + public static void NotNullOrWhiteSpace(string value, string parameterName) + { + // To the guy that is doing random code cleaning: + // Consider the perfomance when changing the code to delegate to NotNull. + // In general do not chain call to another function, check first and return as earlier as possible. + if (value is null) + { + throw new ArgumentNullException(parameterName); } - /// - /// Throws an exception if the specified parameter's value is null or empty. - /// - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// Thrown if is or empty. - [DebuggerStepThrough] - public static void NotNullOrEmpty(string value, string parameterName) + if (value.Length == 0 || value[0] == '\0') { - // To the guy that is doing random code cleaning: - // Consider the perfomance when changing the code to delegate to NotNull. - // In general do not chain call to another function, check first and return as earlier as possible. - if (value is null) - { - throw new ArgumentNullException(parameterName); - } + throw new ArgumentException(Format(Strings.Argument_EmptyString, parameterName), parameterName); + } - if (value.Length == 0 || value[0] == '\0') - { - throw new ArgumentException(Format(Strings.Argument_EmptyString, parameterName), parameterName); - } + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException(Format(Strings.Argument_Whitespace, parameterName)); } + } -#if !NET35 - /// - /// Throws an exception if the specified parameter's value is null, empty, or whitespace. - /// - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// Thrown if is or empty. - [DebuggerStepThrough] - public static void NotNullOrWhiteSpace(string value, string parameterName) + /// + /// Throws an exception if the specified parameter's value is null, + /// has no elements or has an element with a null value. + /// + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// Thrown if the tested condition is false. + [DebuggerStepThrough] + public static void NotNullOrEmpty(System.Collections.IEnumerable values, string parameterName) + { + // To the guy that is doing random code cleaning: + // Consider the perfomance when changing the code to delegate to NotNull. + // In general do not chain call to another function, check first and return as earlier as possible. + if (values is null) { - // To the guy that is doing random code cleaning: - // Consider the perfomance when changing the code to delegate to NotNull. - // In general do not chain call to another function, check first and return as earlier as possible. - if (value is null) - { - throw new ArgumentNullException(parameterName); - } + throw new ArgumentNullException(parameterName); + } - if (value.Length == 0 || value[0] == '\0') - { - throw new ArgumentException(Format(Strings.Argument_EmptyString, parameterName), parameterName); - } + ICollection? collection = values as ICollection; - if (string.IsNullOrWhiteSpace(value)) + if (collection is not null) + { + if (collection.Count > 0) { - throw new ArgumentException(Format(Strings.Argument_Whitespace, parameterName)); + return; } + + throw new ArgumentException(Format(Strings.Argument_EmptyArray, parameterName), parameterName); } -#endif - - /// - /// Throws an exception if the specified parameter's value is null, - /// has no elements or has an element with a null value. - /// - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// Thrown if the tested condition is false. - [DebuggerStepThrough] - public static void NotNullOrEmpty(System.Collections.IEnumerable values, string parameterName) + + IEnumerator enumerator = values.GetEnumerator(); + + using (enumerator as IDisposable) { - // To the guy that is doing random code cleaning: - // Consider the perfomance when changing the code to delegate to NotNull. - // In general do not chain call to another function, check first and return as earlier as possible. - if (values is null) + if (enumerator.MoveNext()) { - throw new ArgumentNullException(parameterName); + return; } + } - ICollection? collection = values as ICollection; - - if (collection is not null) - { - if (collection.Count > 0) - { - return; - } + throw new ArgumentException(Format(Strings.Argument_EmptyArray, parameterName), parameterName); + } - throw new ArgumentException(Format(Strings.Argument_EmptyArray, parameterName), parameterName); - } + /// + /// Throws an exception if the specified parameter's value is null, + /// has no elements or has an element with a null value. + /// + /// The type of the elements in the sequence. + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// Thrown if the tested condition is false. + [DebuggerStepThrough] + public static void NotNullEmptyOrNullElements(IEnumerable values, string parameterName) + where T : class // ensures value-types aren't passed to a null checking method + { + NotNull(values, parameterName); - IEnumerator enumerator = values.GetEnumerator(); + bool hasElements = false; + foreach (T value in values) + { + hasElements = true; - using (enumerator as IDisposable) + if (value is null) { - if (enumerator.MoveNext()) - { - return; - } + throw new ArgumentException(Format(Strings.Argument_NullElement, parameterName), parameterName); } + } + if (!hasElements) + { throw new ArgumentException(Format(Strings.Argument_EmptyArray, parameterName), parameterName); } + } - /// - /// Throws an exception if the specified parameter's value is null, - /// has no elements or has an element with a null value. - /// - /// The type of the elements in the sequence. - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// Thrown if the tested condition is false. - [DebuggerStepThrough] - public static void NotNullEmptyOrNullElements(IEnumerable values, string parameterName) - where T : class // ensures value-types aren't passed to a null checking method + /// + /// Throws an exception if the specified parameter's value is not null + /// and has an element with a null value. + /// + /// The type of the elements in the sequence. + /// The value of the argument. + /// The name of the parameter to include in any thrown exception. + /// Thrown if the tested condition is false. + [DebuggerStepThrough] + public static void NullOrNotNullElements(IEnumerable values, string parameterName) + { + if (values is not null) { - NotNull(values, parameterName); - - bool hasElements = false; foreach (T value in values) { - hasElements = true; - if (value is null) { throw new ArgumentException(Format(Strings.Argument_NullElement, parameterName), parameterName); } } - - if (!hasElements) - { - throw new ArgumentException(Format(Strings.Argument_EmptyArray, parameterName), parameterName); - } } + } - /// - /// Throws an exception if the specified parameter's value is not null - /// and has an element with a null value. - /// - /// The type of the elements in the sequence. - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// Thrown if the tested condition is false. - [DebuggerStepThrough] - public static void NullOrNotNullElements(IEnumerable values, string parameterName) + /// + /// Throws an if a condition does not evaluate to true. + /// + [DebuggerStepThrough] + public static void Range(bool condition, string parameterName, string? message = null) + { + if (!condition) { - if (values is not null) - { - foreach (T value in values) - { - if (value is null) - { - throw new ArgumentException(Format(Strings.Argument_NullElement, parameterName), parameterName); - } - } - } + FailRange(parameterName, message); } + } - /// - /// Throws an if a condition does not evaluate to true. - /// - [DebuggerStepThrough] - public static void Range(bool condition, string parameterName, string? message = null) + /// + /// Throws an if a condition does not evaluate to true. + /// + /// Nothing. This method always throws. + [DebuggerStepThrough] + public static Exception FailRange(string parameterName, string? message = null) + { + if (string.IsNullOrEmpty(message)) { - if (!condition) - { - FailRange(parameterName, message); - } + throw new ArgumentOutOfRangeException(parameterName); } - - /// - /// Throws an if a condition does not evaluate to true. - /// - /// Nothing. This method always throws. - [DebuggerStepThrough] - public static Exception FailRange(string parameterName, string? message = null) + else { - if (string.IsNullOrEmpty(message)) - { - throw new ArgumentOutOfRangeException(parameterName); - } - else - { - throw new ArgumentOutOfRangeException(parameterName, message); - } + throw new ArgumentOutOfRangeException(parameterName, message); } + } - /// - /// Throws an ArgumentException if a condition does not evaluate to true. - /// - [DebuggerStepThrough] - public static void Argument(bool condition, string parameterName, string message) + /// + /// Throws an ArgumentException if a condition does not evaluate to true. + /// + [DebuggerStepThrough] + public static void Argument(bool condition, string parameterName, string message) + { + if (!condition) { - if (!condition) - { - throw new ArgumentException(message, parameterName); - } + throw new ArgumentException(message, parameterName); } + } - /// - /// Throws an ArgumentException if a condition does not evaluate to true. - /// - [DebuggerStepThrough] - public static void Argument(bool condition, string parameterName, string message, object arg1) + /// + /// Throws an ArgumentException if a condition does not evaluate to true. + /// + [DebuggerStepThrough] + public static void Argument(bool condition, string parameterName, string message, object arg1) + { + if (!condition) { - if (!condition) - { - throw new ArgumentException(Format(message, arg1), parameterName); - } + throw new ArgumentException(Format(message, arg1), parameterName); } + } - /// - /// Throws an ArgumentException if a condition does not evaluate to true. - /// - [DebuggerStepThrough] - public static void Argument(bool condition, string parameterName, string message, object arg1, object arg2) + /// + /// Throws an ArgumentException if a condition does not evaluate to true. + /// + [DebuggerStepThrough] + public static void Argument(bool condition, string parameterName, string message, object arg1, object arg2) + { + if (!condition) { - if (!condition) - { - throw new ArgumentException(Format(message, arg1, arg2), parameterName); - } + throw new ArgumentException(Format(message, arg1, arg2), parameterName); } + } - /// - /// Throws an ArgumentException if a condition does not evaluate to true. - /// - [DebuggerStepThrough] - public static void Argument(bool condition, string parameterName, string message, params object[] args) + /// + /// Throws an ArgumentException if a condition does not evaluate to true. + /// + [DebuggerStepThrough] + public static void Argument(bool condition, string parameterName, string message, params object[] args) + { + if (!condition) { - if (!condition) - { - throw new ArgumentException(Format(message, args), parameterName); - } + throw new ArgumentException(Format(message, args), parameterName); } + } - /// - /// Validates some expression describing the acceptable condition for an argument evaluates to true. - /// - /// The expression that must evaluate to true to avoid an . - /// Name of the parameter. - /// The unformatted message. - /// Formatting arguments. - [DebuggerStepThrough] - public static void That(bool condition, string parameterName, string unformattedMessage, params object[] args) + /// + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// + /// The expression that must evaluate to true to avoid an . + /// Name of the parameter. + /// The unformatted message. + /// Formatting arguments. + [DebuggerStepThrough] + public static void That(bool condition, string parameterName, string unformattedMessage, params object[] args) + { + if (!condition) { - if (!condition) - { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, unformattedMessage, args), parameterName); - } + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, unformattedMessage, args), parameterName); } + } - /// - /// Validates some expression describing the acceptable condition for an argument evaluates to true. - /// - /// The expression that must evaluate to true to avoid an . - /// The message to include with the exception. - [DebuggerStepThrough] - public static void ValidState(bool condition, string message) + /// + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// + /// The expression that must evaluate to true to avoid an . + /// The message to include with the exception. + [DebuggerStepThrough] + public static void ValidState(bool condition, string message) + { + if (!condition) { - if (!condition) - { - throw new InvalidOperationException(message); - } + throw new InvalidOperationException(message); } + } - /// - /// Throws an ArgumentException. - /// - /// Nothing. It always throws. - [DebuggerStepThrough] - public static Exception Fail(string message) - { - throw new ArgumentException(message); - } + /// + /// Throws an ArgumentException. + /// + /// Nothing. It always throws. + [DebuggerStepThrough] + public static Exception Fail(string message) + { + throw new ArgumentException(message); + } - /// - /// Throws an ArgumentException. - /// - /// Nothing. It always throws. - [DebuggerStepThrough] - public static Exception Fail(string unformattedMessage, params object[] args) - { - throw Fail(Format(unformattedMessage, args)); - } + /// + /// Throws an ArgumentException. + /// + /// Nothing. It always throws. + [DebuggerStepThrough] + public static Exception Fail(string unformattedMessage, params object[] args) + { + throw Fail(Format(unformattedMessage, args)); + } - /// - /// Throws an ArgumentException. - /// - /// Nothing. This method always throws. But the signature allows calling code to "throw" this method for C# syntax reasons. - [DebuggerStepThrough] - public static Exception Fail(Exception innerException, string unformattedMessage, params object[] args) - { - throw new ArgumentException(Format(unformattedMessage, args), innerException); - } + /// + /// Throws an ArgumentException. + /// + /// Nothing. This method always throws. But the signature allows calling code to "throw" this method for C# syntax reasons. + [DebuggerStepThrough] + public static Exception Fail(Exception innerException, string unformattedMessage, params object[] args) + { + throw new ArgumentException(Format(unformattedMessage, args), innerException); + } - /// - /// Helper method that formats string arguments. - /// - /// The formatted string. - private static string Format(string format, params object[] arguments) - { - return PrivateErrorHelpers.Format(format, arguments); - } + /// + /// Helper method that formats string arguments. + /// + /// The formatted string. + private static string Format(string format, params object[] arguments) + { + return PrivateErrorHelpers.Format(format, arguments); } } diff --git a/src/Xunit.Combinatorial/Strings.Designer.cs b/src/Xunit.Combinatorial/Strings.Designer.cs index 0f72dbd..c2009b4 100644 --- a/src/Xunit.Combinatorial/Strings.Designer.cs +++ b/src/Xunit.Combinatorial/Strings.Designer.cs @@ -8,147 +8,146 @@ // //------------------------------------------------------------------------------ -namespace Xunit { - using System; - using System.Reflection; +namespace Xunit; +using System; +using System.Reflection; + + +/// +/// A strongly-typed resource class, for looking up localized strings, etc. +/// +// This class was auto-generated by the StronglyTypedResourceBuilder +// class via a tool like ResGen or Visual Studio. +// To add or remove a member, edit your .ResX file then rerun ResGen +// with the /str option, or rebuild your VS project. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] +[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } /// - /// A strongly-typed resource class, for looking up localized strings, etc. + /// Returns the cached ResourceManager instance used by this class. /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Strings() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xunit.Strings", typeof(Strings).GetTypeInfo().Assembly); - resourceMan = temp; - } - return resourceMan; + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xunit.Strings", typeof(Strings).GetTypeInfo().Assembly); + resourceMan = temp; } + return resourceMan; } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; } - - /// - /// Looks up a localized string similar to '{0}' must contain at least one element.. - /// - internal static string Argument_EmptyArray { - get { - return ResourceManager.GetString("Argument_EmptyArray", resourceCulture); - } + set { + resourceCulture = value; } - - /// - /// Looks up a localized string similar to '{0}' cannot be an empty string ("") or start with the null character.. - /// - internal static string Argument_EmptyString { - get { - return ResourceManager.GetString("Argument_EmptyString", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to '{0}' must contain at least one element.. + /// + internal static string Argument_EmptyArray { + get { + return ResourceManager.GetString("Argument_EmptyArray", resourceCulture); } - - /// - /// Looks up a localized string similar to '{0}' cannot contain a null (Nothing in Visual Basic) element.. - /// - internal static string Argument_NullElement { - get { - return ResourceManager.GetString("Argument_NullElement", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to '{0}' cannot be an empty string ("") or start with the null character.. + /// + internal static string Argument_EmptyString { + get { + return ResourceManager.GetString("Argument_EmptyString", resourceCulture); } - - /// - /// Looks up a localized string similar to The parameter "{0}" cannot consist entirely of white space characters.. - /// - internal static string Argument_Whitespace { - get { - return ResourceManager.GetString("Argument_Whitespace", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to '{0}' cannot contain a null (Nothing in Visual Basic) element.. + /// + internal static string Argument_NullElement { + get { + return ResourceManager.GetString("Argument_NullElement", resourceCulture); } - - /// - /// Looks up a localized string similar to An internal error occurred. Please contact customer support.. - /// - internal static string InternalExceptionMessage { - get { - return ResourceManager.GetString("InternalExceptionMessage", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to The parameter "{0}" cannot consist entirely of white space characters.. + /// + internal static string Argument_Whitespace { + get { + return ResourceManager.GetString("Argument_Whitespace", resourceCulture); } - - /// - /// Looks up a localized string similar to {0} must not exceed the length of the range from {1} to {2}.. - /// - internal static string MoreRandomValuesRequestedThanPossibleOnes { - get { - return ResourceManager.GetString("MoreRandomValuesRequestedThanPossibleOnes", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to An internal error occurred. Please contact customer support.. + /// + internal static string InternalExceptionMessage { + get { + return ResourceManager.GetString("InternalExceptionMessage", resourceCulture); } - - /// - /// Looks up a localized string similar to Cannot find an instance of the {0} service.. - /// - internal static string ServiceMissing { - get { - return ResourceManager.GetString("ServiceMissing", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to {0} must not exceed the length of the range from {1} to {2}.. + /// + internal static string MoreRandomValuesRequestedThanPossibleOnes { + get { + return ResourceManager.GetString("MoreRandomValuesRequestedThanPossibleOnes", resourceCulture); } - - /// - /// Looks up a localized string similar to We are unable to generate the desired number of unique random values because too many non-unique random numbers are coming from the random number generator. Try reducing your target count or expanding your allowed range.. - /// - internal static string TooManyRandomCollisions { - get { - return ResourceManager.GetString("TooManyRandomCollisions", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Cannot find an instance of the {0} service.. + /// + internal static string ServiceMissing { + get { + return ResourceManager.GetString("ServiceMissing", resourceCulture); } - - /// - /// Looks up a localized string similar to The value for {0} must be positive.. - /// - internal static string ValueMustBePositive { - get { - return ResourceManager.GetString("ValueMustBePositive", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to We are unable to generate the desired number of unique random values because too many non-unique random numbers are coming from the random number generator. Try reducing your target count or expanding your allowed range.. + /// + internal static string TooManyRandomCollisions { + get { + return ResourceManager.GetString("TooManyRandomCollisions", resourceCulture); } - - /// - /// Looks up a localized string similar to The value of {0} must not be greater than the value of {1}.. - /// - internal static string XMustNotBeGreaterThanY { - get { - return ResourceManager.GetString("XMustNotBeGreaterThanY", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to The value for {0} must be positive.. + /// + internal static string ValueMustBePositive { + get { + return ResourceManager.GetString("ValueMustBePositive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value of {0} must not be greater than the value of {1}.. + /// + internal static string XMustNotBeGreaterThanY { + get { + return ResourceManager.GetString("XMustNotBeGreaterThanY", resourceCulture); } } } diff --git a/src/Xunit.Combinatorial/TypeInfoExtensions.cs b/src/Xunit.Combinatorial/TypeInfoExtensions.cs index 7c04268..431fa5f 100644 --- a/src/Xunit.Combinatorial/TypeInfoExtensions.cs +++ b/src/Xunit.Combinatorial/TypeInfoExtensions.cs @@ -3,22 +3,21 @@ using System.Reflection; -namespace Xunit +namespace Xunit; + +/// +/// Extension methods for the class to emulate older reflection APIs. +/// +internal static class TypeInfoExtensions { /// - /// Extension methods for the class to emulate older reflection APIs. + /// Returns the generic type arguments of specified type. /// - internal static class TypeInfoExtensions - { - /// - /// Returns the generic type arguments of specified type. - /// - /// The type whose generic type arguments should be returned. - /// An array of types. - /// - /// This silly method allows the same code to compile against the newer - /// as well as older Reflection APIs. - /// - internal static Type[] GetGenericArguments(this TypeInfo type) => type.GenericTypeArguments; - } + /// The type whose generic type arguments should be returned. + /// An array of types. + /// + /// This silly method allows the same code to compile against the newer + /// as well as older Reflection APIs. + /// + internal static Type[] GetGenericArguments(this TypeInfo type) => type.GenericTypeArguments; } diff --git a/src/Xunit.Combinatorial/ValuesUtilities.cs b/src/Xunit.Combinatorial/ValuesUtilities.cs index bda970a..293c69d 100644 --- a/src/Xunit.Combinatorial/ValuesUtilities.cs +++ b/src/Xunit.Combinatorial/ValuesUtilities.cs @@ -4,128 +4,127 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; -namespace Xunit +namespace Xunit; + +/// +/// Utility methods for generating values for test parameters. +/// +internal static class ValuesUtilities { /// - /// Utility methods for generating values for test parameters. + /// Gets a sequence of values that should be tested for the specified parameter. /// - internal static class ValuesUtilities + /// The parameter to get possible values for. + /// A sequence of values for the parameter. + internal static IEnumerable GetValuesFor(ParameterInfo parameter) { - /// - /// Gets a sequence of values that should be tested for the specified parameter. - /// - /// The parameter to get possible values for. - /// A sequence of values for the parameter. - internal static IEnumerable GetValuesFor(ParameterInfo parameter) + Requires.NotNull(parameter, nameof(parameter)); { - Requires.NotNull(parameter, nameof(parameter)); + CombinatorialValuesAttribute? attribute = parameter.GetCustomAttribute(); + if (attribute is not null) { - CombinatorialValuesAttribute? attribute = parameter.GetCustomAttribute(); - if (attribute is not null) - { - return attribute.Values; - } + return attribute.Values; } + } + { + CombinatorialRangeAttribute? attribute = parameter.GetCustomAttribute(); + if (attribute is not null) { - CombinatorialRangeAttribute? attribute = parameter.GetCustomAttribute(); - if (attribute is not null) - { - return attribute.Values; - } + return attribute.Values; } + } + { + CombinatorialRandomDataAttribute? attribute = parameter.GetCustomAttribute(); + if (attribute is not null) { - CombinatorialRandomDataAttribute? attribute = parameter.GetCustomAttribute(); - if (attribute is not null) - { - return attribute.Values; - } + return attribute.Values; } + } + { + CombinatorialMemberDataAttribute? attribute = parameter.GetCustomAttribute(); + if (attribute is not null) { - CombinatorialMemberDataAttribute? attribute = parameter.GetCustomAttribute(); - if (attribute is not null) - { - return attribute.GetValues(parameter); - } + return attribute.GetValues(parameter); } - - return GetValuesFor(parameter.ParameterType); } - /// - /// Gets a sequence of values that should be tested for the specified type. - /// - /// The type to get possible values for. - /// A sequence of values for the . - internal static IEnumerable GetValuesFor(Type dataType) - { - Requires.NotNull(dataType, nameof(dataType)); + return GetValuesFor(parameter.ParameterType); + } - if (dataType == typeof(bool)) - { - yield return true; - yield return false; - } - else if (dataType == typeof(int)) - { - yield return 0; - yield return 1; - } - else if (dataType.GetTypeInfo().IsEnum) - { - foreach (string name in Enum.GetNames(dataType)) - { - yield return Enum.Parse(dataType, name); - } - } - else if (IsNullable(dataType, out Type? innerDataType)) + /// + /// Gets a sequence of values that should be tested for the specified type. + /// + /// The type to get possible values for. + /// A sequence of values for the . + internal static IEnumerable GetValuesFor(Type dataType) + { + Requires.NotNull(dataType, nameof(dataType)); + + if (dataType == typeof(bool)) + { + yield return true; + yield return false; + } + else if (dataType == typeof(int)) + { + yield return 0; + yield return 1; + } + else if (dataType.GetTypeInfo().IsEnum) + { + foreach (string name in Enum.GetNames(dataType)) { - yield return null; - foreach (object? value in GetValuesFor(innerDataType)) - { - yield return value; - } + yield return Enum.Parse(dataType, name); } - else + } + else if (IsNullable(dataType, out Type? innerDataType)) + { + yield return null; + foreach (object? value in GetValuesFor(innerDataType)) { - throw new NotSupportedException(); + yield return value; } } - - /// - /// Determines whether is - /// and extracts the inner type, if any. - /// - /// - /// The type to test whether it is . - /// - /// - /// When this method returns, contains the inner type of the Nullable, if the - /// type is Nullable is found; otherwise, null. - /// - /// - /// if the type is a Nullable type; otherwise . - /// - private static bool IsNullable(Type dataType, [NotNullWhen(true)] out Type? innerDataType) + else { - innerDataType = null; + throw new NotSupportedException(); + } + } - TypeInfo? ti = dataType.GetTypeInfo(); + /// + /// Determines whether is + /// and extracts the inner type, if any. + /// + /// + /// The type to test whether it is . + /// + /// + /// When this method returns, contains the inner type of the Nullable, if the + /// type is Nullable is found; otherwise, null. + /// + /// + /// if the type is a Nullable type; otherwise . + /// + private static bool IsNullable(Type dataType, [NotNullWhen(true)] out Type? innerDataType) + { + innerDataType = null; - if (!ti.IsGenericType) - { - return false; - } + TypeInfo? ti = dataType.GetTypeInfo(); - if (ti.GetGenericTypeDefinition() != typeof(Nullable<>)) - { - return false; - } + if (!ti.IsGenericType) + { + return false; + } - innerDataType = ti.GenericTypeArguments[0]; - return true; + if (ti.GetGenericTypeDefinition() != typeof(Nullable<>)) + { + return false; } + + innerDataType = ti.GenericTypeArguments[0]; + return true; } } diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialDataAttributeTests.cs b/test/Xunit.Combinatorial.Tests/CombinatorialDataAttributeTests.cs index 7bf6390..565efdc 100644 --- a/test/Xunit.Combinatorial.Tests/CombinatorialDataAttributeTests.cs +++ b/test/Xunit.Combinatorial.Tests/CombinatorialDataAttributeTests.cs @@ -12,105 +12,103 @@ public class CombinatorialDataAttributeTests [Fact] public void GetData_NoArguments() { - AssertData(new object[][] - { - }); + AssertData([]); } [Fact] public void GetData_Bool() { - AssertData(new object[][] - { - new object[] { true }, - new object[] { false }, - }); + AssertData( + [ + [true], + [false], + ]); } [Fact] public void GetData_BoolBool() { - AssertData(new object[][] - { - new object[] { true, true }, - new object[] { true, false }, - new object[] { false, true }, - new object[] { false, false }, - }); + AssertData( + [ + [true, true], + [true, false], + [false, true], + [false, false], + ]); } [Fact] public void GetData_Int() { - AssertData(new object[][] - { - new object[] { 0 }, - new object[] { 1 }, - }); + AssertData( + [ + [0], + [1], + ]); } [Fact] public void GetData_NullableInt() { - AssertData(new object?[][] - { - new object?[] { null }, - new object?[] { 0 }, - new object?[] { 1 }, - }); + AssertData( + [ + [null], + [0], + [1], + ]); } [Fact] public void GetData_Int_35() { - AssertData(new object[][] - { - new object[] { 3 }, - new object[] { 5 }, - }); + AssertData( + [ + [3], + [5], + ]); } [Fact] public void GetData_string_int_bool_Values() { - AssertData(new object[][] - { - new object[] { "a", 2, true }, - new object[] { "a", 2, false }, - new object[] { "a", 4, true }, - new object[] { "a", 4, false }, - new object[] { "a", 6, true }, - new object[] { "a", 6, false }, - new object[] { "b", 2, true }, - new object[] { "b", 2, false }, - new object[] { "b", 4, true }, - new object[] { "b", 4, false }, - new object[] { "b", 6, true }, - new object[] { "b", 6, false }, - }); + AssertData( + [ + ["a", 2, true], + ["a", 2, false], + ["a", 4, true], + ["a", 4, false], + ["a", 6, true], + ["a", 6, false], + ["b", 2, true], + ["b", 2, false], + ["b", 4, true], + ["b", 4, false], + ["b", 6, true], + ["b", 6, false], + ]); } [Fact] public void GetData_DateTimeKind() { - AssertData(new object[][] - { - new object[] { DateTimeKind.Unspecified }, - new object[] { DateTimeKind.Utc }, - new object[] { DateTimeKind.Local }, - }); + AssertData( + [ + [DateTimeKind.Unspecified], + [DateTimeKind.Utc], + [DateTimeKind.Local], + ]); } [Fact] public void GetData_NullableDateTimeKind() { - AssertData(new object?[][] - { - new object?[] { null }, - new object?[] { DateTimeKind.Unspecified }, - new object?[] { DateTimeKind.Utc }, - new object?[] { DateTimeKind.Local }, - }); + AssertData( + [ + [null], + [DateTimeKind.Unspecified], + [DateTimeKind.Utc], + [DateTimeKind.Local], + ]); } [Fact] @@ -134,12 +132,11 @@ public void GetData_CustomDataFromDerivedAttriute() MethodInfo testhelperMethodInfo = this.GetType().GetMethod(nameof(this.SomeTestWithCustomValues), BindingFlags.Instance | BindingFlags.NonPublic)!; IEnumerable actual = att.GetData(testhelperMethodInfo); Assert.Equal( - new[] - { - new object[] { 5 }, - new object[] { 10 }, - new object[] { 15 }, - }, + [ + [5], + [10], + [15], + ], actual); } @@ -260,7 +257,7 @@ private void SomeTestWithCustomValues([CustomValues] int a) private class CustomValuesAttribute : CombinatorialValuesAttribute { public CustomValuesAttribute() - : base(new object[] { 5, 10, 15 }) + : base([5, 10, 15]) { } } diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataAttributeTests.cs b/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataAttributeTests.cs index ba8aec0..932666a 100644 --- a/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataAttributeTests.cs +++ b/test/Xunit.Combinatorial.Tests/CombinatorialMemberDataAttributeTests.cs @@ -17,7 +17,7 @@ public void EnumerableOfIntReturnsValues() var attribute = new CombinatorialMemberDataAttribute(nameof(GetValuesAsEnumerableOfInt)); ParameterInfo parameter = StubIntMethodInfo.GetParameters()[0]; object?[]? values = attribute.GetValues(parameter); - Assert.Equal(new object[] { 1, 2, 3, 4 }, values); + Assert.Equal([1, 2, 3, 4], values); } [Fact] @@ -26,7 +26,7 @@ public void ConcreteClassImplementingEnumerableOfIntReturnsValues() var attribute = new CombinatorialMemberDataAttribute(nameof(GetValuesAsConcreteClassImplementingEnumerableOfInt)); ParameterInfo parameter = StubIntMethodInfo.GetParameters()[0]; object?[]? values = attribute.GetValues(parameter); - Assert.Equal(new object[] { 1, 2, 3, 4 }, values); + Assert.Equal([1, 2, 3, 4], values); } [Fact] @@ -35,7 +35,7 @@ public void ConcreteNonGenericClassImplementingEnumerableOfIntReturnsValues() var attribute = new CombinatorialMemberDataAttribute(nameof(GetValuesAsConcreteNonGenericClassImplementingEnumerableOfInt)); ParameterInfo parameter = StubIntMethodInfo.GetParameters()[0]; object?[]? values = attribute.GetValues(parameter); - Assert.Equal(new object[] { 1, 2, 3, 4 }, values); + Assert.Equal([1, 2, 3, 4], values); } [Fact] diff --git a/test/Xunit.Combinatorial.Tests/CombinatorialValuesAttributeTests.cs b/test/Xunit.Combinatorial.Tests/CombinatorialValuesAttributeTests.cs index f56a90b..bc0b7e5 100644 --- a/test/Xunit.Combinatorial.Tests/CombinatorialValuesAttributeTests.cs +++ b/test/Xunit.Combinatorial.Tests/CombinatorialValuesAttributeTests.cs @@ -17,13 +17,13 @@ public void Ctor_SetsProperty() public void NullArg() { var attribute = new CombinatorialValuesAttribute(null); - Assert.Equal(new object?[] { null }, attribute.Values); + Assert.Equal([null], attribute.Values); } [Fact] public void NullArgInArray() { - var attribute = new CombinatorialValuesAttribute(new object?[] { null }); - Assert.Equal(new object?[] { null }, attribute.Values); + var attribute = new CombinatorialValuesAttribute([null]); + Assert.Equal([null], attribute.Values); } } diff --git a/test/Xunit.Combinatorial.Tests/SampleUses.cs b/test/Xunit.Combinatorial.Tests/SampleUses.cs index 524d88e..4eb7524 100644 --- a/test/Xunit.Combinatorial.Tests/SampleUses.cs +++ b/test/Xunit.Combinatorial.Tests/SampleUses.cs @@ -98,7 +98,7 @@ public void CombinatorialRandomValuesCountMinMaxValuesSeed([CombinatorialRandomD private class CustomValuesAttribute : CombinatorialValuesAttribute { public CustomValuesAttribute() - : base(new object[] { 5, 10, 15 }) + : base([5, 10, 15]) { } }