Skip to content

Commit 89ad9a7

Browse files
authored
test: harden routing generator coverage (#479)
1 parent 752f6de commit 89ad9a7

3 files changed

Lines changed: 192 additions & 8 deletions

File tree

src/PatternKit.Examples/CacheStampedeProtectionDemo/ProductCatalogStampedeProtectionDemo.cs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,28 +59,43 @@ public sealed class ProductCatalogStampedeProtectionDemoRunner(ProductCatalogSta
5959
{
6060
public async ValueTask<IReadOnlyList<ProductAvailabilitySummary>> RunGeneratedAsync(ProductAvailabilityRequest request)
6161
{
62-
var first = service.GetAvailabilityAsync(request).AsTask();
63-
var second = service.GetAvailabilityAsync(request).AsTask();
64-
return await Task.WhenAll(first, second).ConfigureAwait(false);
62+
return await RunConcurrentLoadsAsync(service, request).ConfigureAwait(false);
6563
}
6664

6765
public static async ValueTask<IReadOnlyList<ProductAvailabilitySummary>> RunFluentAsync(ProductAvailabilityRequest request)
6866
{
6967
var origin = new ProductCatalogOrigin();
7068
var service = new ProductCatalogStampedeProtectionService(ProductCatalogStampedeProtectionPolicies.CreateFluent(), origin);
71-
var first = service.GetAvailabilityAsync(request).AsTask();
72-
var second = service.GetAvailabilityAsync(request).AsTask();
73-
return await Task.WhenAll(first, second).ConfigureAwait(false);
69+
return await RunConcurrentLoadsAsync(service, request).ConfigureAwait(false);
7470
}
7571

7672
public static async ValueTask<IReadOnlyList<ProductAvailabilitySummary>> RunGeneratedStaticAsync(ProductAvailabilityRequest request)
7773
{
7874
var origin = new ProductCatalogOrigin();
7975
var service = new ProductCatalogStampedeProtectionService(GeneratedProductCatalogStampedeProtectionPolicy.CreateGenerated(), origin);
80-
var first = service.GetAvailabilityAsync(request).AsTask();
81-
var second = service.GetAvailabilityAsync(request).AsTask();
76+
return await RunConcurrentLoadsAsync(service, request).ConfigureAwait(false);
77+
}
78+
79+
private static async ValueTask<IReadOnlyList<ProductAvailabilitySummary>> RunConcurrentLoadsAsync(
80+
ProductCatalogStampedeProtectionService service,
81+
ProductAvailabilityRequest request)
82+
{
83+
var start = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
84+
var first = WaitThenLoadAsync(start.Task, service, request);
85+
var second = WaitThenLoadAsync(start.Task, service, request);
86+
87+
start.SetResult();
8288
return await Task.WhenAll(first, second).ConfigureAwait(false);
8389
}
90+
91+
private static async Task<ProductAvailabilitySummary> WaitThenLoadAsync(
92+
Task start,
93+
ProductCatalogStampedeProtectionService service,
94+
ProductAvailabilityRequest request)
95+
{
96+
await start.ConfigureAwait(false);
97+
return await service.GetAvailabilityAsync(request).ConfigureAwait(false);
98+
}
8499
}
85100

86101
public static class ProductCatalogStampedeProtectionServiceCollectionExtensions

test/PatternKit.Generators.Tests/RecipientListGeneratorTests.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,56 @@ private static ValueTask PriorityAudit(Message<Order> message, MessageContext co
101101
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
102102
}
103103

104+
[Scenario("Generates recipient-list factories for global struct with sync and async recipients")]
105+
[Fact]
106+
public void GeneratesRecipientListFactoriesForGlobalStructWithSyncAndAsyncRecipients()
107+
{
108+
var source = """
109+
using System.Threading;
110+
using System.Threading.Tasks;
111+
using PatternKit.Generators.Messaging;
112+
using PatternKit.Messaging;
113+
114+
public sealed record Order(string Channel);
115+
116+
[GenerateRecipientList(typeof(Order), FactoryName = "BuildSync", AsyncFactoryName = "BuildAsync")]
117+
public partial struct OrderRecipients
118+
{
119+
private static bool IsRetail(Message<Order> message, MessageContext context)
120+
=> message.Payload.Channel == "retail";
121+
122+
private static ValueTask<bool> IsPriority(Message<Order> message, MessageContext context, CancellationToken cancellationToken)
123+
=> ValueTask.FromResult(message.Payload.Channel == "priority");
124+
125+
[RecipientListRecipient("retail\"audit", 10, nameof(IsRetail))]
126+
private static void RetailAudit(Message<Order> message, MessageContext context) { }
127+
128+
[RecipientListRecipient("priority-audit", 20, nameof(IsPriority))]
129+
private static ValueTask PriorityAudit(Message<Order> message, MessageContext context, CancellationToken cancellationToken)
130+
=> ValueTask.CompletedTask;
131+
}
132+
""";
133+
134+
var comp = CreateCompilation(source, nameof(GeneratesRecipientListFactoriesForGlobalStructWithSyncAndAsyncRecipients));
135+
var gen = new RecipientListGenerator();
136+
_ = RoslynTestHelpers.Run(comp, gen, out var run, out var updated);
137+
138+
ScenarioExpect.All(run.Results, result => ScenarioExpect.Empty(result.Diagnostics));
139+
140+
var generated = ScenarioExpect.Single(run.Results.SelectMany(result => result.GeneratedSources));
141+
var text = generated.SourceText.ToString();
142+
ScenarioExpect.Equal("OrderRecipients.RecipientList.g.cs", generated.HintName);
143+
ScenarioExpect.DoesNotContain("namespace ", text);
144+
ScenarioExpect.Contains("partial struct OrderRecipients", text);
145+
ScenarioExpect.Contains("BuildSync()", text);
146+
ScenarioExpect.Contains("BuildAsync()", text);
147+
ScenarioExpect.Contains(".When(\"retail\\\"audit\", IsRetail).Then(RetailAudit)", text);
148+
ScenarioExpect.Contains(".When(\"priority-audit\", IsPriority).Then(PriorityAudit)", text);
149+
150+
var emit = updated.Emit(Stream.Null);
151+
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
152+
}
153+
104154
[Scenario("Reports diagnostic for non-partial recipient list")]
105155
[Fact]
106156
public void ReportsDiagnosticForNonPartialRecipientList()
@@ -184,6 +234,46 @@ public static partial class OrderRecipients
184234
ScenarioExpect.Equal("PKRL003", diagnostic.Id);
185235
}
186236

237+
[Scenario("Reports diagnostics for invalid recipient-list recipient shapes")]
238+
[Fact]
239+
public void ReportsDiagnosticsForInvalidRecipientListRecipientShapes()
240+
{
241+
var source = """
242+
using PatternKit.Generators.Messaging;
243+
using PatternKit.Messaging;
244+
245+
namespace MyApp;
246+
247+
public sealed record Order(string Channel);
248+
249+
[GenerateRecipientList(typeof(Order))]
250+
public static partial class OrderRecipients
251+
{
252+
private static bool IsRetail(Message<Order> message, MessageContext context) => true;
253+
private static int InvalidPredicate(Message<Order> message, MessageContext context) => 1;
254+
255+
[RecipientListRecipient(" ", 10, nameof(IsRetail))]
256+
private static void BlankName(Message<Order> message, MessageContext context) { }
257+
258+
[RecipientListRecipient("missing-predicate", 20, "Missing")]
259+
private static void MissingPredicate(Message<Order> message, MessageContext context) { }
260+
261+
[RecipientListRecipient("invalid-predicate", 30, nameof(InvalidPredicate))]
262+
private static void InvalidPredicateRecipient(Message<Order> message, MessageContext context) { }
263+
264+
[RecipientListRecipient("instance", 40, nameof(IsRetail))]
265+
private void Instance(Message<Order> message, MessageContext context) { }
266+
}
267+
""";
268+
269+
var comp = CreateCompilation(source, nameof(ReportsDiagnosticsForInvalidRecipientListRecipientShapes));
270+
var gen = new RecipientListGenerator();
271+
_ = RoslynTestHelpers.Run(comp, gen, out var run, out _);
272+
273+
var diagnostics = run.Results.SelectMany(result => result.Diagnostics).ToArray();
274+
ScenarioExpect.Equal(4, diagnostics.Count(diagnostic => diagnostic.Id == "PKRL003"));
275+
}
276+
187277
[Scenario("Reports diagnostic for duplicate recipient name or order")]
188278
[Fact]
189279
public void ReportsDiagnosticForDuplicateRecipientNameOrOrder()

test/PatternKit.Generators.Tests/RoutingSlipGeneratorTests.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,51 @@ public static async Task<string> Run()
100100
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
101101
}
102102

