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
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,43 @@ public sealed class ProductCatalogStampedeProtectionDemoRunner(ProductCatalogSta
{
public async ValueTask<IReadOnlyList<ProductAvailabilitySummary>> RunGeneratedAsync(ProductAvailabilityRequest request)
{
var first = service.GetAvailabilityAsync(request).AsTask();
var second = service.GetAvailabilityAsync(request).AsTask();
return await Task.WhenAll(first, second).ConfigureAwait(false);
return await RunConcurrentLoadsAsync(service, request).ConfigureAwait(false);
}

public static async ValueTask<IReadOnlyList<ProductAvailabilitySummary>> RunFluentAsync(ProductAvailabilityRequest request)
{
var origin = new ProductCatalogOrigin();
var service = new ProductCatalogStampedeProtectionService(ProductCatalogStampedeProtectionPolicies.CreateFluent(), origin);
var first = service.GetAvailabilityAsync(request).AsTask();
var second = service.GetAvailabilityAsync(request).AsTask();
return await Task.WhenAll(first, second).ConfigureAwait(false);
return await RunConcurrentLoadsAsync(service, request).ConfigureAwait(false);
}

public static async ValueTask<IReadOnlyList<ProductAvailabilitySummary>> RunGeneratedStaticAsync(ProductAvailabilityRequest request)
{
var origin = new ProductCatalogOrigin();
var service = new ProductCatalogStampedeProtectionService(GeneratedProductCatalogStampedeProtectionPolicy.CreateGenerated(), origin);
var first = service.GetAvailabilityAsync(request).AsTask();
var second = service.GetAvailabilityAsync(request).AsTask();
return await RunConcurrentLoadsAsync(service, request).ConfigureAwait(false);
}

private static async ValueTask<IReadOnlyList<ProductAvailabilitySummary>> RunConcurrentLoadsAsync(
ProductCatalogStampedeProtectionService service,
ProductAvailabilityRequest request)
{
var start = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var first = WaitThenLoadAsync(start.Task, service, request);
var second = WaitThenLoadAsync(start.Task, service, request);

start.SetResult();
return await Task.WhenAll(first, second).ConfigureAwait(false);
}

private static async Task<ProductAvailabilitySummary> WaitThenLoadAsync(
Task start,
ProductCatalogStampedeProtectionService service,
ProductAvailabilityRequest request)
{
await start.ConfigureAwait(false);
return await service.GetAvailabilityAsync(request).ConfigureAwait(false);
}
}

public static class ProductCatalogStampedeProtectionServiceCollectionExtensions
Expand Down
90 changes: 90 additions & 0 deletions test/PatternKit.Generators.Tests/RecipientListGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,56 @@ private static ValueTask PriorityAudit(Message<Order> message, MessageContext co
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
}

[Scenario("Generates recipient-list factories for global struct with sync and async recipients")]
[Fact]
public void GeneratesRecipientListFactoriesForGlobalStructWithSyncAndAsyncRecipients()
{
var source = """
using System.Threading;
using System.Threading.Tasks;
using PatternKit.Generators.Messaging;
using PatternKit.Messaging;

public sealed record Order(string Channel);

[GenerateRecipientList(typeof(Order), FactoryName = "BuildSync", AsyncFactoryName = "BuildAsync")]
public partial struct OrderRecipients
{
private static bool IsRetail(Message<Order> message, MessageContext context)
=> message.Payload.Channel == "retail";

private static ValueTask<bool> IsPriority(Message<Order> message, MessageContext context, CancellationToken cancellationToken)
=> ValueTask.FromResult(message.Payload.Channel == "priority");

[RecipientListRecipient("retail\"audit", 10, nameof(IsRetail))]
private static void RetailAudit(Message<Order> message, MessageContext context) { }

[RecipientListRecipient("priority-audit", 20, nameof(IsPriority))]
private static ValueTask PriorityAudit(Message<Order> message, MessageContext context, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;
}
""";

var comp = CreateCompilation(source, nameof(GeneratesRecipientListFactoriesForGlobalStructWithSyncAndAsyncRecipients));
var gen = new RecipientListGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var run, out var updated);

ScenarioExpect.All(run.Results, result => ScenarioExpect.Empty(result.Diagnostics));

var generated = ScenarioExpect.Single(run.Results.SelectMany(result => result.GeneratedSources));
var text = generated.SourceText.ToString();
ScenarioExpect.Equal("OrderRecipients.RecipientList.g.cs", generated.HintName);
ScenarioExpect.DoesNotContain("namespace ", text);
ScenarioExpect.Contains("partial struct OrderRecipients", text);
ScenarioExpect.Contains("BuildSync()", text);
ScenarioExpect.Contains("BuildAsync()", text);
ScenarioExpect.Contains(".When(\"retail\\\"audit\", IsRetail).Then(RetailAudit)", text);
ScenarioExpect.Contains(".When(\"priority-audit\", IsPriority).Then(PriorityAudit)", text);

var emit = updated.Emit(Stream.Null);
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
}

[Scenario("Reports diagnostic for non-partial recipient list")]
[Fact]
public void ReportsDiagnosticForNonPartialRecipientList()
Expand Down Expand Up @@ -184,6 +234,46 @@ public static partial class OrderRecipients
ScenarioExpect.Equal("PKRL003", diagnostic.Id);
}

