Skip to content

Commit 8dfd600

Browse files
committed
🌍 #371 Fix infinite rendering loop on static properties
It's not meaningful to render static properties on a (non-static) counterexample, so don't. Also, this was causing an infinite loop when rendering a counterexample containing System.Drawing.Color. I've put in an extra safeguard, so that the object renderer checks that it isn't rendering a type that it hasn't just seen.
1 parent 63693df commit 8dfd600

File tree

10 files changed

+149
-29
lines changed

10 files changed

+149
-29
lines changed

.editorconfig

+7-6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ indent_size = 4
1010
indent_size = 4
1111
insert_final_newline = true
1212
charset = utf-8-bom
13+
max_line_length = 150
1314

1415
# Xml project files
1516
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
@@ -84,9 +85,9 @@ dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public
8485
dotnet_naming_symbols.public_internal_fields.applicable_kinds = field
8586
# private_protected_fields - Define private and protected fields
8687
dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected
87-
dotnet_naming_symbols.private_protected_fields.applicable_kinds = field
88+
dotnet_naming_symbols.private_protected_fields.applicable_kinds = field
8889
# private_fields - Define private fields
89-
dotnet_naming_symbols.private_fields.applicable_kinds = field
90+
dotnet_naming_symbols.private_fields.applicable_kinds = field
9091
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
9192
# public_symbols - Define any public symbol
9293
dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal
@@ -107,7 +108,7 @@ dotnet_naming_style.pascal_case.capitalization = pascal_case
107108
dotnet_naming_style.first_upper.capitalization = first_word_upper
108109
# prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I'
109110
dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I
110-
dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case
111+
dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case
111112
# prefix_underscore - Define the prefix underscore style
112113
dotnet_naming_style.prefix_underscore.capitalization = camel_case
113114
dotnet_naming_style.prefix_underscore.required_prefix = _
@@ -124,15 +125,15 @@ dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pasca
124125
# Static readonly fields must be PascalCase
125126
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning
126127
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields
127-
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case
128+
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case
128129
# Private readonly fields be prefixed with underscore
129130
dotnet_naming_rule.private_readonly_fields_with_underscore.severity = warning
130131
dotnet_naming_rule.private_readonly_fields_with_underscore.symbols = private_readonly_fields
131132
dotnet_naming_rule.private_readonly_fields_with_underscore.style = prefix_underscore
132133
# Private fields must be prefixed with underscore
133134
dotnet_naming_rule.private_members_with_underscore.severity = warning
134135
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
135-
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
136+
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
136137
# Public and internal fields must be PascalCase
137138
dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning
138139
dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields
@@ -161,7 +162,7 @@ dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interf
161162
# C# Code Style Settings
162163
# See https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
163164
# See http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers
164-
[*.cs,csx,cake]
165+
[*.cs,csx, cake]
165166
# Indentation Options
166167
csharp_indent_block_contents = true:warning
167168
csharp_indent_braces = false:warning

