Skip to content

Commit

Permalink
Introduce FromValues attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrzajac committed Nov 7, 2023
1 parent 4c7bf29 commit 5a62aca
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3284,7 +3284,7 @@ dotnet_diagnostic.SA1519.severity = error
dotnet_diagnostic.SA1520.severity = error
dotnet_diagnostic.SA1600.severity = none
dotnet_diagnostic.SA1601.severity = none
dotnet_diagnostic.SA1602.severity = error
dotnet_diagnostic.SA1602.severity = none
dotnet_diagnostic.SA1603.severity = error
dotnet_diagnostic.SA1604.severity = error
dotnet_diagnostic.SA1605.severity = error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,39 +49,39 @@ public void GivenValidParameters_WhenConstructorIsInvoked_ThenParametersArePrope

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN byte populated THEN the value from range is generated")]
public void GivenRengeSpecified_WhenBytePopulated_ThenOnlyDecoratedParameterHasValueFromRange(
public void GivenRengeSpecified_WhenBytePopulated_ThenTheValueFromRangeIsGenerated(
[FromRange(Ranges.ByteRange.Min, Ranges.ByteRange.Max)] byte rangeValue)
{
rangeValue.Should().BeInRange(Ranges.ByteRange.Min, Ranges.ByteRange.Max);
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN unsigned short populated THEN the value from range is generated")]
public void GivenRengeSpecified_WhenUShortPopulated_ThenOnlyDecoratedParameterHasValueFromRange(
public void GivenRengeSpecified_WhenUShortPopulated_ThenTheValueFromRangeIsGenerated(
[FromRange(Ranges.UShortRange.Min, Ranges.UShortRange.Max)] ushort rangeValue)
{
rangeValue.Should().BeInRange(Ranges.UShortRange.Min, Ranges.UShortRange.Max);
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN unsigned integer populated THEN the value from range is generated")]
public void GivenRengeSpecified_WhenUIntPopulated_ThenOnlyDecoratedParameterHasValueFromRange(
public void GivenRengeSpecified_WhenUIntPopulated_ThenTheValueFromRangeIsGenerated(
[FromRange(Ranges.UIntRange.Min, Ranges.UIntRange.Max)] uint rangeValue)
{
rangeValue.Should().BeInRange(Ranges.UIntRange.Min, Ranges.UIntRange.Max);
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN unsigned long populated THEN the value from range is generated")]
public void GivenRengeSpecified_WhenULongPopulated_ThenOnlyDecoratedParameterHasValueFromRange(
public void GivenRengeSpecified_WhenULongPopulated_ThenTheValueFromRangeIsGenerated(
[FromRange(Ranges.ULongRange.Min, Ranges.ULongRange.Max)] ulong rangeValue)
{
rangeValue.Should().BeInRange(Ranges.ULongRange.Min, Ranges.ULongRange.Max);
}

[AutoData]
[Theory(DisplayName = "GIVEN renge specified WHEN signed byte populated THEN the value from range is generated")]
public void GivenRengeSpecified_WhenSBytePopulated_ThenOnlyDecoratedParameterHasValueFromRange(
public void GivenRengeSpecified_WhenSBytePopulated_ThenTheValueFromRangeIsGenerated(
[FromRange(Ranges.SByteRange.Min, Ranges.SByteRange.Max)] sbyte rangeValue)
{
rangeValue.Should().BeInRange(Ranges.SByteRange.Min, Ranges.SByteRange.Max);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Attributes
{
using FluentAssertions;

using global::AutoFixture.Xunit2;

using Objectivity.AutoFixture.XUnit2.Core.Attributes;

using Xunit;

[Collection("FromValuesAttribute")]
[Trait("Category", "Attributes")]
public class FromValuesAttributeTests
{
public enum Test
{
None = 0,
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
}

[AutoData]
[Theory(DisplayName = "GIVEN values specified WHEN unsigned short populated THEN the value from set is generated")]
public void GivenValuesSpecified_WhenUShortPopulated_ThenTheValueFromSetIsGenerated(
[FromValues(1, 5, 4)] ushort targetValue)
{
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(
[FromValues(Test.One, Test.Five, 100)] Test targetValue)
{
targetValue.Should().BeOneOf(Test.One, Test.Five);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Objectivity.AutoFixture.XUnit2.Core.Attributes
{
using System;
using System.Reflection;

using global::AutoFixture;
using global::AutoFixture.Xunit2;
using Objectivity.AutoFixture.XUnit2.Core.CustomisationFactories;
using Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders;

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public sealed class FromValuesAttribute : CustomizeAttribute
{
private readonly ICustomisationFactoryProvider factoryProvider = new CustomisationFactoryProvider();

public FromValuesAttribute(params object[] args)
{
this.Args = args ?? Array.Empty<object>();
}

public object[] Args { get; }

public override ICustomization GetCustomization(ParameterInfo parameter)
{
var type = typeof(RandomValuesParameterBuilder);
var factory = this.factoryProvider.GetFactory(type);

return factory.Create(parameter, false, type, this.Args);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

using Objectivity.AutoFixture.XUnit2.Core.Common;

public class RandomRangedNumberParameterBuilder : ISpecimenBuilder
internal class RandomRangedNumberParameterBuilder : ISpecimenBuilder
{
private readonly RandomRangedNumberGenerator randomRangedNumberGenerator = new();

Expand Down Expand Up @@ -39,8 +39,7 @@ public RandomRangedNumberParameterBuilder(object minimum, object maximum)

public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi is not null) //// is a parameter
if (request is ParameterInfo pi) //// is a parameter
{
var rangeRequest = new RangedNumberRequest(pi.ParameterType, this.Minimum, this.Maximum);
return this.randomRangedNumberGenerator.Create(rangeRequest, context);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
namespace Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

using global::AutoFixture;
using global::AutoFixture.Kernel;

using Objectivity.AutoFixture.XUnit2.Core.Common;

internal class RandomValuesParameterBuilder : ISpecimenBuilder
{
private readonly Dictionary<Type, IEnumerator> enumerators = new();
private readonly object syncRoot = new();

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

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

Check warning on line 26 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#L26

Added line #L26 was not covered by tests
}
}

public object[] Args { get; }

public object Create(object request, ISpecimenContext context)
{
if (request is ParameterInfo pi) //// is a parameter
{
lock (this.syncRoot)
{
return this.CreateValue(pi.ParameterType);
}
}

return new NoSpecimen();

Check warning on line 42 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#L42

Added line #L42 was not covered by tests
}

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

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

return enumerator;
}

private sealed class RoundRobinCollection : IEnumerable<object>
{
private readonly IEnumerable<object> values;

[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "It is good enought for collection randomisation.")]
internal RoundRobinCollection(Type targetType, object[] args)
{
var type = targetType.NotNull(nameof(targetType));
var random = new Random();

if (type.IsEnum)
{
// TODO: Should we
// 1. Allow values outside enum?
// 2. Filter out values outside enum?
// 3. Throw exception when values outside enum?
var enumValues = Enum.GetValues(type).Cast<object>().ToList();
this.values = args.Where(enumValues.Contains)
.OrderBy((_) => random.Next());
}
else
{
// TODO: Check nullable
// TODO: Check reference types
// TODO: Check min/max of numeric types
this.values = args.OrderBy((_) => random.Next());
}

if (!this.values.Any())
{
var message = $"AutoFixture was unable to create a value for {targetType.FullName} since it is an enum containing no values. Please add at least one value to the enum.";
throw new ObjectCreationException(message);

Check warning on line 94 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#L93-L94

Added lines #L93 - L94 were not covered by tests
}
}

[SuppressMessage("Blocker Bug", "S2190:Loops and recursions should not be infinite", Justification = "This is a round robin implementation.")]
public IEnumerator<object> GetEnumerator()
{
while (true)

Check warning on line 101 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#L101

Added line #L101 was not covered by tests
{
foreach (var obj in this.values)
{
yield return obj;
}
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();

Check warning on line 112 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#L112

Added line #L112 was not covered by tests
}
}
}
}

0 comments on commit 5a62aca

Please sign in to comment.