From 571e77f4e414811fff6be18d81164db48583de16 Mon Sep 17 00:00:00 2001 From: nth-commit Date: Fri, 27 Jan 2023 09:46:43 +1300 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=8D=20#373=20Add=20default=20gen=20sup?= =?UTF-8?q?port=20for=20more=20primitives?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - double - float - decimal - sbyte - DateTimeOffset - TimeSpan - Color - Uri --- src/GalaxyCheck.Tests/GenExtensions.cs | 11 +- .../AboutGeneratingPrimitives.cs | 245 +++--------------- .../AboutOverridingMembers.cs | 4 +- src/GalaxyCheck/Gens/FactoryGen.cs | 79 +++--- 4 files changed, 99 insertions(+), 240 deletions(-) diff --git a/src/GalaxyCheck.Tests/GenExtensions.cs b/src/GalaxyCheck.Tests/GenExtensions.cs index e4bd10a2..4980ab02 100644 --- a/src/GalaxyCheck.Tests/GenExtensions.cs +++ b/src/GalaxyCheck.Tests/GenExtensions.cs @@ -7,13 +7,22 @@ namespace Tests.V2 { public static class GenExtensions { + public static List> SampleNTraversals(this IGen gen, int count, int seed, int size) => gen + .Advanced + .SampleExampleSpaces(iterations: count, seed: seed, size: size) + .Select(exs => + exs.Traverse() + .Take(100) + .ToList()).ToList(); + public static List SampleOneTraversal(this IGen gen, int seed, int size) => gen.Advanced .SampleOneExampleSpace(seed: seed, size: size) .Traverse() .Take(100) .ToList(); - public static string RenderOneTraversal(this IGen gen, int seed, int size, Func? renderer = null) => gen.Advanced + public static string RenderOneTraversal(this IGen gen, int seed, int size, + Func? renderer = null) => gen.Advanced .SampleOneExampleSpace(seed: seed, size: size) .Take(500) .Render(x => renderer == null ? x!.ToString()! : renderer(x)); diff --git a/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutGeneratingPrimitives.cs b/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutGeneratingPrimitives.cs index 5b7900cd..7c771666 100644 --- a/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutGeneratingPrimitives.cs +++ b/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutGeneratingPrimitives.cs @@ -1,218 +1,47 @@ using FluentAssertions; using GalaxyCheck; -using NebulaCheck; using System; -using System.Collections.Generic; -using System.Linq; -using Property = NebulaCheck.Property; -using Test = NebulaCheck.Test; +using System.Drawing; +using System.Reflection; +using Xunit; namespace Tests.V2.GenTests.ReflectedGenTests { public class AboutGeneratingPrimitives { - [Property] - public NebulaCheck.IGen ItCanGenerateInt16s() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Int16(); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateUnsignedInt16s() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Int16().Select(x => (ushort)x); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateInt32s() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Int32(); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateUnsignedInt32s() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Int32().Select(x => (uint)x); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateInt64s() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Int64(); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateUnsignedInt64s() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Int64().Select(x => (ulong)x); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateChars() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Char(); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateStrings() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.String(); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateBytes() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Byte(); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateGuids() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Guid(); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateDateTimes() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.DateTime(); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); - - [Property] - public NebulaCheck.IGen ItCanGenerateBooleans() => - from seed in DomainGen.Seed() - from size in DomainGen.Size() - select Property.ForThese(() => - { - List SampleTraversal(GalaxyCheck.IGen gen) => gen.SampleOneTraversal(seed, size); - - var gen0 = GalaxyCheck.Gen.Create(); - var gen1 = GalaxyCheck.Gen.Boolean(); - - var sample0 = SampleTraversal(gen0); - var sample1 = SampleTraversal(gen1); - - sample0.Should().BeEquivalentTo(sample1); - }); + [Theory] + [InlineData(typeof(short))] + [InlineData(typeof(ushort))] + [InlineData(typeof(int))] + [InlineData(typeof(uint))] + [InlineData(typeof(long))] + [InlineData(typeof(ulong))] + [InlineData(typeof(float))] + [InlineData(typeof(double))] + [InlineData(typeof(decimal))] + [InlineData(typeof(bool))] + [InlineData(typeof(char))] + [InlineData(typeof(byte))] + [InlineData(typeof(sbyte))] + [InlineData(typeof(string))] + [InlineData(typeof(DateTime))] + [InlineData(typeof(DateTimeOffset))] + [InlineData(typeof(TimeSpan))] + [InlineData(typeof(Guid))] + [InlineData(typeof(Uri))] + [InlineData(typeof(Color))] + public void ItCanGenerateType(Type type) + { + // Arrange + var method = typeof(Gen).GetMethod(nameof(Gen.Create), types: new[] { typeof(NullabilityInfo) }); + var genericMethod = method!.MakeGenericMethod(type); + var gen = (IGen)genericMethod.Invoke(null, new object?[] { null })!; + + // Act + Action action = () => gen.Cast().SampleNTraversals(10, 0, 100); + + // Assert + action.Should().NotThrow(); + } } } diff --git a/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutOverridingMembers.cs b/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutOverridingMembers.cs index e09a0864..05ae1c0c 100644 --- a/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutOverridingMembers.cs +++ b/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutOverridingMembers.cs @@ -92,7 +92,7 @@ select Property.ForThese(() => action.Should() .Throw() - .WithMessage("Error while running generator ReflectedGen: expression 'x => x.Method()' was invalid, an overridding expression may only contain member access"); + .WithMessage("Error while running generator ReflectedGen: expression 'x => x.Method()' was invalid, an overriding expression may only contain member access"); }); [Property] @@ -111,7 +111,7 @@ select Property.ForThese(() => action.Should() .Throw() - .WithMessage("Error while running generator ReflectedGen: expression 'x => x.Method()' was invalid, an overridding expression may only contain member access"); + .WithMessage("Error while running generator ReflectedGen: expression 'x => x.Method()' was invalid, an overriding expression may only contain member access"); }); private class ClassWithNonDefaultConstructor diff --git a/src/GalaxyCheck/Gens/FactoryGen.cs b/src/GalaxyCheck/Gens/FactoryGen.cs index c43b7667..0e319310 100644 --- a/src/GalaxyCheck/Gens/FactoryGen.cs +++ b/src/GalaxyCheck/Gens/FactoryGen.cs @@ -1,17 +1,11 @@ -/// -/// TODO: -/// - Docs -/// - Plugin-able strategies for handling nullables, ienumerables etc. -/// - Render ctor params in path more sensibly -/// -namespace GalaxyCheck +namespace GalaxyCheck { - using GalaxyCheck.Gens; + using Gens; public static partial class Gen { /// - /// Creates a factory for . The factory allows you to assign specific generators to + /// Creates a factory for . The factory allows you to assign specific generators to /// types, and then be able to share that configuration across many auto-generators, see /// . /// @@ -22,15 +16,16 @@ public static partial class Gen namespace GalaxyCheck.Gens { - using GalaxyCheck.Gens.Internal; - using GalaxyCheck.Gens.ReflectedGenHelpers; + using Internal; + using ReflectedGenHelpers; using GalaxyCheck.Internal; using System; using System.Collections.Generic; using System.Collections.Immutable; - using System.Linq; using System.Linq.Expressions; using System.Reflection; + using System.Drawing; + public interface IReflectedGen : IGen { @@ -91,18 +86,37 @@ internal class GenFactory : IGenFactory private static readonly IReadOnlyDictionary> DefaultRegisteredGensByType = new Dictionary> { + // Integers and complements { typeof(short), () => Gen.Int16() }, { typeof(ushort), () => Gen.Int16().Select(x => (ushort)x) }, { typeof(int), () => Gen.Int32() }, { typeof(uint), () => Gen.Int32().Select(x => (uint)x) }, { typeof(long), () => Gen.Int64() }, { typeof(ulong), () => Gen.Int64().Select(x => (ulong)x) }, + { typeof(byte), () => Gen.Byte() }, + { typeof(sbyte), () => Gen.Byte().Select(x => (sbyte)x) }, + + // Dates and times + { typeof(DateTime), () => Gen.DateTime() }, + { + typeof(DateTimeOffset), + () => Gen.DateTime().Within(new DateTime(1, 1, 2), new DateTime(9998, 12, 30)) + .Select((x => new DateTimeOffset(x))) + }, + { typeof(TimeSpan), () => Gen.Int64().Select(x => new TimeSpan(x)) }, + + // Decimals - not interesting values, but won't halt the generator at least + { typeof(float), () => Gen.Int32().Select(x => (float)x) }, + { typeof(double), () => Gen.Int32().Select(x => (double)x) }, + { typeof(decimal), () => Gen.Int32().Select(x => (decimal)x) }, + + // Misc... { typeof(char), () => Gen.Char() }, { typeof(string), () => Gen.String() }, - { typeof(byte), () => Gen.Byte() }, { typeof(Guid), () => Gen.Guid() }, - { typeof(DateTime), () => Gen.DateTime() }, - { typeof(bool), () => Gen.Boolean() } + { typeof(bool), () => Gen.Boolean() }, + { typeof(Color), () => Gen.Int32().Select(x => Color.FromArgb(x)) }, + { typeof(Uri), () => Gen.Constant(new Uri("https://github.com/nth-commit/GalaxyCheck/releases")) } }; private readonly ImmutableDictionary> _registeredGensByType; @@ -145,7 +159,9 @@ internal record ReflectedGen( { public record OverrideMemberErrorKind { - private OverrideMemberErrorKind() { } + private OverrideMemberErrorKind() + { + } public record InvalidExpression(string Expression) : OverrideMemberErrorKind; @@ -156,14 +172,15 @@ public ReflectedGen( IReadOnlyDictionary> registeredGensByType, NullabilityInfo? nullabilityInfo) : this( - registeredGensByType, - ImmutableList.Create(), - null, - nullabilityInfo) + registeredGensByType, + ImmutableList.Create(), + null, + nullabilityInfo) { } - public IReflectedGen OverrideMember(Expression> memberSelector, IGen fieldGen) + public IReflectedGen OverrideMember(Expression> memberSelector, + IGen fieldGen) { // TODO: Either.Select, Either.SelectMany, Either.SelectError var pathResult = PathResolver.FromExpression(memberSelector); @@ -200,14 +217,18 @@ protected override IGen Get } } - private static IGen ResolveError(ReflectedGen.OverrideMemberErrorKind overrideMemberError) => overrideMemberError switch - { - ReflectedGen.OverrideMemberErrorKind.InvalidExpression invalidExpression => - Error($"expression '{invalidExpression.Expression}' was invalid, an overridding expression may only contain member access"), - ReflectedGen.OverrideMemberErrorKind.AttemptedOverrideOnRegisteredGen attemptedOverrideOnRegisteredGen => - Error($"attempted to override expression '{attemptedOverrideOnRegisteredGen.Expression}' on type '{typeof(T)}', but overriding members for registered types is not currently supported (GitHub issue: https://github.com/nth-commit/GalaxyCheck/issues/346)"), - _ => throw new NotSupportedException($"Type not supported in switch: {overrideMemberError.GetType()}") - }; + private static IGen ResolveError(OverrideMemberErrorKind overrideMemberError) => + overrideMemberError switch + { + OverrideMemberErrorKind.InvalidExpression invalidExpression => + Error( + $"expression '{invalidExpression.Expression}' was invalid, an overriding expression may only contain member access"), + OverrideMemberErrorKind.AttemptedOverrideOnRegisteredGen + attemptedOverrideOnRegisteredGen => + Error( + $"attempted to override expression '{attemptedOverrideOnRegisteredGen.Expression}' on type '{typeof(T)}', but overriding members for registered types is not currently supported (GitHub issue: https://github.com/nth-commit/GalaxyCheck/issues/346)"), + _ => throw new NotSupportedException($"Type not supported in switch: {overrideMemberError.GetType()}") + }; private static IGen Error(string message) => Gen.Advanced.Error(nameof(ReflectedGen), message); }