diff --git a/Src/AutoFixture/RegularExpressionGenerator.cs b/Src/AutoFixture/RegularExpressionGenerator.cs index 31346f9f9..a8cf4847e 100644 --- a/Src/AutoFixture/RegularExpressionGenerator.cs +++ b/Src/AutoFixture/RegularExpressionGenerator.cs @@ -10,6 +10,18 @@ namespace AutoFixture /// public class RegularExpressionGenerator : ISpecimenBuilder { + private readonly Random random; + private readonly object syncRoot; + + /// + /// Initializes a new instance of the class. + /// + public RegularExpressionGenerator() + { + this.random = new Random(); + this.syncRoot = new object(); + } + /// /// Creates a string that is guaranteed to match a RegularExpressionRequest. /// @@ -28,16 +40,18 @@ public object Create(object request, ISpecimenContext context) return new NoSpecimen(); } - return GenerateRegularExpression(regularExpressionRequest); + return this.GenerateRegularExpression(regularExpressionRequest); } - private static object GenerateRegularExpression(RegularExpressionRequest request) + private object GenerateRegularExpression(RegularExpressionRequest request) { string pattern = request.Pattern; try { - string regex = new Xeger(pattern).Generate(); + // Use the Xeger constructor overload that that takes an instance of Random. + // Otherwise identically strings can be generated, if regex are generated within short time. + string regex = new Xeger(pattern, new Random(this.GenerateSeed())).Generate(); if (Regex.IsMatch(regex, pattern)) { return regex; @@ -54,5 +68,13 @@ private static object GenerateRegularExpression(RegularExpressionRequest request return new NoSpecimen(); } + + private int GenerateSeed() + { + lock (this.syncRoot) + { + return this.random.Next(); + } + } } -} +} \ No newline at end of file diff --git a/Src/AutoFixtureUnitTest/DataAnnotations/RegularExpressionValidatedType.cs b/Src/AutoFixtureUnitTest/DataAnnotations/RegularExpressionValidatedType.cs index 394f665db..8d05bdff8 100644 --- a/Src/AutoFixtureUnitTest/DataAnnotations/RegularExpressionValidatedType.cs +++ b/Src/AutoFixtureUnitTest/DataAnnotations/RegularExpressionValidatedType.cs @@ -4,7 +4,7 @@ namespace AutoFixtureUnitTest.DataAnnotations { public class RegularExpressionValidatedType { - public const string Pattern = @"^[a-zA-Z''-'\s]{1,40}$"; + public const string Pattern = @"^[a-zA-Z''-'\s]{20,40}$"; [RegularExpression(Pattern)] public string Property { get; set; } diff --git a/Src/AutoFixtureUnitTest/FixtureTest.cs b/Src/AutoFixtureUnitTest/FixtureTest.cs index c8e972f5d..728cebf7a 100644 --- a/Src/AutoFixtureUnitTest/FixtureTest.cs +++ b/Src/AutoFixtureUnitTest/FixtureTest.cs @@ -1636,6 +1636,21 @@ public void CreateAnonymousWithRegularExpressionValidatedTypeReturnsCorrectResul Assert.Matches(RegularExpressionValidatedType.Pattern, result.Property); } + [Fact] + public void CreateManyAnonymousWithRegularExpressionValidatedTypeReturnsDifferentResults() + { + // This test exposes an issue with Xeger/Random. + // Xeger(pattern) internally creates an instance of Random with the default seed. + // This means that the RegularExpressionGenerator might create identical strings + // if called multiple times within a short time. + + // Arrange + var fixture = new Fixture(); + var result = fixture.CreateMany(10).Select(x => x.Property).ToArray(); + // Assert + Assert.Equal(result.Distinct(), result); + } + [Fact] public void CreateAnonymousWithStringLengthValidatedTypeReturnsCorrectResult() { diff --git a/Src/AutoFixtureUnitTest/RegularExpressionGeneratorTest.cs b/Src/AutoFixtureUnitTest/RegularExpressionGeneratorTest.cs index 570461564..e0dff1f4d 100644 --- a/Src/AutoFixtureUnitTest/RegularExpressionGeneratorTest.cs +++ b/Src/AutoFixtureUnitTest/RegularExpressionGeneratorTest.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System.Linq; +using System.Text.RegularExpressions; using AutoFixture; using AutoFixture.Kernel; using AutoFixtureUnitTest.Kernel; @@ -103,6 +104,27 @@ public void CreateWithRegularExpressionRequestReturnsCorrectResult(string patter Assert.True(Regex.IsMatch(result.ToString(), pattern), string.Format("result: {0}", result)); } + [Theory] + [InlineData(@"^[A-Z]{27}$")] + public void CreateMultipleWithRegularExpressionRequestReturnsDifferentResults(string pattern) + { + // This test exposes an issue with Xeger/Random. + // Xeger(pattern) internally creates an instance of Random with the default seed. + // This means that the RegularExpressionGenerator might create identical strings + // if called multiple times within a short time. + + // Arrange + var sut = new RegularExpressionGenerator(); + var request = new RegularExpressionRequest(pattern); + var dummyContext = new DelegatingSpecimenContext(); + + // Repeat a few times to make the test more robust. + // Use ToArray to iterate the IEnumerable at this point. + var result = Enumerable.Range(0, 10).Select(_ => sut.Create(request, dummyContext)).ToArray(); + + Assert.Equal(result.Distinct(), result); + } + [Theory] [InlineData("[")] [InlineData(@"(?\[Test\]|\[Foo\]|\[Bar\])?(?:-)?(?\[[()a-zA-Z0-9_\s]+\])?(?:-)?(?\[[a-zA-Z0-9_\s]+\])?(?:-)?(?\[[a-zA-Z0-9_\s]+\])?(?:-)?(?\[[a-zA-Z0-9_\s]+\])?")]