103+
[Scenario("Generates routing slip factories for global struct with sync and async steps")]
104+
[Fact]
105+
public void GeneratesRoutingSlipFactoriesForGlobalStructWithSyncAndAsyncSteps()
106+
{
107+
var source = """
108+
using System.Threading;
109+
using System.Threading.Tasks;
110+
using PatternKit.Generators.Messaging;
111+
using PatternKit.Messaging;
112+
113+
public sealed record Order(string Status);
114+
115+
[GenerateRoutingSlip(typeof(Order), FactoryName = "BuildSync", AsyncFactoryName = "BuildAsync")]
116+
public partial struct OrderSlip
117+
{
118+
[RoutingSlipStep("validate", 10)]
119+
private static Message<Order> Validate(Message<Order> message, MessageContext context)
120+
=> message;
121+
122+
[RoutingSlipStep("ship\"express", 20)]
123+
private static ValueTask<Message<Order>> ShipAsync(Message<Order> message, MessageContext context, CancellationToken cancellationToken)
124+
=> ValueTask.FromResult(message);
125+
}
126+
""";
127+
128+
var comp = CreateCompilation(source, nameof(GeneratesRoutingSlipFactoriesForGlobalStructWithSyncAndAsyncSteps));
129+
var gen = new RoutingSlipGenerator();
130+
_ = RoslynTestHelpers.Run(comp, gen, out var run, out var updated);
131+
132+
ScenarioExpect.All(run.Results, result => ScenarioExpect.Empty(result.Diagnostics));
133+
134+
var generated = ScenarioExpect.Single(run.Results.SelectMany(result => result.GeneratedSources));
135+
var text = generated.SourceText.ToString();
136+
ScenarioExpect.Equal("OrderSlip.RoutingSlip.g.cs", generated.HintName);
137+
ScenarioExpect.DoesNotContain("namespace ", text);
138+
ScenarioExpect.Contains("partial struct OrderSlip", text);
139+
ScenarioExpect.Contains("BuildSync()", text);
140+
ScenarioExpect.Contains("BuildAsync()", text);
141+
ScenarioExpect.Contains(".Step(\"validate\", Validate)", text);
142+
ScenarioExpect.Contains(".Step(\"ship\\\"express\", ShipAsync)", text);
143+
144+
var emit = updated.Emit(Stream.Null);
145+
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
146+
}
147+
103148
[Scenario("ReportsDiagnosticForNonPartialSlip")]
104149
[Fact]
105150
public void ReportsDiagnosticForNonPartialSlip()
@@ -179,6 +224,40 @@ public static partial class OrderSlip
179224
ScenarioExpect.Equal("PKRS003", diagnostic.Id);
180225
}
181226

