Skip to content

Commit

Permalink
Further improvments
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrzajac committed Nov 8, 2023
1 parent 5a62aca commit 71cf2dc
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;

using FluentAssertions;

using global::AutoFixture.Xunit2;

using Objectivity.AutoFixture.XUnit2.Core.Attributes;
Expand Down Expand Up @@ -164,6 +167,70 @@ public void GivenRengeSpecifiedAndMemberDataValueOutsideRange_WhenDataPopulated_
value.Should().Be(expectedResult).And.NotBeInRange(Ranges.IntRange.Min, Ranges.IntRange.Max);
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN arrays populated THEN only decorated parameter has value from range")]
public void GivenRengeSpecified_WhenArraysPopulated_ThenOnlyDecoratedParameterHasValuesFromRange(
[FromRange(Ranges.IntRange.Min, Ranges.IntRange.Max)] int[] rangeValues,
int[] unrestrictedValues)
{
rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(Ranges.IntRange.Min, Ranges.IntRange.Max));
unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0));
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN enumerables populated THEN only decorated parameter has value from range")]
public void GivenRengeSpecified_WhenEnumerablesPopulated_ThenOnlyDecoratedParameterHasValuesFromRange(
[FromRange(Ranges.IntRange.Min, Ranges.IntRange.Max)] IEnumerable<int> rangeValues,
IEnumerable<int> unrestrictedValues)
{
rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(Ranges.IntRange.Min, Ranges.IntRange.Max));
unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0));
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN lists populated THEN only decorated parameter has value from range")]
[SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We are testing generic lists")]
public void GivenRengeSpecified_WhenListPopulated_ThenOnlyDecoratedParameterHasValuesFromRange(
[FromRange(Ranges.IntRange.Min, Ranges.IntRange.Max)] List<int> rangeValues,
List<int> unrestrictedValues)
{
rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(Ranges.IntRange.Min, Ranges.IntRange.Max));
unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0));
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN sets populated THEN only decorated parameter has value from range")]
[SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We are testing generic lists")]
public void GivenRengeSpecified_WhenSetsPopulated_ThenOnlyDecoratedParameterHasValuesFromRange(
[FromRange(Ranges.IntRange.Min, Ranges.IntRange.Max)] HashSet<int> rangeValues,
HashSet<int> unrestrictedValues)
{
rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(Ranges.IntRange.Min, Ranges.IntRange.Max));
unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0));
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN collections populated THEN only decorated parameter has value from range")]
[SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We are testing generic lists")]
public void GivenRengeSpecified_WhenCollectionsPopulated_ThenOnlyDecoratedParameterHasValuesFromRange(
[FromRange(Ranges.IntRange.Min, Ranges.IntRange.Max)] Collection<int> rangeValues,
Collection<int> unrestrictedValues)
{
rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(Ranges.IntRange.Min, Ranges.IntRange.Max));
unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0));
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN read-only collections populated THEN only decorated parameter has value from range")]
[SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We are testing generic lists")]
public void GivenRengeSpecified_WhenReadOnlyCollectionsPopulated_ThenOnlyDecoratedParameterHasValuesFromRange(
[FromRange(Ranges.IntRange.Min, Ranges.IntRange.Max)] ReadOnlyCollection<int> rangeValues,
ReadOnlyCollection<int> unrestrictedValues)
{
rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(Ranges.IntRange.Min, Ranges.IntRange.Max));
unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0));
}

