From 4426d3f120587b57fdc1bbeaa0a50b14d16adf91 Mon Sep 17 00:00:00 2001 From: bchavez Date: Sat, 4 Nov 2017 04:18:10 -0700 Subject: [PATCH] Implements .Clone() on top of flowed localized seed. #100. --- Source/Bogus.Tests/Bogus.Tests.csproj | 1 + Source/Bogus.Tests/CloneTests.cs | 50 ++++++++++++++++++ Source/Bogus.Tests/GitHubIssues/Issue100.cs | 41 ++++++++------- Source/Bogus/Faker[T].cs | 58 ++++++++++++++++++++- 4 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 Source/Bogus.Tests/CloneTests.cs diff --git a/Source/Bogus.Tests/Bogus.Tests.csproj b/Source/Bogus.Tests/Bogus.Tests.csproj index 4187eb71..3c5ba372 100644 --- a/Source/Bogus.Tests/Bogus.Tests.csproj +++ b/Source/Bogus.Tests/Bogus.Tests.csproj @@ -79,6 +79,7 @@ + diff --git a/Source/Bogus.Tests/CloneTests.cs b/Source/Bogus.Tests/CloneTests.cs new file mode 100644 index 00000000..7c0e51a5 --- /dev/null +++ b/Source/Bogus.Tests/CloneTests.cs @@ -0,0 +1,50 @@ +using FluentAssertions; +using Xunit; + +namespace Bogus.Tests +{ + public class CloneTests : SeededTest + { + [Fact] + public void can_create_a_simple_clone() + { + var orderFaker = new Faker() + .UseSeed(88) + .RuleFor(o => o.OrderId, f => f.IndexVariable++) + .RuleFor(o => o.Quantity, f => f.Random.Number(1, 3)) + .RuleFor(o => o.Item, f => f.Commerce.Product()); + + var clone = orderFaker.Clone(); + + var clonedOrder = clone.Generate(); + + var rootOrder = orderFaker.Generate(); + + clonedOrder.ShouldBeEquivalentTo(rootOrder); + } + + [Fact] + public void clone_has_different_rules() + { + var rootFaker = new Faker() + .UseSeed(88) + .RuleFor(o => o.OrderId, f => f.IndexVariable++) + .RuleFor(o => o.Quantity, f => f.Random.Number(1, 3)) + .RuleFor(o => o.Item, f => f.Commerce.Product()); + + var cloneFaker = rootFaker.Clone() + .RuleFor(o => o.Quantity, f => f.Random.Number(4, 6)); + + var rootOrder = rootFaker.Generate(); + var clonedOrder = cloneFaker.Generate(); + + rootOrder.Quantity.Should() + .BeGreaterOrEqualTo(1).And + .BeLessOrEqualTo(3); + + clonedOrder.Quantity.Should() + .BeGreaterOrEqualTo(4).And + .BeLessOrEqualTo(6); + } + } +} \ No newline at end of file diff --git a/Source/Bogus.Tests/GitHubIssues/Issue100.cs b/Source/Bogus.Tests/GitHubIssues/Issue100.cs index 5b72899b..58c529b6 100644 --- a/Source/Bogus.Tests/GitHubIssues/Issue100.cs +++ b/Source/Bogus.Tests/GitHubIssues/Issue100.cs @@ -147,26 +147,27 @@ public void complex_faker_t_test() CheckSequence(items); } - //[Fact] - //public void parallel_determinism() - //{ - // var orderFaker = new Faker() - // .RuleFor(o => o.OrderId, f => f.IndexVariable++) - // .RuleFor(o => o.Quantity, f => f.Random.Number(1, 3)) - // .RuleFor(o => o.Item, f => f.Commerce.Product()); - - // var orders = ParallelEnumerable.Range(1, 5) - // .Select(threadId => - // orderFaker - // .UseSeed(88) - // .Generate(4).ToArray() - // ).ToArray(); - - // foreach( var orderOfFour in orders ) - // { - // CheckSequence(orderOfFour); - // } - //} + [Fact] + public void parallel_determinism() + { + var orderFaker = new Faker() + .RuleFor(o => o.OrderId, f => f.IndexVariable++) + .RuleFor(o => o.Quantity, f => f.Random.Number(1, 3)) + .RuleFor(o => o.Item, f => f.Commerce.Product()); + + var orders = ParallelEnumerable.Range(1, 5) + .Select(threadId => + orderFaker + .Clone() + .UseSeed(88) + .Generate(4).ToArray() + ).ToArray(); + + foreach( var orderOfFour in orders ) + { + CheckSequence(orderOfFour); + } + } private void CheckSequence(Examples.Order[] items) { diff --git a/Source/Bogus/Faker[T].cs b/Source/Bogus/Faker[T].cs index 6e76c708..74556d7c 100644 --- a/Source/Bogus/Faker[T].cs +++ b/Source/Bogus/Faker[T].cs @@ -26,8 +26,62 @@ public class Faker : ILocaleAware, IRuleSet where T : class protected internal Dictionary StrictModes = new Dictionary(); protected internal bool? IsValid; protected internal string currentRuleSet = Default; + protected internal int? localSeed; // if null, the global Randomizer.Seed is used. #pragma warning restore 1591 + /// + /// Clones the internal state of a Faker[T] into a new Faker[T] so that + /// both are isolated from each other. The clone will have internal state + /// reset as if .Generate() was never + /// + public Faker Clone() + { + var clone = new Faker(this.Locale, this.binder); + + //copy internal state. + //strict modes. + foreach( var root in this.StrictModes ) + { + clone.StrictModes.Add(root.Key, root.Value); + } + + //ignores + foreach( var root in this.Ignores ) + { + foreach( var str in root.Value ) + { + clone.Ignores.Add(root.Key, str); + } + } + + //create actions + foreach( var root in this.CreateActions ) + { + clone.CreateActions[root.Key] = root.Value; + } + //finalize actions + foreach( var root in this.FinalizeActions ) + { + clone.FinalizeActions.Add(root.Key, root.Value); + } + + //actions + foreach( var root in this.Actions ) + { + foreach( var kv in root.Value ) + { + clone.Actions.Add(root.Key, kv.Key, kv.Value); + } + } + + if( localSeed.HasValue ) + { + clone.UseSeed(localSeed.Value); + } + + return clone; + } + /// /// The current locale. /// @@ -68,8 +122,8 @@ public Faker(string locale = "en", IBinder binder = null) /// The seed value to use within this Faker[T] instance. public virtual Faker UseSeed(int seed) { - var r = new Randomizer(seed); - this.FakerHub.Random = r; + this.localSeed = seed; + this.FakerHub.Random = new Randomizer(seed); return this; }