Skip to content

Commit cb777e1

Browse files
Compile SourceGenerator-tests to ensure that the sample itself is valid (#8484)
Co-authored-by: Michael Staib <[email protected]>
1 parent e560395 commit cb777e1

13 files changed

+978
-65
lines changed

src/HotChocolate/Core/test/Types.Analyzers.Tests/DataLoaderTests.cs

Lines changed: 204 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,35 @@ public class Entity
7979
""").MatchMarkdownAsync();
8080
}
8181

82+
[Fact]
83+
public async Task GenerateSource_BatchDataLoader_Nullable_Object_MatchesSnapshot()
84+
{
85+
await TestHelper.GetGeneratedSourceSnapshot(
86+
"""
87+
using System.Collections.Generic;
88+
using System.Threading;
89+
using System.Threading.Tasks;
90+
using HotChocolate;
91+
using GreenDonut;
92+
93+
namespace TestNamespace;
94+
95+
internal static class TestClass
96+
{
97+
[DataLoader]
98+
public static Task<IReadOnlyDictionary<int, Entity?>> GetEntityByIdAsync(
99+
IReadOnlyList<int> entityIds,
100+
CancellationToken cancellationToken)
101+
=> default!;
102+
}
103+
104+
public class Entity
105+
{
106+
public int Id { get; set; }
107+
}
108+
""").MatchMarkdownAsync();
109+
}
110+
82111
[Fact]
83112
public async Task GenerateSource_BatchDataLoader_With_Group_MatchesSnapshot()
84113
{
@@ -200,6 +229,96 @@ public class Entity
200229
""").MatchMarkdownAsync();
201230
}
202231

