Skip to content

Commit 4dd7b01

Browse files
committed
šŸŒ #376 Fix conversion exception when a generator error couldn't be resolved
1 parent 44f7033 commit 4dd7b01

17 files changed

+237
-80
lines changed

ā€Žsrc/GalaxyCheck.Tests.V3/DomainGen.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,17 @@ from testFunction in TestFunctions.Nullary()
3535
public static Stable.IGen<int> Size() =>
3636
Stable.Gen.Int32().Between(GalaxyCheck.Gens.Parameters.Size.Zero.Value, GalaxyCheck.Gens.Parameters.Size.Max.Value);
3737

38-
public static Stable.IGen<int> Seed() => Stable.Gen.Int32();
38+
public static Stable.IGen<int> Seed() => Stable.Gen.Int32().NoShrink();
3939

4040
public static Stable.IGen<int?> SeedWaypoint() => Seed().OrNullStruct();
4141

4242
public static Stable.IGen<Property> ToProperty(this Stable.IGen<IGen<object>> metaGen) =>
4343
from gen in metaGen
4444
from testFunction in TestFunctions.Unary<object>()
4545
select new Property((IGen<Property.Test<object>>)Property.ForAll<object>(gen, testFunction));
46+
47+
public class SeedAttribute : Stable.GenAttribute
48+
{
49+
public override Stable.IGen Value => Seed();
50+
}
4651
}

ā€Žsrc/GalaxyCheck.Tests.V3/Features/Generators/Operators/AboutWhereOperator.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ static void Test(int seed, int size)
1515
var filteredGen = baseGen.Where(testFunction);
1616

1717
// Act
18-
var sample = filteredGen.Sample(args => args with { Seed = seed, Size = size });
18+
var sample = filteredGen.SampleTraversal(args => args with { Seed = seed, Size = size });
1919

2020
// Assert
2121
sample.Should().OnlyContain(x => testFunction(x));
@@ -35,7 +35,7 @@ static void Test(int seed, int size)
3535
var filteredGen = baseGen.Where(testFunction);
3636

3737
// Act
38-
var act = () => filteredGen.Sample(args => args with { Seed = seed, Size = size });
38+
var act = () => filteredGen.SampleTraversal(args => args with { Seed = seed, Size = size });
3939

4040
// Assert
4141
act.Should().NotThrow<Exceptions.GenExhaustionException>();

ā€Žsrc/GalaxyCheck.Tests.V3/Features/Generators/Reflection/AboutDynamicGeneration.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public void ItCanGenerateTheType(Type type)
1616
var gen = Gen.Create(type);
1717

1818
// Act
19-
var sample = gen.Sample();
19+
var sample = gen.SampleTraversal();
2020

