Skip to content

Commit 3fc4ac0

Browse files
committed
add test case for reproducing issue #20
1 parent f376b0b commit 3fc4ac0

File tree

2 files changed

+187
-1
lines changed

2 files changed

+187
-1
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
#if SYSTEM_REFLECTION_NULLABILITYINFOCONTEXT
4+
5+
#nullable enable annotations
6+
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Reflection;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
using NUnit.Framework;
14+
15+
namespace Smdn.Reflection.ReverseGenerating;
16+
17+
public partial class CSharpFormatterTests {
18+
#pragma warning disable CS0649
19+
private class C {
20+
public string? F0;
21+
public string? F1;
22+
public string? F2;
23+
public string? F3;
24+
public string? F4;
25+
public string? F5;
26+
public string? F6;
27+
public string? F7;
28+
public string? F8;
29+
public string? F9;
30+
31+
public string? P0 { get; set; }
32+
public string? P1 { get; set; }
33+
public string? P2 { get; set; }
34+
public string? P3 { get; set; }
35+
public string? P4 { get; set; }
36+
public string? P5 { get; set; }
37+
public string? P6 { get; set; }
38+
public string? P7 { get; set; }
39+
public string? P8 { get; set; }
40+
public string? P9 { get; set; }
41+
42+
public event EventHandler? E0;
43+
public event EventHandler? E1;
44+
public event EventHandler? E2;
45+
public event EventHandler? E3;
46+
public event EventHandler? E4;
47+
public event EventHandler? E5;
48+
public event EventHandler? E6;
49+
public event EventHandler? E7;
50+
public event EventHandler? E8;
51+
public event EventHandler? E9;
52+
53+
public void M(
54+
string? p0,
55+
string? p1,
56+
string? p2,
57+
string? p3,
58+
string? p4,
59+
string? p5,
60+
string? p6,
61+
string? p7,
62+
string? p8,
63+
string? p9
64+
) => throw new NotImplementedException();
65+
}
66+
#pragma warning restore CS0649
67+
68+
[Test]
69+
public Task FormatTypeName_FieldInfo_Concurrent()
70+
=> FormatTypeName_Concurrent(
71+
typeof(C),
72+
typeof(C).GetField(nameof(C.F0)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F0)}"),
73+
typeof(C).GetField(nameof(C.F1)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F1)}"),
74+
typeof(C).GetField(nameof(C.F2)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F2)}"),
75+
typeof(C).GetField(nameof(C.F3)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F3)}"),
76+
typeof(C).GetField(nameof(C.F4)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F4)}"),
77+
typeof(C).GetField(nameof(C.F5)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F5)}"),
78+
typeof(C).GetField(nameof(C.F6)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F6)}"),
79+
typeof(C).GetField(nameof(C.F7)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F7)}"),
80+
typeof(C).GetField(nameof(C.F8)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F8)}"),
81+
typeof(C).GetField(nameof(C.F9)) ?? throw new InvalidOperationException($"field not found: {nameof(C.F9)}")
82+
);
83+
84+
[Test]
85+
public Task FormatTypeName_PropertyInfo_Concurrent()
86+
=> FormatTypeName_Concurrent(
87+
typeof(C),
88+
typeof(C).GetProperty(nameof(C.P0)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P0)}"),
89+
typeof(C).GetProperty(nameof(C.P1)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P1)}"),
90+
typeof(C).GetProperty(nameof(C.P2)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P2)}"),
91+
typeof(C).GetProperty(nameof(C.P3)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P3)}"),
92+
typeof(C).GetProperty(nameof(C.P4)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P4)}"),
93+
typeof(C).GetProperty(nameof(C.P5)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P5)}"),
94+
typeof(C).GetProperty(nameof(C.P6)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P6)}"),
95+
typeof(C).GetProperty(nameof(C.P7)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P7)}"),
96+
typeof(C).GetProperty(nameof(C.P8)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P8)}"),
97+
typeof(C).GetProperty(nameof(C.P9)) ?? throw new InvalidOperationException($"property not found: {nameof(C.P9)}")
98+
);
99+
100+
[Test]
101+
public Task FormatTypeName_EventInfo_Concurrent()
102+
=> FormatTypeName_Concurrent(
103+
typeof(C),
104+
typeof(C).GetEvent(nameof(C.E0)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E0)}"),
105+
typeof(C).GetEvent(nameof(C.E1)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E1)}"),
106+
typeof(C).GetEvent(nameof(C.E2)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E2)}"),
107+
typeof(C).GetEvent(nameof(C.E3)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E3)}"),
108+
typeof(C).GetEvent(nameof(C.E4)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E4)}"),
109+
typeof(C).GetEvent(nameof(C.E5)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E5)}"),
110+
typeof(C).GetEvent(nameof(C.E6)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E6)}"),
111+
typeof(C).GetEvent(nameof(C.E7)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E7)}"),
112+
typeof(C).GetEvent(nameof(C.E8)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E8)}"),
113+
typeof(C).GetEvent(nameof(C.E9)) ?? throw new InvalidOperationException($"event not found: {nameof(C.E9)}")
114+
);
115+
116+
[Test]
117+
public Task FormatTypeName_ParameterInfo_Concurrent()
118+
=> FormatTypeName_Concurrent(
119+
typeof(C),
120+
typeof(C).GetMethod(nameof(C.M))!.GetParameters()
121+
);
122+
123+
private async Task FormatTypeName_Concurrent<TMemberInfo>(Type declaringTypeOfTargets, params TMemberInfo[] targets)
124+
where TMemberInfo : class
125+
{
126+
Func<object, NullabilityInfoContext, string> formatTypeName;
127+
128+
if (typeof(TMemberInfo) == typeof(FieldInfo))
129+
formatTypeName = new Func<object, NullabilityInfoContext, string>(static (f, ctx) => CSharpFormatter.FormatTypeName((FieldInfo)f, ctx));
130+
else if (typeof(TMemberInfo) == typeof(PropertyInfo))
131+
formatTypeName = new Func<object, NullabilityInfoContext, string>(static (p, ctx) => CSharpFormatter.FormatTypeName((PropertyInfo)p, ctx));
132+
else if (typeof(TMemberInfo) == typeof(EventInfo))
133+
formatTypeName = new Func<object, NullabilityInfoContext, string>(static (ev, ctx) => CSharpFormatter.FormatTypeName((EventInfo)ev, ctx));
134+
else if (typeof(TMemberInfo) == typeof(ParameterInfo))
135+
formatTypeName = new Func<object, NullabilityInfoContext, string>(static (para, ctx) => CSharpFormatter.FormatTypeName((ParameterInfo)para, ctx));
136+
else
137+
throw new NotSupportedException();
138+
139+
const int maxNumberOfRepeat = 20;
140+
var participantCount = Environment.ProcessorCount * 2;
141+
var expectedExceptionThrown = false;
142+
143+
for (var n = 0; n < maxNumberOfRepeat; n++) {
144+
using var barrier = new Barrier(participantCount);
145+
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10.0));
146+
147+
var parallelOptions = new ParallelOptions() {
148+
MaxDegreeOfParallelism = participantCount,
149+
CancellationToken = cancellationTokenSource.Token,
150+
};
151+
152+
// confirms that an ArgumentException or IndexOutOfRangeException is thrown when
153+
// NullabilityInfoContext.Create() is called in a concurrency.
154+
var ctx = new NullabilityInfoContext();
155+
156+
try {
157+
await Parallel.ForEachAsync(Enumerable.Range(0, participantCount), parallelOptions, (_, cancellationToken) => {
158+
barrier.SignalAndWait(cancellationToken);
159+
160+
foreach (var target in targets) {
161+
formatTypeName(target, ctx);
162+
}
163+
164+
return default; // ValueTask
165+
});
166+
}
167+
catch (ArgumentException ex) when (
168+
(ex.Message ?? string.Empty).Contains(declaringTypeOfTargets.FullName!, StringComparison.Ordinal)
169+
) {
170+
// excepted exception: "An item with the same key has already been added."
171+
expectedExceptionThrown = true;
172+
}
173+
catch (IndexOutOfRangeException) {
174+
// excepted exception: "Index was outside the bounds of the array."
175+
expectedExceptionThrown = true;
176+
}
177+
178+
if (expectedExceptionThrown)
179+
break;
180+
}
181+
182+
if (!expectedExceptionThrown)
183+
Assert.Warn($"The operation succeeded unexpectedly. ({formatTypeName.Method.Name}");
184+
}
185+
}
186+
#endif // SYSTEM_REFLECTION_NULLABILITYINFOCONTEXT

tests/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating/CSharpFormatter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class CCloseGenericNestedNested : CGenericNestedNested<object> { }
3232
}
3333

3434
[TestFixture]
35-
public class CSharpFormatterTests {
35+
public partial class CSharpFormatterTests {
3636
[TestCase(typeof(int), "int")]
3737
[TestCase(typeof(int[]), "int[]")]
3838
[TestCase(typeof(int[,]), "int[,]")]

0 commit comments

Comments
 (0)