232+
[Fact]
233+
public async Task GenerateSource_GroupedDataLoader_Nullable_Object_MatchesSnapshot()
234+
{
235+
await TestHelper.GetGeneratedSourceSnapshot(
236+
"""
237+
using System.Collections.Generic;
238+
using System.Linq;
239+
using System.Threading;
240+
using System.Threading.Tasks;
241+
using HotChocolate;
242+
using GreenDonut;
243+
244+
namespace TestNamespace;
245+
246+
internal static class TestClass
247+
{
248+
[DataLoader]
249+
public static Task<ILookup<int, Entity?>> GetEntitiesByIdAsync(
250+
IReadOnlyList<int> entityIds,
251+
CancellationToken cancellationToken)
252+
=> default!;
253+
}
254+
255+
public class Entity
256+
{
257+
public int Id { get; set; }
258+
}
259+
""").MatchMarkdownAsync();
260+
}
261+
262+
[Fact]
263+
public async Task GenerateSource_GroupedDataLoader_ValueType_MatchesSnapshot()
264+
{
265+
await TestHelper.GetGeneratedSourceSnapshot(
266+
"""
267+
using System.Collections.Generic;
268+
using System.Linq;
269+
using System.Threading;
270+
using System.Threading.Tasks;
271+
using HotChocolate;
272+
using GreenDonut;
273+
274+
namespace TestNamespace;
275+
276+
internal static class TestClass
277+
{
278+
[DataLoader]
279+
public static Task<ILookup<int, long>> GetEntitiesByIdAsync(
280+
IReadOnlyList<int> entityIds,
281+
CancellationToken cancellationToken)
282+
=> default!;
283+
}
284+
285+
public class Entity
286+
{
287+
public int Id { get; set; }
288+
}
289+
""").MatchMarkdownAsync();
290+
}
291+
292+
[Fact]
293+
public async Task GenerateSource_GroupedDataLoader_Nullable_ValueType_MatchesSnapshot()
294+
{
295+
await TestHelper.GetGeneratedSourceSnapshot(
296+
"""
297+
using System.Collections.Generic;
298+
using System.Linq;
299+
using System.Threading;
300+
using System.Threading.Tasks;
301+
using HotChocolate;
302+
using GreenDonut;
303+
304+
namespace TestNamespace;
305+
306+
internal static class TestClass
307+
{
308+
[DataLoader]
309+
public static Task<ILookup<int, long?>> GetEntitiesByIdAsync(
310+
IReadOnlyList<int> entityIds,
311+
CancellationToken cancellationToken)
312+
=> default!;
313+
}
314+
315+
public class Entity
316+
{
317+
public int Id { get; set; }
318+
}
319+
""").MatchMarkdownAsync();
320+
}
321+
203322
[Fact]
204323
public async Task GenerateSource_CacheDataLoader_MatchesSnapshot()
205324
{
@@ -228,6 +347,90 @@ public class Entity
228347
""").MatchMarkdownAsync();
229348
}
230349

350+
[Fact]
351+
public async Task GenerateSource_CacheDataLoader_Nullable_Object_MatchesSnapshot()
352+
{
353+
await TestHelper.GetGeneratedSourceSnapshot(
354+
"""
355+
using System.Threading;
356+
using System.Threading.Tasks;
357+
using HotChocolate;
358+
using GreenDonut;
359+
360+
namespace TestNamespace;
361+
362+
internal static class TestClass
363+
{
364+
[DataLoader]
365+
public static Task<Entity?> GetEntityByIdAsync(
366+
int entityId,
367+
CancellationToken cancellationToken)
368+
=> default!;
369+
}
370+
371+
public class Entity
372+
{
373+
public int Id { get; set; }
374+
}
375+
""").MatchMarkdownAsync();
376+
}
377+
378+
[Fact]
379+
public async Task GenerateSource_CacheDataLoader_ValueType_MatchesSnapshot()
380+
{
381+
await TestHelper.GetGeneratedSourceSnapshot(
382+
"""
383+
using System.Threading;
384+
using System.Threading.Tasks;
385+
using HotChocolate;
386+
using GreenDonut;
387+
388+
namespace TestNamespace;
389+
390+
internal static class TestClass
391+
{
392+
[DataLoader]
393+
public static Task<long> GetEntityByIdAsync(
394+
int entityId,
395+
CancellationToken cancellationToken)
396+
=> default!;
397+
}
398+
399+
public class Entity
400+
{
401+
public int Id { get; set; }
402+
}
403+
""").MatchMarkdownAsync();
404+
}
405+
406+
[Fact]
407+
public async Task GenerateSource_CacheDataLoader_Nullable_ValueType_MatchesSnapshot()
408+
{
409+
await TestHelper.GetGeneratedSourceSnapshot(
410+
"""
411+
using System.Threading;
412+
using System.Threading.Tasks;
413+
using HotChocolate;
414+
using GreenDonut;
415+
416+
namespace TestNamespace;
417+
418+
internal static class TestClass
419+
{
420+
[DataLoader]
421+
public static Task<long?> GetEntityByIdAsync(
422+
int entityId,
423+
CancellationToken cancellationToken)
424+
=> default!;
425+
}
426+
427+
public class Entity
428+
{
429+
public int Id { get; set; }
430+
}
431+
""").MatchMarkdownAsync();
432+
}
433+
231434
[Fact]
232435
public async Task GenerateSource_GenericBatchDataLoader_MatchesSnapshot()
233436
{
@@ -324,7 +527,7 @@ public static Task<IDictionary<int, Entity2>> GetEntityByIdAsync(
324527
CancellationToken cancellationToken)
325528
=> default!;
326529
327-
public static KeyValuePair<int, Entity2> CreateLookupKey(Entity1 key)
530+
public static KeyValuePair<int, Entity2>? CreateLookupKey(Entity1 key)
328531
=> default!;
329532
}
330533

src/HotChocolate/Core/test/Types.Analyzers.Tests/RequestMiddlewareTests.cs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@ public class RequestMiddlewareTests
77
[Fact]
88
public async Task GenerateSource_RequestMiddleware_MatchesSnapshot()
99
{
10-
var currentUiCulture = CultureInfo.CurrentUICulture;
11-
try
12-
{
13-
// Snapshot contains localized strings -> adjust culture
14-
CultureInfo.CurrentUICulture = new CultureInfo("en-US");
15-
await TestHelper.GetGeneratedSourceSnapshot(
10+
await TestHelper.GetGeneratedSourceSnapshot(
11+
[
1612
"""
1713
#nullable enable
1814
using System.Threading.Tasks;
@@ -40,7 +36,7 @@ public class SomeRequestMiddleware(
4036
#pragma warning restore CS9113
4137
{
4238
public async ValueTask InvokeAsync(
43-
IRequestContext context,
39+
RequestContext context,
4440
#pragma warning disable CS9113
4541
Service1 service1,
4642
Service2? service2)
@@ -52,11 +48,8 @@ public async ValueTask InvokeAsync(
5248
5349
public class Service1;
5450
public class Service2;
55-
""").MatchMarkdownAsync();
56-
}
57-
finally
58-
{
59-
CultureInfo.CurrentUICulture = currentUiCulture;
60-
}
51+
"""
52+
],
53+
enableInterceptors: true).MatchMarkdownAsync();
6154
}
6255
}