2121
// Assert
2222
sample.Should().AllBeAssignableTo(type);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace GalaxyCheck.Tests.Features.Generators.Reflection.ObjectGraph;
2+
3+
public class AboutErrorReproduction
4+
{
5+
[Stable.Property]
6+
[InlineData(typeof(ExampleObjects.UnconstructableClass), "")]
7+
[InlineData(typeof(ClassWithNestedUnconstructableClass), " at path '$.Property'")]
8+
[InlineData(typeof(ClassWithNestedNullableUnconstructableClass), " at path '$.Property'")]
9+
[InlineData(typeof(List<ExampleObjects.UnconstructableClass>), " at path '$.[*]'")]
10+
public Stable.Property WhenTypeIsUnresolvable(Type type, string expectedPathDescription)
11+
{
12+
return Stable.Property.ForAll(DomainGen.Seed(), seed =>
13+
{
14+
// Arrange
15+
var gen = Gen.Create(type);
16+
17+
// Act
18+
var action = () => gen.Sample(args => args with { Seed = seed });
19+
20+
// Assert
21+
action
22+
.Should()
23+
.Throw<Exceptions.GenErrorException>()
24+
.WithGenErrorMessage($"could not resolve type '*'{expectedPathDescription}");
25+
});
26+
}
27+
28+
private class ClassWithNestedUnconstructableClass
29+
{
30+
public ExampleObjects.UnconstructableClass Property { get; set; } = null!;
31+
}
32+
33+
private class ClassWithNestedNullableUnconstructableClass
34+
{
35+
public ExampleObjects.UnconstructableClass? Property { get; set; } = null!;
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
ļ»ænamespace GalaxyCheck.Tests.Features.Generators.Reflection.ObjectGraph;
2+
3+
public static class ExampleObjects
4+
{
5+
public class ClassWithFailingConstructor
6+
{
7+
private static readonly Exception _exception = new("Constructor failed");
8+
9+
public ClassWithFailingConstructor()
10+
{
11+
throw _exception;
12+
}
13+
}
14+
15+
public class ClassWithNestedFailingConstructor
16+
{
17+
public ClassWithFailingConstructor Property { get; set; } = null!;
18+
}
19+
20+
public class ClassWithArrayOfFailingConstructors
21+
{
22+
public ClassWithFailingConstructor[] Property { get; set; } = null!;
23+
}
24+
25+
public class ClassWithArrayOfNestedFailingConstructors
26+
{
27+
public ClassWithNestedFailingConstructor[] Property { get; set; } = null!;
28+
}
29+
30+
public class ClassWithFailingConstructorAsConstructorParameter
31+
{
32+
public ClassWithFailingConstructor Property { get; set; }
33+
34+
public ClassWithFailingConstructorAsConstructorParameter(ClassWithFailingConstructor property)
35+
{
36+
Property = property;
37+
}
38+
}
39+
40+
public class ClassWithNestedFailingConstructorAsConstructorParameter
41+
{
42+
public ClassWithNestedFailingConstructor Property { get; set; }
43+
44+
public ClassWithNestedFailingConstructorAsConstructorParameter(ClassWithNestedFailingConstructor property)
45+
{
46+
Property = property;
47+
}
48+
}
49+
50+
public class ClassWithOneProperty
51+
{
52+
public int Property { get; set; }
53+
}
54+
55+
public class ClassWithTwoProperties
56+
{
57+
public int Property1 { get; set; }
58+
public int Property2 { get; set; }
59+
}
60+
61+
public class ClassWithOneNestedProperty
62+
{
63+
public ClassWithOneProperty Property { get; set; } = null!;
64+
}
65+
66+
public abstract class UnconstructableClass
67+
{
68+
}
69+
}

ā€Žsrc/GalaxyCheck.Tests.V3/GalaxyCheck.Tests.V3.csproj

+1-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="FluentAssertions" Version="6.11.0" />
15-
<PackageReference Include="GalaxyCheck.Stable.Xunit" Version="0.0.0-5085494672" />
15+
<PackageReference Include="GalaxyCheck.Stable.Xunit" Version="0.0.0-5131115847" />
1616
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
1717
<PackageReference Include="Snapshooter.Xunit" Version="0.5.8" />
1818
<PackageReference Include="xunit" Version="2.4.2" />
@@ -31,8 +31,4 @@
3131
<ProjectReference Include="..\GalaxyCheck\GalaxyCheck.csproj" />
3232
</ItemGroup>
3333

34-
<ItemGroup>
35-
<Folder Include="IntegrationTests" />
36-
</ItemGroup>
37-
3834
</Project>

ā€Žsrc/GalaxyCheck.Tests.V3/TestProxy.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public static CheckResult<object> Check(this PropertyProxy propertyProxy, Func<C
7575
/// Samples a generator by traversing the example space. This is useful to ensure that not only the produced value satisfies some invariant, but
7676
/// also all shrunk values.
7777
/// </summary>
78-
public static IReadOnlyCollection<T> Sample<T>(this IGen<T> gen, Func<SampleGenArgs, SampleGenArgs>? configure = null)
78+
public static IReadOnlyCollection<T> SampleTraversal<T>(this IGen<T> gen, Func<SampleGenArgs, SampleGenArgs>? configure = null)
7979
{
8080
var args = SampleGenArgs.Default;
8181
if (configure != null)
@@ -84,12 +84,26 @@ public static IReadOnlyCollection<T> Sample<T>(this IGen<T> gen, Func<SampleGenA
8484
}
8585

8686
return gen.Advanced
87-
.SampleOneExampleSpace(seed: args.Seed, size: args.Size)
87+
.SampleOneExampleSpace(seed: args.Seed, size: args.Size ?? 100)
8888
.Traverse()
8989
.Take(args.MaxSampleSize)
9090
.ToList();
9191
}
9292

93+
public static IReadOnlyCollection<T> Sample<T>(this IGen<T> gen, Func<SampleGenArgs, SampleGenArgs>? configure = null)
94+
{
95+
var args = SampleGenArgs.Default;
96+
if (configure != null)
97+
{
98+
args = configure(args);
99+
}
100+
101+
return gen
102+
.Sample(seed: args.Seed, size: args.Size ?? 100)
103+
.Take(args.MaxSampleSize)
104+
.ToList();
105+
}
106+
93107
public record CheckPropertyArgs(int Seed, int? Size, string? Replay)
94108
{
95109
public static CheckPropertyArgs Default { get; } = new(0, null, null);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using FluentAssertions.Specialized;
2+
3+
namespace GalaxyCheck.Tests.TestUtility;
4+
5+
public static class AssertionExtensions
6+
{
7+
public static ExceptionAssertions<Exceptions.GenErrorException> WithGenErrorMessage(
8+
this ExceptionAssertions<Exceptions.GenErrorException> assertions,
9+
string messageBody)
10+
{
11+
return assertions.WithMessage("Error during generation: " + messageBody);
12+
}
13+
}

ā€Žsrc/GalaxyCheck/Gens/FactoryGen.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ protected override IGen<T> Get
229229
var context = ReflectedGenHandlerContext.Create(typeof(T), NullabilityInfo);
230230

231231
return ReflectedGenBuilder
232-
.Build(typeof(T), RegisteredGensByType, MemberOverrides, Error, context)
232+
.Build(typeof(T), RegisteredGensByType, MemberOverrides, context)
233233
.Cast<T>();
234234
}
235235
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
ļ»ænamespace GalaxyCheck.Gens.ReflectedGenHelpers
1+
ļ»æusing System;
2+
3+
namespace GalaxyCheck.Gens.ReflectedGenHelpers
24
{
35
internal delegate IGen ErrorFactory(string message);
4-
5-
internal delegate IGen ContextualErrorFactory(string message, ReflectedGenHandlerContext context);
66
}

ā€Žsrc/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenBuilder.cs

+12-12
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,30 @@ public static IGen Build(
1111
Type type,
1212
IReadOnlyDictionary<Type, Func<IGen>> registeredGensByType,
1313
IReadOnlyList<ReflectedGenMemberOverride> memberOverrides,
14-
ErrorFactory errorFactory,
1514
ReflectedGenHandlerContext context)
1615
{
17-
ContextualErrorFactory contextualErrorFactory = (message, context) =>
18-
{
19-
var suffix = context.Members.Count() == 1 ? "" : $" at path '{context.MemberPath}'";
20-
return errorFactory(message + suffix);
21-
};
22-
2316
var genFactoriesByPriority = new List<IReflectedGenHandler>
2417
{
2518
new MemberOverrideReflectedGenHandler(memberOverrides),
2619
new NullableGenHandler(),
27-
new RegistryReflectedGenHandler(registeredGensByType, contextualErrorFactory),
20+
new RegistryReflectedGenHandler(registeredGensByType),
2821
new CollectionReflectedGenHandler(),
2922
new ArrayReflectedGenHandler(),
3023
new EnumReflectedGenHandler(),
31-
new DefaultConstructorReflectedGenHandler(contextualErrorFactory),
32-
new NonDefaultConstructorReflectedGenHandler(contextualErrorFactory),
24+
new DefaultConstructorReflectedGenHandler(),
25+
new NonDefaultConstructorReflectedGenHandler(),
3326
};
3427

35-
var compositeReflectedGenFactory = new CompositeReflectedGenHandler(genFactoriesByPriority, contextualErrorFactory);
28+
var compositeReflectedGenFactory = new CompositeReflectedGenHandler(genFactoriesByPriority);
3629

37-
return compositeReflectedGenFactory.CreateGen(type, context);
30+
try
31+
{
32+
return compositeReflectedGenFactory.CreateGen(type, context);
33+
}
34+
catch (Exception ex)
35+
{
36+
return Gen.Advanced.Error(type, $"{ex.Message} \n {ex.StackTrace}");
37+
}
3838
}
3939
}
4040
}

ā€Žsrc/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlerContext.cs

+12
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,17 @@ public static int CalculateStableSeed(this ReflectedGenHandlerContext context) =
3636
return acc + curr;
3737
}
3838
});
39+
40+
public static IGen<T> Error<T>(this ReflectedGenHandlerContext context, string message)
41+
{
42+
var suffix = context.Members.Count == 1 ? "" : $" at path '{context.MemberPath}'";
43+
return Gen.Advanced.Error<T>(message + suffix);
44+
}
45+
46+
public static IGen Error(this ReflectedGenHandlerContext context, Type type, string message)
47+
{
48+
var suffix = context.Members.Count == 1 ? "" : $" at path '{context.MemberPath}'";
49+
return Gen.Advanced.Error(type, message + suffix);
50+
}
3951
}
4052
}