227+
[Scenario("Reports diagnostics for invalid routing slip step shapes")]
228+
[Fact]
229+
public void ReportsDiagnosticsForInvalidRoutingSlipStepShapes()
230+
{
231+
var source = """
232+
using PatternKit.Generators.Messaging;
233+
using PatternKit.Messaging;
234+
235+
namespace MyApp;
236+
237+
public sealed record Order(string Status);
238+
239+
[GenerateRoutingSlip(typeof(Order))]
240+
public partial class OrderSlip
241+
{
242+
[RoutingSlipStep(" ", 10)]
243+
private static Message<Order> BlankName(Message<Order> message, MessageContext context) => message;
244+
245+
[RoutingSlipStep("missing-context", 20)]
246+
private static Message<Order> MissingContext(Message<Order> message) => message;
247+
248+
[RoutingSlipStep("instance", 30)]
249+
private Message<Order> Instance(Message<Order> message, MessageContext context) => message;
250+
}
251+
""";
252+
253+
var comp = CreateCompilation(source, nameof(ReportsDiagnosticsForInvalidRoutingSlipStepShapes));
254+
var gen = new RoutingSlipGenerator();
255+
_ = RoslynTestHelpers.Run(comp, gen, out var run, out _);
256+
257+
var diagnostics = run.Results.SelectMany(result => result.Diagnostics).ToArray();
258+
ScenarioExpect.Equal(3, diagnostics.Count(diagnostic => diagnostic.Id == "PKRS003"));
259+
}
260+
182261
private static CSharpCompilation CreateCompilation(string source, string assemblyName)
183262
=> RoslynTestHelpers.CreateCompilation(
184263
source,

0 commit comments

Comments
 (0)