private static class Ranges
{
public static class SByteRange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,34 @@ public enum Test
public void GivenValuesSpecified_WhenUShortPopulated_ThenTheValueFromSetIsGenerated(
[FromValues(1, 5, 4)] ushort targetValue)
{
// Arrange
// Act
// Assert
targetValue.Should().BeOneOf(1, 5, 4);
}

[AutoData]
[Theory(DisplayName = "GIVEN values specified WHEN unsigned short populated THEN the value from set is generated")]
public void GivenValuesSpecified_WhenEnumPopulated_ThenTheValueFromSetIsGenerated(
[Theory(DisplayName = "GIVEN values specified for argument WHEN unsigned short populated THEN the value from set is generated")]
public void GivenValuesSpecifiedForAgrument_WhenEnumPopulated_ThenTheValueFromSetIsGenerated(
[FromValues(Test.One, Test.Five, 100)] Test targetValue)
{
// Arrange
// Act
// Assert
targetValue.Should().BeOneOf(Test.One, Test.Five);
}

////[AutoData]
////[Theory(DisplayName = "GIVEN values specified for collection WHEN unsigned short populated THEN the value from set is generated")]
////public void GivenValuesSpecifiedForCollection_WhenEnumPopulated_ThenTheValueFromSetIsGenerated(
//// [FromValues(Test.One, Test.Five)] Test[] targetValues)
////{
//// // Arrange
//// var supported = new[] { Test.One, Test.Five };

//// // Act
//// // Assert
//// targetValues.Should().AllSatisfy(x => supported.Should().Contain(x));
////}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Linq;

using FluentAssertions;

using global::AutoFixture;
using global::AutoFixture.Kernel;
using global::AutoFixture.Xunit2;
using Moq;
Expand Down Expand Up @@ -70,18 +72,18 @@ public void GivenUnsupportedRequestType_WhenCreateIsInvoked_ThenNoSpecimenIsRetu
[InlineAutoData(10, 10)]
[InlineAutoData(1, 100)]
[Theory(DisplayName = "GIVEN valid request type WHEN create is invoked THEN value from range is returned")]
public void GivenValidRequestType_WhenCreateIsInvoked_ThenValueFromRangeIsReturned(int min, int max)
public void GivenValidRequestType_WhenCreateIsInvoked_ThenValueFromRangeIsReturned(int min, int max, IFixture fixture)
{
// Arrange
var builder = new RandomRangedNumberParameterBuilder(min, max);
var context = new Mock<ISpecimenContext>();
var context = new SpecimenContext(fixture);
var request = this.GetType()
.GetMethod(nameof(this.GivenValidRequestType_WhenCreateIsInvoked_ThenValueFromRangeIsReturned))
.GetParameters()
.First();

// Act
var result = builder.Create(request, context.Object);
var result = builder.Create(request, context);

// Assert
result.Should().NotBeNull().And.Subject.As<int>().Should().BeInRange(min, max);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace Objectivity.AutoFixture.XUnit2.Core.Tests.SpecimenBuilders
{
using System;

using global::AutoFixture.Xunit2;

using Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders;

using Xunit;

[Collection("RandomValuesParameterBuilder")]
[Trait("Category", "SpecimenBuilders")]
public class RandomValuesParameterBuilderTests
{
[Fact(DisplayName = "GIVEN uninitialized argument WHEN constructor is invoked THEN exception is thrown")]
public void GivenUninitializedArgument_WhenConstructorIsInvoked_ThenExceptionIsThrown()
{
// Arrange
// Act
// Assert
Assert.Throws<ArgumentNullException>(() => new RandomValuesParameterBuilder(null));
}

[Fact(DisplayName = "GIVEN empty argument WHEN constructor is invoked THEN exception is thrown")]
public void GivenEmptyArgument_WhenConstructorIsInvoked_ThenExceptionIsThrown()
{
// Arrange
// Act
// Assert
Assert.Throws<ArgumentException>(() => new RandomValuesParameterBuilder(Array.Empty<object>()));
}

[AutoData]
[Theory(DisplayName = "GIVEN empty argument WHEN Create is invoked THEN exception is thrown")]
internal void GivenEmptyArgument_WhenCreateIsInvoked_ThenExceptionIsThrown(RandomValuesParameterBuilder builder)
{
// Arrange
// Act
// Assert
Assert.Throws<ArgumentNullException>(() => builder.Create(new object(), null));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
namespace Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using global::AutoFixture;
Expand All @@ -10,7 +13,13 @@

internal class RandomRangedNumberParameterBuilder : ISpecimenBuilder
{
private readonly RandomRangedNumberGenerator randomRangedNumberGenerator = new();
// TODO: Consider encapsulating in separate structure
private static readonly MethodInfo BuildTypedArrayMethodInfo =
typeof(RandomRangedNumberParameterBuilder).GetTypeInfo().GetMethod(
nameof(BuildTypedArray),
BindingFlags.Static | BindingFlags.NonPublic);

private readonly IRequestMemberTypeResolver typeResolver = new RequestMemberTypeResolver();

public RandomRangedNumberParameterBuilder(object minimum, object maximum)
{
Expand Down Expand Up @@ -39,13 +48,77 @@ public RandomRangedNumberParameterBuilder(object minimum, object maximum)

public object Create(object request, ISpecimenContext context)
{
if (request is ParameterInfo pi) //// is a parameter
if (this.typeResolver.TryGetMemberType(request, out var type))
{
var rangeRequest = new RangedNumberRequest(pi.ParameterType, this.Minimum, this.Maximum);
return this.randomRangedNumberGenerator.Create(rangeRequest, context);
if (type.IsArray)
{
return this.CreateMultiple(type.GetElementType(), context, true);
}
else if (TryGetSingleEnumerableTypeArgument(type, out var enumerableType))
{
var items = this.CreateMultiple(enumerableType, context, false);
if (!type.IsInterface && !type.IsAbstract)
{
return Activator.CreateInstance(type, items);
}

return items;
}
else
{
var rangeRequest = new RangedNumberRequest(type, this.Minimum, this.Maximum);
return context.Resolve(rangeRequest);
}
}

return new NoSpecimen();
}

private static bool TryGetSingleEnumerableTypeArgument(Type currentType, out Type argument)
{
var interfaces = currentType.GetInterfaces().Append(currentType).ToArray();
var typeInfo = Array.Find(
interfaces,
x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (typeInfo is not null)
{
var typeArguments = typeInfo.GenericTypeArguments;
if (typeArguments.Length == 1)
{
argument = typeArguments[0];
return true;
}
}

argument = null;
return false;
}

private static object ToTypedArray(IEnumerable items, Type elementType, bool isArrayExpected)
{
return BuildTypedArrayMethodInfo.MakeGenericMethod(elementType).Invoke(null, new object[] { items, isArrayExpected });
}

private static IEnumerable<TElementType> BuildTypedArray<TElementType>(IEnumerable items, bool isArrayExpected)
{
var casted = items is IEnumerable<TElementType> castedItems
? castedItems
: items.Cast<TElementType>();

return isArrayExpected ? casted.ToArray() : casted.ToList();
}

private object CreateMultiple(Type type, ISpecimenContext context, bool isArrayExpected)
{
var rangeRequest = new RangedNumberRequest(type, this.Minimum, this.Maximum);
var specimen = context.Resolve(new MultipleRequest(rangeRequest));

if (specimen is not IEnumerable elements)
{
return new NoSpecimen();

Check warning on line 118 in src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomRangedNumberParameterBuilder.cs

View check run for this annotation

Codecov / codecov/patch

src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomRangedNumberParameterBuilder.cs#L118

Added line #L118 was not covered by tests
}

return ToTypedArray(elements, type, isArrayExpected);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

using global::AutoFixture;
using global::AutoFixture.Kernel;
Expand All @@ -16,45 +15,66 @@ internal class RandomValuesParameterBuilder : ISpecimenBuilder
{
private readonly Dictionary<Type, IEnumerator> enumerators = new();
private readonly object syncRoot = new();
private readonly object[] inputValues;
private readonly Lazy<IReadOnlyCollection<object>> readonlyValues;
private readonly IRequestMemberTypeResolver typeResolver = new RequestMemberTypeResolver();

public RandomValuesParameterBuilder(params object[] args)
public RandomValuesParameterBuilder(params object[] values)
{
this.Args = args.NotNull(nameof(args));

if (this.Args.Length == 0)
this.inputValues = values.NotNull(nameof(values));
if (this.inputValues.Length == 0)
{
throw new ArgumentException("At least one argument is expected to be specified.", nameof(args));
throw new ArgumentException("At least one value is expected to be specified.", nameof(values));
}

this.readonlyValues = new Lazy<IReadOnlyCollection<object>>(() => Array.AsReadOnly(this.inputValues));
}

public object[] Args { get; }
public IReadOnlyCollection<object> Values => this.readonlyValues.Value;

public object Create(object request, ISpecimenContext context)
{
if (request is ParameterInfo pi) //// is a parameter
if (context is null)
{
lock (this.syncRoot)
throw new ArgumentNullException(nameof(context));
}

if (!this.typeResolver.TryGetMemberType(request, out var type))
{
type = request as Type;

Check warning on line 44 in src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs

View check run for this annotation

Codecov / codecov/patch

src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs#L44

Added line #L44 was not covered by tests
}

if (type is not null)
{
if (type.IsArray)
{
context.Resolve(new MultipleRequest(type.GetElementType()));

Check warning on line 51 in src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs

View check run for this annotation

Codecov / codecov/patch

src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs#L51

Added line #L51 was not covered by tests
}
else
{
return this.CreateValue(pi.ParameterType);
lock (this.syncRoot)
{
return this.CreateValue(type);
}
}
}

return new NoSpecimen();

Check warning on line 62 in src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs

View check run for this annotation

Codecov / codecov/patch

src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs#L62

Added line #L62 was not covered by tests
}

private object CreateValue(Type t)
private object CreateValue(Type targetType)
{
var generator = this.EnsureGenerator(t);
var generator = this.EnsureGenerator(targetType);
generator.MoveNext();
return generator.Current;
}

private IEnumerator EnsureGenerator(Type t)
private IEnumerator EnsureGenerator(Type targetType)
{
if (!this.enumerators.TryGetValue(t, out var enumerator))
if (!this.enumerators.TryGetValue(targetType, out var enumerator))
{
enumerator = new RoundRobinCollection(t, this.Args).GetEnumerator();
this.enumerators.Add(t, enumerator);
enumerator = new RoundRobinCollection(targetType, this.inputValues).GetEnumerator();
this.enumerators.Add(targetType, enumerator);
}

return enumerator;
Expand Down

0 comments on commit 71cf2dc

Please sign in to comment.