ā€Žsrc/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/CompositeReflectedGenHandler.cs

+3-7
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,10 @@ namespace GalaxyCheck.Gens.ReflectedGenHelpers.ReflectedGenHandlers
77
internal class CompositeReflectedGenHandler : IReflectedGenHandler
88
{
99
private readonly IReadOnlyList<IReflectedGenHandler> _genHandlersByPriority;
10-
private readonly ContextualErrorFactory _errorFactory;
1110

12-
public CompositeReflectedGenHandler(
13-
IReadOnlyList<IReflectedGenHandler> genHandlersByPriority,
14-
ContextualErrorFactory errorFactory)
11+
public CompositeReflectedGenHandler(IReadOnlyList<IReflectedGenHandler> genHandlersByPriority)
1512
{
1613
_genHandlersByPriority = genHandlersByPriority;
17-
_errorFactory = errorFactory;
1814
}
1915

2016
public bool CanHandleGen(Type type, ReflectedGenHandlerContext context) =>
@@ -24,7 +20,7 @@ public IGen CreateGen(IReflectedGenHandler innerHandler, Type type, ReflectedGen
2420
{
2521
if (context.TypeHistory.Skip(1).Any(t => t == type))
2622
{
27-
return _errorFactory($"detected circular reference on type '{type}'", context);
23+
return context.Error(type, $"detected circular reference on type '{type}'");
2824
}
2925

3026
var gen = _genHandlersByPriority
@@ -34,7 +30,7 @@ public IGen CreateGen(IReflectedGenHandler innerHandler, Type type, ReflectedGen
3430

3531
if (gen == null)
3632
{
37-
return _errorFactory($"could not resolve type '{type}'", context);
33+
return context.Error(type, $"could not resolve type '{type}'");
3834
}
3935

4036
return gen;

0 commit comments

Comments
Ā (0)