[Scenario("Reports diagnostics for invalid recipient-list recipient shapes")]
[Fact]
public void ReportsDiagnosticsForInvalidRecipientListRecipientShapes()
{
var source = """
using PatternKit.Generators.Messaging;
using PatternKit.Messaging;

namespace MyApp;

public sealed record Order(string Channel);

[GenerateRecipientList(typeof(Order))]
public static partial class OrderRecipients
{
private static bool IsRetail(Message<Order> message, MessageContext context) => true;
private static int InvalidPredicate(Message<Order> message, MessageContext context) => 1;

[RecipientListRecipient(" ", 10, nameof(IsRetail))]
private static void BlankName(Message<Order> message, MessageContext context) { }

[RecipientListRecipient("missing-predicate", 20, "Missing")]
private static void MissingPredicate(Message<Order> message, MessageContext context) { }

[RecipientListRecipient("invalid-predicate", 30, nameof(InvalidPredicate))]
private static void InvalidPredicateRecipient(Message<Order> message, MessageContext context) { }

[RecipientListRecipient("instance", 40, nameof(IsRetail))]
private void Instance(Message<Order> message, MessageContext context) { }
}
""";

var comp = CreateCompilation(source, nameof(ReportsDiagnosticsForInvalidRecipientListRecipientShapes));
var gen = new RecipientListGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var run, out _);

var diagnostics = run.Results.SelectMany(result => result.Diagnostics).ToArray();
ScenarioExpect.Equal(4, diagnostics.Count(diagnostic => diagnostic.Id == "PKRL003"));
}

[Scenario("Reports diagnostic for duplicate recipient name or order")]
[Fact]
public void ReportsDiagnosticForDuplicateRecipientNameOrOrder()
Expand Down
79 changes: 79 additions & 0 deletions test/PatternKit.Generators.Tests/RoutingSlipGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,51 @@ public static async Task<string> Run()
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
}

[Scenario("Generates routing slip factories for global struct with sync and async steps")]
[Fact]
public void GeneratesRoutingSlipFactoriesForGlobalStructWithSyncAndAsyncSteps()
{
var source = """
using System.Threading;
using System.Threading.Tasks;
using PatternKit.Generators.Messaging;
using PatternKit.Messaging;

public sealed record Order(string Status);

[GenerateRoutingSlip(typeof(Order), FactoryName = "BuildSync", AsyncFactoryName = "BuildAsync")]
public partial struct OrderSlip
{
[RoutingSlipStep("validate", 10)]
private static Message<Order> Validate(Message<Order> message, MessageContext context)
=> message;

[RoutingSlipStep("ship\"express", 20)]
private static ValueTask<Message<Order>> ShipAsync(Message<Order> message, MessageContext context, CancellationToken cancellationToken)
=> ValueTask.FromResult(message);
}
""";

var comp = CreateCompilation(source, nameof(GeneratesRoutingSlipFactoriesForGlobalStructWithSyncAndAsyncSteps));
var gen = new RoutingSlipGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var run, out var updated);

ScenarioExpect.All(run.Results, result => ScenarioExpect.Empty(result.Diagnostics));

var generated = ScenarioExpect.Single(run.Results.SelectMany(result => result.GeneratedSources));
var text = generated.SourceText.ToString();
ScenarioExpect.Equal("OrderSlip.RoutingSlip.g.cs", generated.HintName);
ScenarioExpect.DoesNotContain("namespace ", text);
ScenarioExpect.Contains("partial struct OrderSlip", text);
ScenarioExpect.Contains("BuildSync()", text);
ScenarioExpect.Contains("BuildAsync()", text);
ScenarioExpect.Contains(".Step(\"validate\", Validate)", text);
ScenarioExpect.Contains(".Step(\"ship\\\"express\", ShipAsync)", text);

var emit = updated.Emit(Stream.Null);
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
}

[Scenario("ReportsDiagnosticForNonPartialSlip")]
[Fact]
public void ReportsDiagnosticForNonPartialSlip()
Expand Down Expand Up @@ -179,6 +224,40 @@ public static partial class OrderSlip
ScenarioExpect.Equal("PKRS003", diagnostic.Id);
}

[Scenario("Reports diagnostics for invalid routing slip step shapes")]
[Fact]
public void ReportsDiagnosticsForInvalidRoutingSlipStepShapes()
{
var source = """
using PatternKit.Generators.Messaging;
using PatternKit.Messaging;

namespace MyApp;

public sealed record Order(string Status);

[GenerateRoutingSlip(typeof(Order))]
public partial class OrderSlip
{
[RoutingSlipStep(" ", 10)]
private static Message<Order> BlankName(Message<Order> message, MessageContext context) => message;

[RoutingSlipStep("missing-context", 20)]
private static Message<Order> MissingContext(Message<Order> message) => message;

[RoutingSlipStep("instance", 30)]
private Message<Order> Instance(Message<Order> message, MessageContext context) => message;
}
""";

var comp = CreateCompilation(source, nameof(ReportsDiagnosticsForInvalidRoutingSlipStepShapes));
var gen = new RoutingSlipGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var run, out _);

var diagnostics = run.Results.SelectMany(result => result.Diagnostics).ToArray();
ScenarioExpect.Equal(3, diagnostics.Count(diagnostic => diagnostic.Id == "PKRS003"));
}

private static CSharpCompilation CreateCompilation(string source, string assemblyName)
=> RoslynTestHelpers.CreateCompilation(
source,
Expand Down
Loading