src/HotChocolate/Core/test/Types.Analyzers.Tests/TestHelper.cs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@
1010
using HotChocolate.Data.Filters;
1111
using HotChocolate.Execution;
1212
using HotChocolate.Execution.Configuration;
13+
using HotChocolate.Execution.Processing;
14+
using HotChocolate.Features;
15+
using HotChocolate.Language;
1316
using HotChocolate.Types.Analyzers;
1417
using HotChocolate.Types.Pagination;
1518
using Microsoft.AspNetCore.Builder;
1619
using Microsoft.CodeAnalysis;
1720
using Microsoft.CodeAnalysis.CSharp;
21+
using Microsoft.CodeAnalysis.Diagnostics;
1822
using Microsoft.Extensions.DependencyInjection;
1923

2024
namespace HotChocolate.Types;
@@ -28,7 +32,10 @@ public static Snapshot GetGeneratedSourceSnapshot([StringSyntax("csharp")] strin
2832
return GetGeneratedSourceSnapshot([sourceText]);
2933
}
3034

31-
public static Snapshot GetGeneratedSourceSnapshot(string[] sourceTexts, string? assemblyName = "Tests")
35+
public static Snapshot GetGeneratedSourceSnapshot(
36+
string[] sourceTexts,
37+
string? assemblyName = "Tests",
38+
bool enableInterceptors = false)
3239
{
3340
IEnumerable<PortableExecutableReference> references =
3441
[
@@ -39,17 +46,38 @@ public static Snapshot GetGeneratedSourceSnapshot(string[] sourceTexts, string?
3946
#elif NET10_0
4047
.. Net100.References.All,
4148
#endif
49+
// HotChocolate.Primitives
50+
MetadataReference.CreateFromFile(typeof(ITypeSystemMember).Assembly.Location),
51+
4252
// HotChocolate.Execution
4353
MetadataReference.CreateFromFile(typeof(RequestDelegate).Assembly.Location),
4454

55+
// HotChocolate.Execution.Abstractions
56+
MetadataReference.CreateFromFile(typeof(RequestContext).Assembly.Location),
57+
58+
// HotChocolate.Execution.Processing
59+
MetadataReference.CreateFromFile(typeof(HotChocolateExecutionSelectionExtensions).Assembly.Location),
60+
4561
// HotChocolate.Execution.Abstractions
4662
MetadataReference.CreateFromFile(typeof(IRequestExecutorBuilder).Assembly.Location),
4763

64+
// HotChocolate.Execution.DependencyInjection
65+
MetadataReference.CreateFromFile(typeof(RequestExecutorBuilderExtensions).Assembly.Location),
66+
4867
// HotChocolate.Types
4968
MetadataReference.CreateFromFile(typeof(ObjectTypeAttribute).Assembly.Location),
5069
MetadataReference.CreateFromFile(typeof(Connection).Assembly.Location),
5170
MetadataReference.CreateFromFile(typeof(PageConnection<>).Assembly.Location),
5271

72+
// HotChocolate.Types.Abstractions
73+
MetadataReference.CreateFromFile(typeof(ISchemaDefinition).Assembly.Location),
74+
75+
// HotChocolate.Features
76+
MetadataReference.CreateFromFile(typeof(IFeatureProvider).Assembly.Location),
77+
78+
// HotChocolate.Language
79+
MetadataReference.CreateFromFile(typeof(OperationType).Assembly.Location),
80+
5381
// HotChocolate.Abstractions
5482
MetadataReference.CreateFromFile(typeof(ParentAttribute).Assembly.Location),
5583

@@ -64,6 +92,7 @@ public static Snapshot GetGeneratedSourceSnapshot(string[] sourceTexts, string?
6492
// GreenDonut.Data
6593
MetadataReference.CreateFromFile(typeof(PagingArguments).Assembly.Location),
6694
MetadataReference.CreateFromFile(typeof(IPredicateBuilder).Assembly.Location),
95+
MetadataReference.CreateFromFile(typeof(DefaultPredicateBuilder).Assembly.Location),
6796

6897
// HotChocolate.Data
6998
MetadataReference.CreateFromFile(typeof(IFilterContext).Assembly.Location),
@@ -76,9 +105,18 @@ public static Snapshot GetGeneratedSourceSnapshot(string[] sourceTexts, string?
76105
];
77106

78107
// Create a Roslyn compilation for the syntax tree.
108+
var parseOptions = !enableInterceptors
109+
? CSharpParseOptions.Default
110+
: CSharpParseOptions.Default
111+
.WithPreprocessorSymbols("InterceptorsPreviewFeature")
112+
.WithFeatures(new Dictionary<string, string>
113+
{
114+
["InterceptorsNamespaces"] = "HotChocolate.Execution.Generated"
115+
});
116+
79117
var compilation = CSharpCompilation.Create(
80118
assemblyName: assemblyName,
81-
syntaxTrees: sourceTexts.Select(s => CSharpSyntaxTree.ParseText(s)),
119+
syntaxTrees: sourceTexts.Select(s => CSharpSyntaxTree.ParseText(s, parseOptions)),
82120
references);
83121

84122
// Create an instance of our GraphQLServerGenerator incremental source generator.
@@ -91,7 +129,25 @@ public static Snapshot GetGeneratedSourceSnapshot(string[] sourceTexts, string?
91129
driver = driver.RunGenerators(compilation);
92130

93131
// Create a snapshot.
94-
return CreateSnapshot(compilation, driver);
132+
var snapshot = CreateSnapshot(compilation, driver);
133+
134+
// Finally, compile the entire assembly (original code + generated code) to check
135+
// if the sample is valid as a whole
136+
var updatedCompilation = compilation.AddSyntaxTrees(
137+
driver.GetRunResult()
138+
.Results
139+
.SelectMany(r => r.GeneratedSources)
140+
.Select(gs => CSharpSyntaxTree.ParseText(gs.SourceText, parseOptions, path: gs.HintName))
141+
);
142+
143+
using var dllStream = new MemoryStream();
144+
var emitResult = updatedCompilation.Emit(dllStream);
145+
if (!emitResult.Success || emitResult.Diagnostics.Any())
146+
{
147+
AddDiagnosticsToSnapshot(snapshot, emitResult.Diagnostics, "Assembly Emit Diagnostics");
148+
}
149+
150+
return snapshot;
95151
}
96152

97153
private static Snapshot CreateSnapshot(CSharpCompilation compilation, GeneratorDriver driver)

0 commit comments

Comments
 (0)