-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
- Loading branch information
There are no files selected for viewing
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 |
---|---|---|
@@ -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 Codecov / codecov/patchsrc/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs#L26
|
||
} | ||
} | ||
|
||
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 Codecov / codecov/patchsrc/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs#L42
|
||
} | ||
|
||
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 Codecov / codecov/patchsrc/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs#L93-L94
|
||
} | ||
} | ||
|
||
[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 Codecov / codecov/patchsrc/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs#L101
|
||
{ | ||
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 Codecov / codecov/patchsrc/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomValuesParameterBuilder.cs#L112
|
||
} | ||
} | ||
} | ||
} |