GalaxyCheck.sln

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GalaxyCheck.Xunit.CodeAnaly
2525
EndProject
2626
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GalaxyCheck.Xunit.CodeAnalysis.Tests", "src\GalaxyCheck.Xunit.CodeAnalysis.Tests\GalaxyCheck.Xunit.CodeAnalysis.Tests.csproj", "{0BBFED38-BA46-451A-B346-67F29CFCDA04}"
2727
EndProject
28+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GalaxyCheck.Tests.V3", "src\GalaxyCheck.Tests.V3\GalaxyCheck.Tests.V3.csproj", "{0C715CD7-6439-49FB-BB80-3D80D24AFE9F}"
29+
EndProject
2830
Global
2931
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3032
Debug|Any CPU = Debug|Any CPU
@@ -63,6 +65,10 @@ Global
6365
{0BBFED38-BA46-451A-B346-67F29CFCDA04}.Debug|Any CPU.Build.0 = Debug|Any CPU
6466
{0BBFED38-BA46-451A-B346-67F29CFCDA04}.Release|Any CPU.ActiveCfg = Release|Any CPU
6567
{0BBFED38-BA46-451A-B346-67F29CFCDA04}.Release|Any CPU.Build.0 = Release|Any CPU
68+
{0C715CD7-6439-49FB-BB80-3D80D24AFE9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69+
{0C715CD7-6439-49FB-BB80-3D80D24AFE9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
70+
{0C715CD7-6439-49FB-BB80-3D80D24AFE9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
71+
{0C715CD7-6439-49FB-BB80-3D80D24AFE9F}.Release|Any CPU.Build.0 = Release|Any CPU
6672
EndGlobalSection
6773
GlobalSection(SolutionProperties) = preSolution
6874
HideSolutionNode = FALSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Feature Interactions
2+
3+
This is a collection of tests about interesting interactions between mostly-isolated features. Especially compositions
4+
that result in hairy interactions, or compositions that are not intuitive and need to be documented.
5+
6+
Features are tested in isolation elsewhere.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using GalaxyCheck;
2+
3+
namespace GalaxyCheck_Tests_V3.Features.Properties;
4+
5+
public class AboutFailingTests
6+
{
7+
[Fact]
8+
public void ItShouldFail()
9+
{
10+
// Arrange
11+
var property = Gen.Constant(true).ForAll(x => x == false);
12+
13+
// Act
14+
var result = property.Check();
15+
16+
// Assert
17+
result.Falsified.Should().BeTrue();
18+
result.Iterations.Should().Be(1);
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
10+
<RootNamespace>GalaxyCheck_Tests_V3</RootNamespace>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="FluentAssertions" Version="6.11.0"/>
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
16+
<PackageReference Include="NebulaCheck.Xunit" Version="0.0.0-4021874656"/>
17+
<PackageReference Include="xunit" Version="2.4.2"/>
18+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
<PrivateAssets>all</PrivateAssets>
21+
</PackageReference>
22+
<PackageReference Include="coverlet.collector" Version="3.1.2">
23+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
24+
<PrivateAssets>all</PrivateAssets>
25+
</PackageReference>
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<ProjectReference Include="..\GalaxyCheck.Xunit\GalaxyCheck.Xunit.csproj"/>
30+
<ProjectReference Include="..\GalaxyCheck\GalaxyCheck.csproj"/>
31+
</ItemGroup>
32+
33+
</Project>

src/GalaxyCheck.Tests.V3/TestProxy.cs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Reflection;
2+
using GalaxyCheck;
3+
using GalaxyCheck.Runners.Check;
4+
5+
namespace GalaxyCheck_Tests_V3;
6+
7+
public static class TestProxy
8+
{
9+
public static CheckResult<T> Check<T>(this Property<T> property, Func<CheckPropertyArgs, CheckPropertyArgs>? configure = null)
10+
{
11+
var args = CheckPropertyArgs.Default;
12+
if (configure != null)
13+
{
14+
args = configure(args);
15+
}
16+
17+
// Rider can't handle static partial extensions :( Completely shits itself.
18+
var result = typeof(Extensions)
19+
.GetMethod(nameof(Check))!
20+
.MakeGenericMethod(typeof(T))
21+
.Invoke(null, new object?[] { property, null, args.Seed, null, null, null, true })!;
22+
return (CheckResult<T>)result;
23+
}
24+
25+
public record CheckPropertyArgs(int Seed)
26+
{
27+
public static CheckPropertyArgs Default { get; } = new(0);
28+
}
29+
}

src/GalaxyCheck.Tests.V3/Usings.cs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
global using Xunit;
2+
global using FluentAssertions;

src/GalaxyCheck.Tests/RendererTests/AboutUnaryExamples.cs

+23-5
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void ItCanHandleCircularReferences()
4646
{
4747
{ new Tuple<int, int, int>(1, 2, 3), new object[] { 1, 2, 3 } },
4848
{ (1, 2, 3), new object[] { 1, 2, 3 } },
49-
{ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), new object[] {1, 2, 3, 4, 5, 6, 7 } },
49+
{ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), new object[] { 1, 2, 3, 4, 5, 6, 7 } },
5050
};
5151

5252
[Theory]
@@ -87,8 +87,21 @@ public void TupleExamples(object value, string expectedRendering)
8787
{ new Dictionary<RecordObj, int> { { new RecordObj(1, 2, 3), 4 } }, "[{ Key = { A = 1, B = 2, C = 3 }, Value = 4 }]" },
8888
{ new RecordObj(1, 2, 3), "{ A = 1, B = 2, C = 3 }" },
8989
{ new RecordObjWithList(new List<int> { 1, 2, 3 }), "{ As = [1, 2, 3] }" },
90-
{ new RecordObjWithManyProperties(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), "{ A = 1, B = 2, C = 3, D = 4, E = 5, F = 6, G = 7, H = 8, I = 9, J = 10, ... }" },
91-
{ new { a = new { b = new { c = new { d = new { e = new { f = new { g = new { h = new { i = new { j = new { k = new { } } } } } } } } } } } }, "{ a = { b = { c = { d = { e = { f = { g = { h = { i = { j = ... } } } } } } } } } }" },
90+
{
91+
new RecordObjWithManyProperties(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
92+
"{ A = 1, B = 2, C = 3, D = 4, E = 5, F = 6, G = 7, H = 8, I = 9, J = 10, ... }"
93+
},
94+
{ new RecordWithStaticProperty(1), "{ A = 1 }" },
95+
{
96+
new
97+
{
98+
a = new
99+
{
100+
b = new { c = new { d = new { e = new { f = new { g = new { h = new { i = new { j = new { k = new { } } } } } } } } } }
101+
}
102+
},
103+
"{ a = { b = { c = { d = { e = { f = { g = { h = { i = { j = ... } } } } } } } } } }"
104+
},
92105
{ new ClassObj(1, 2, 3), "{ A = 1, B = 2, C = 3 }" },
93106
{ new { a = 1, b = 2, c = 3 }, "{ a = 1, b = 2, c = 3 }" },
94107
{ new Func<int, bool>((_) => true), "System.Func`2[System.Int32,System.Boolean]" },
@@ -147,10 +160,15 @@ public enum Operations
147160
public struct Struct
148161
{
149162
public int A { get; set; }
150-
163+
151164
public int B { get; set; }
152-
165+
153166
public int C { get; set; }
154167
}
168+
169+
public record RecordWithStaticProperty(int A)
170+
{
171+
public static int B => 2;
172+
}
155173
}
156174
}

src/GalaxyCheck/Runners/Check.cs

+14-14
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ namespace GalaxyCheck
1515
public static partial class Extensions
1616
{
1717
public static CheckResult<T> Check<T>(
18-
this IGen<Property.Test<T>> property,
19-
int? iterations = null,
20-
int? seed = null,
21-
int? size = null,
22-
int? shrinkLimit = null,
23-
string? replay = null,
24-
bool deepCheck = true)
18+
this IGen<Property.Test<T>> property,
19+
int? iterations = null,
20+
int? seed = null,
21+
int? size = null,
22+
int? shrinkLimit = null,
23+
string? replay = null,
24+
bool deepCheck = true)
2525
{
2626
var resolvedIterations = iterations ?? 100;
2727
var (initialSize, resizeStrategy) = SizingAspects<T>.Resolve(size == null ? null : new Size(size.Value), resolvedIterations);
@@ -60,13 +60,13 @@ public static CheckResult<T> Check<T>(
6060
}
6161

6262
public static async Task<CheckResult<T>> CheckAsync<T>(
63-
this IGen<Property.AsyncTest<T>> property,
64-
int? iterations = null,
65-
int? seed = null,
66-
int? size = null,
67-
int? shrinkLimit = null,
68-
string? replay = null,
69-
bool deepCheck = true)
63+
this IGen<Property.AsyncTest<T>> property,
64+
int? iterations = null,
65+
int? seed = null,
66+
int? size = null,
67+
int? shrinkLimit = null,
68+
string? replay = null,
69+
bool deepCheck = true)
7070
{
7171
var resolvedIterations = iterations ?? 100;
7272
var (initialSize, resizeStrategy) = SizingAspectsAsync<T>.Resolve(size == null ? null : new Size(size.Value), resolvedIterations);

src/GalaxyCheck/Runners/ExampleRenderer.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Generic;
44
using System.Collections.Immutable;
55
using System.Linq;
6+
using System.Reflection;
67

78
namespace GalaxyCheck.Runners
89
{
@@ -103,7 +104,7 @@ public string Render(object? obj, IExampleRendererHandler renderer, ImmutableLis
103104

104105
try
105106
{
106-
var innerHandler = _innerHandlers.Where(h => h.CanRender(obj)).FirstOrDefault();
107+
var innerHandler = _innerHandlers.FirstOrDefault(h => h.CanRender(obj));
107108

108109
if (innerHandler == null)
109110
{
@@ -202,7 +203,7 @@ private class TupleLiteralRendererHandler : BaseTupleRendererHandler
202203
var fields = obj!.GetType().GetFields();
203204

204205
var fieldsToRender = fields.Where(f => f.Name != "Rest").ToList();
205-
foreach (var field in fields.Where(f => f.Name != "Rest").ToList())
206+
foreach (var field in fieldsToRender)
206207
{
207208
yield return (field.Name, field.GetValue(obj));
208209
}
@@ -229,7 +230,12 @@ public bool CanRender(object? obj) =>
229230

230231
public string Render(object? obj, IExampleRendererHandler renderer, ImmutableList<object?> path)
231232
{
232-
var properties = obj!.GetType().GetProperties();
233+
if (path.Where(x => x != obj).Select(x => x?.GetType()).Contains(obj!.GetType()))
234+
{
235+
return "<Circular reference>";
236+
}
237+
238+
var properties = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
233239

234240
var propertiesToRender = properties.Take(_propertyLimit).ToList();
235241
var hasMoreProperties = properties.Skip(_propertyLimit).Any();
@@ -266,6 +272,5 @@ value is not null &&
266272
value.GetType().GetInterface("System.Runtime.CompilerServices.ITuple") != null &&
267273
value!.GetType().GetFields().Any() == true;
268274
}
269-
270275
}
271276
}

0 commit comments

Comments
 (0)