Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 204 additions & 1 deletion src/HotChocolate/Core/test/Types.Analyzers.Tests/DataLoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,35 @@ public class Entity
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_BatchDataLoader_Nullable_Object_MatchesSnapshot()
{
await TestHelper.GetGeneratedSourceSnapshot(
"""
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
using GreenDonut;

namespace TestNamespace;

internal static class TestClass
{
[DataLoader]
public static Task<IReadOnlyDictionary<int, Entity?>> GetEntityByIdAsync(
IReadOnlyList<int> entityIds,
CancellationToken cancellationToken)
=> default!;
}

public class Entity
{
public int Id { get; set; }
}
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_BatchDataLoader_With_Group_MatchesSnapshot()
{
Expand Down Expand Up @@ -200,6 +229,96 @@ public class Entity
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_GroupedDataLoader_Nullable_Object_MatchesSnapshot()
{
await TestHelper.GetGeneratedSourceSnapshot(
"""
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
using GreenDonut;

namespace TestNamespace;

internal static class TestClass
{
[DataLoader]
public static Task<ILookup<int, Entity?>> GetEntitiesByIdAsync(
IReadOnlyList<int> entityIds,
CancellationToken cancellationToken)
=> default!;
}

public class Entity
{
public int Id { get; set; }
}
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_GroupedDataLoader_ValueType_MatchesSnapshot()
{
await TestHelper.GetGeneratedSourceSnapshot(
"""
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
using GreenDonut;

namespace TestNamespace;

internal static class TestClass
{
[DataLoader]
public static Task<ILookup<int, long>> GetEntitiesByIdAsync(
IReadOnlyList<int> entityIds,
CancellationToken cancellationToken)
=> default!;
}

public class Entity
{
public int Id { get; set; }
}
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_GroupedDataLoader_Nullable_ValueType_MatchesSnapshot()
{
await TestHelper.GetGeneratedSourceSnapshot(
"""
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
using GreenDonut;

namespace TestNamespace;

internal static class TestClass
{
[DataLoader]
public static Task<ILookup<int, long?>> GetEntitiesByIdAsync(
IReadOnlyList<int> entityIds,
CancellationToken cancellationToken)
=> default!;
}

public class Entity
{
public int Id { get; set; }
}
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_CacheDataLoader_MatchesSnapshot()
{
Expand Down Expand Up @@ -228,6 +347,90 @@ public class Entity
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_CacheDataLoader_Nullable_Object_MatchesSnapshot()
{
await TestHelper.GetGeneratedSourceSnapshot(
"""
using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
using GreenDonut;

namespace TestNamespace;

internal static class TestClass
{
[DataLoader]
public static Task<Entity?> GetEntityByIdAsync(
int entityId,
CancellationToken cancellationToken)
=> default!;
}

public class Entity
{
public int Id { get; set; }
}
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_CacheDataLoader_ValueType_MatchesSnapshot()
{
await TestHelper.GetGeneratedSourceSnapshot(
"""
using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
using GreenDonut;

namespace TestNamespace;

internal static class TestClass
{
[DataLoader]
public static Task<long> GetEntityByIdAsync(
int entityId,
CancellationToken cancellationToken)
=> default!;
}

public class Entity
{
public int Id { get; set; }
}
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_CacheDataLoader_Nullable_ValueType_MatchesSnapshot()
{
await TestHelper.GetGeneratedSourceSnapshot(
"""
using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
using GreenDonut;

namespace TestNamespace;

internal static class TestClass
{
[DataLoader]
public static Task<long?> GetEntityByIdAsync(
int entityId,
CancellationToken cancellationToken)
=> default!;
}

public class Entity
{
public int Id { get; set; }
}
""").MatchMarkdownAsync();
}

[Fact]
public async Task GenerateSource_GenericBatchDataLoader_MatchesSnapshot()
{
Expand Down Expand Up @@ -324,7 +527,7 @@ public static Task<IDictionary<int, Entity2>> GetEntityByIdAsync(
CancellationToken cancellationToken)
=> default!;

public static KeyValuePair<int, Entity2> CreateLookupKey(Entity1 key)
public static KeyValuePair<int, Entity2>? CreateLookupKey(Entity1 key)
=> default!;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,8 @@ public class RequestMiddlewareTests
[Fact]
public async Task GenerateSource_RequestMiddleware_MatchesSnapshot()
{
var currentUiCulture = CultureInfo.CurrentUICulture;
try
{
// Snapshot contains localized strings -> adjust culture
CultureInfo.CurrentUICulture = new CultureInfo("en-US");
await TestHelper.GetGeneratedSourceSnapshot(
await TestHelper.GetGeneratedSourceSnapshot(
[
"""
#nullable enable
using System.Threading.Tasks;
Expand Down Expand Up @@ -40,7 +36,7 @@ public class SomeRequestMiddleware(
#pragma warning restore CS9113
{
public async ValueTask InvokeAsync(
IRequestContext context,
RequestContext context,
#pragma warning disable CS9113
Service1 service1,
Service2? service2)
Expand All @@ -52,11 +48,8 @@ public async ValueTask InvokeAsync(

public class Service1;
public class Service2;
""").MatchMarkdownAsync();
}
finally
{
CultureInfo.CurrentUICulture = currentUiCulture;
}
"""
],
enableInterceptors: true).MatchMarkdownAsync();
}
}
62 changes: 59 additions & 3 deletions src/HotChocolate/Core/test/Types.Analyzers.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@
using HotChocolate.Data.Filters;
using HotChocolate.Execution;
using HotChocolate.Execution.Configuration;
using HotChocolate.Execution.Processing;
using HotChocolate.Features;
using HotChocolate.Language;
using HotChocolate.Types.Analyzers;
using HotChocolate.Types.Pagination;
using Microsoft.AspNetCore.Builder;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Extensions.DependencyInjection;

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

public static Snapshot GetGeneratedSourceSnapshot(string[] sourceTexts, string? assemblyName = "Tests")
public static Snapshot GetGeneratedSourceSnapshot(
string[] sourceTexts,
string? assemblyName = "Tests",
bool enableInterceptors = false)
{
IEnumerable<PortableExecutableReference> references =
[
Expand All @@ -39,17 +46,38 @@ public static Snapshot GetGeneratedSourceSnapshot(string[] sourceTexts, string?
#elif NET10_0
.. Net100.References.All,
#endif
// HotChocolate.Primitives
MetadataReference.CreateFromFile(typeof(ITypeSystemMember).Assembly.Location),

// HotChocolate.Execution
MetadataReference.CreateFromFile(typeof(RequestDelegate).Assembly.Location),

// HotChocolate.Execution.Abstractions
MetadataReference.CreateFromFile(typeof(RequestContext).Assembly.Location),

// HotChocolate.Execution.Processing
MetadataReference.CreateFromFile(typeof(HotChocolateExecutionSelectionExtensions).Assembly.Location),

// HotChocolate.Execution.Abstractions
MetadataReference.CreateFromFile(typeof(IRequestExecutorBuilder).Assembly.Location),

// HotChocolate.Execution.DependencyInjection
MetadataReference.CreateFromFile(typeof(RequestExecutorBuilderExtensions).Assembly.Location),

// HotChocolate.Types
MetadataReference.CreateFromFile(typeof(ObjectTypeAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Connection).Assembly.Location),
MetadataReference.CreateFromFile(typeof(PageConnection<>).Assembly.Location),

// HotChocolate.Types.Abstractions
MetadataReference.CreateFromFile(typeof(ISchemaDefinition).Assembly.Location),

// HotChocolate.Features
MetadataReference.CreateFromFile(typeof(IFeatureProvider).Assembly.Location),

// HotChocolate.Language
MetadataReference.CreateFromFile(typeof(OperationType).Assembly.Location),

// HotChocolate.Abstractions
MetadataReference.CreateFromFile(typeof(ParentAttribute).Assembly.Location),

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

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

// Create a Roslyn compilation for the syntax tree.
var parseOptions = !enableInterceptors
? CSharpParseOptions.Default
: CSharpParseOptions.Default
.WithPreprocessorSymbols("InterceptorsPreviewFeature")
.WithFeatures(new Dictionary<string, string>
{
["InterceptorsNamespaces"] = "HotChocolate.Execution.Generated"
});

var compilation = CSharpCompilation.Create(
assemblyName: assemblyName,
syntaxTrees: sourceTexts.Select(s => CSharpSyntaxTree.ParseText(s)),
syntaxTrees: sourceTexts.Select(s => CSharpSyntaxTree.ParseText(s, parseOptions)),
references);

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

// Create a snapshot.
return CreateSnapshot(compilation, driver);
var snapshot = CreateSnapshot(compilation, driver);

// Finally, compile the entire assembly (original code + generated code) to check
// if the sample is valid as a whole
var updatedCompilation = compilation.AddSyntaxTrees(
driver.GetRunResult()
.Results
.SelectMany(r => r.GeneratedSources)
.Select(gs => CSharpSyntaxTree.ParseText(gs.SourceText, parseOptions, path: gs.HintName))
);

using var dllStream = new MemoryStream();
var emitResult = updatedCompilation.Emit(dllStream);
if (!emitResult.Success || emitResult.Diagnostics.Any())
{
AddDiagnosticsToSnapshot(snapshot, emitResult.Diagnostics, "Assembly Emit Diagnostics");
}

return snapshot;
}

private static Snapshot CreateSnapshot(CSharpCompilation compilation, GeneratorDriver driver)
Expand Down
Loading
Loading