-
Notifications
You must be signed in to change notification settings - Fork 53
Expand Azure Functions smoke tests to cover source generator scenarios #604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+332
−2
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
149fe15
Initial plan
Copilot 80e7d37
Add source generator smoke test scenario definitions
Copilot 8d9a3a2
Expand Azure Functions smoke tests for source generator coverage
Copilot 59594f6
Update test/AzureFunctionsSmokeTests/AzureFunctionsSmokeTests.csproj
YunchuWang 0158415
Update test/AzureFunctionsSmokeTests/README.md
YunchuWang a348938
Update test/AzureFunctionsSmokeTests/run-smoketests.ps1
YunchuWang 8afdd80
Handle null checks in smoke validation
Copilot 410895f
Switch generator entity increment to CallEntityAsync
Copilot d8fde77
Merge branch 'main' into copilot/update-smoke-test-ci
YunchuWang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
210 changes: 210 additions & 0 deletions
210
test/AzureFunctionsSmokeTests/SourceGeneratorScenarios.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.IO; | ||
| using System.Text.Json; | ||
| using System.Text.Json.Serialization; | ||
| using Microsoft.Azure.Functions.Worker; | ||
| using Microsoft.Azure.Functions.Worker.Http; | ||
| using Microsoft.DurableTask; | ||
| using Microsoft.DurableTask.Client; | ||
| using Microsoft.DurableTask.Entities; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace AzureFunctionsSmokeTests; | ||
|
|
||
| /// <summary> | ||
| /// Input payload for the generated orchestration scenario. | ||
| /// </summary> | ||
| /// <param name="Name">The name to use when composing greetings.</param> | ||
| public record GeneratorRequest([property: JsonPropertyName("name")] string? Name); | ||
|
|
||
| /// <summary> | ||
| /// Output payload for the generated orchestration scenario. | ||
| /// </summary> | ||
| /// <param name="Greeting">The greeting text created by the activity function.</param> | ||
| /// <param name="GreetingLength">The length of the generated greeting.</param> | ||
| /// <param name="CounterTotal">The current total stored in the entity.</param> | ||
| /// <param name="ChildMessage">The response returned by the child orchestrator.</param> | ||
| /// <param name="EventMessage">The message carried by the durable event.</param> | ||
| public record GeneratorResult( | ||
| [property: JsonPropertyName("greeting")] string Greeting, | ||
| [property: JsonPropertyName("greetingLength")] int GreetingLength, | ||
| [property: JsonPropertyName("counterTotal")] int CounterTotal, | ||
| [property: JsonPropertyName("childMessage")] string ChildMessage, | ||
| [property: JsonPropertyName("eventMessage")] string EventMessage); | ||
|
|
||
| /// <summary> | ||
| /// Durable event payload used by the generated orchestration. | ||
| /// </summary> | ||
| /// <param name="Message">The event message.</param> | ||
| [DurableEvent("GeneratorSignal")] | ||
| public record GeneratorSignal([property: JsonPropertyName("message")] string Message); | ||
|
|
||
| /// <summary> | ||
| /// Entity state used by <see cref="GeneratorCounter"/>. | ||
| /// </summary> | ||
| public sealed class GeneratorCounterState | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the running total tracked by the entity. | ||
| /// </summary> | ||
| public int Count { get; set; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Entity implementation used to validate source generator entity trigger output. | ||
| /// </summary> | ||
| [DurableTask(nameof(GeneratorCounter))] | ||
| public sealed class GeneratorCounter : TaskEntity<GeneratorCounterState> | ||
| { | ||
| /// <summary> | ||
| /// Increments the counter by the specified amount. | ||
| /// </summary> | ||
| /// <param name="context">The task entity context.</param> | ||
| /// <param name="amount">The amount to add.</param> | ||
| public void Add(TaskEntityContext context, int amount) | ||
| { | ||
| this.State.Count += amount; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the current counter value. | ||
| /// </summary> | ||
| /// <returns>The current counter total.</returns> | ||
| public int GetCount() | ||
| { | ||
| return this.State.Count; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override GeneratorCounterState InitializeState(TaskEntityOperation entityOperation) | ||
| { | ||
| return new GeneratorCounterState(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Activity used to validate source generator activity trigger output. | ||
| /// </summary> | ||
| [DurableTask(nameof(CountCharactersActivity))] | ||
| public sealed class CountCharactersActivity : TaskActivity<string, int> | ||
| { | ||
| /// <inheritdoc/> | ||
| public override Task<int> RunAsync(TaskActivityContext context, string input) | ||
| { | ||
| return Task.FromResult(input?.Length ?? 0); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Child orchestrator used to validate generated sub-orchestration call methods. | ||
| /// </summary> | ||
| [DurableTask(nameof(ChildGeneratedOrchestration))] | ||
| public sealed class ChildGeneratedOrchestration : TaskOrchestrator<int, string> | ||
| { | ||
| /// <inheritdoc/> | ||
| public override Task<string> RunAsync(TaskOrchestrationContext context, int input) | ||
| { | ||
| return Task.FromResult($"Child processed {input}"); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Primary orchestration that exercises the Durable Task source generator output for Azure Functions. | ||
| /// </summary> | ||
| [DurableTask(nameof(GeneratedOrchestration))] | ||
| public sealed class GeneratedOrchestration : TaskOrchestrator<GeneratorRequest?, GeneratorResult> | ||
| { | ||
| internal const string DefaultName = "SourceGen"; | ||
|
|
||
| /// <inheritdoc/> | ||
| public override async Task<GeneratorResult> RunAsync(TaskOrchestrationContext context, GeneratorRequest? input) | ||
| { | ||
| string name = string.IsNullOrWhiteSpace(input?.Name) ? DefaultName : input!.Name!; | ||
|
|
||
| // Function-based activity trigger call using generated extension. | ||
| string greeting = await context.CallComposeGreetingAsync(name); | ||
|
|
||
| // Class-based activity call using generated extension and activity trigger generated by source generator. | ||
| int length = await context.CallCountCharactersActivityAsync(greeting); | ||
|
|
||
| // Entity trigger generated by source generator. | ||
| EntityInstanceId counterId = new EntityInstanceId(nameof(GeneratorCounter), context.InstanceId); | ||
| await context.Entities.CallEntityAsync(counterId, "Add", length); | ||
| int total = await context.Entities.CallEntityAsync<int>(counterId, "GetCount"); | ||
|
|
||
| // Durable event extensions generated by source generator. | ||
| context.SendGeneratorSignal(context.InstanceId, new GeneratorSignal($"Processed {name}")); | ||
| GeneratorSignal confirmation = await context.WaitForGeneratorSignalAsync(); | ||
|
|
||
| // Sub-orchestration call using generated extension methods. | ||
| string childMessage = await context.CallChildGeneratedOrchestrationAsync(length); | ||
|
|
||
| return new GeneratorResult(greeting, length, total, childMessage, confirmation.Message); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// HTTP trigger and auxiliary functions used to start source generator scenarios. | ||
| /// </summary> | ||
| public static class GeneratorFunctions | ||
| { | ||
| /// <summary> | ||
| /// Composes a greeting string. Generates an activity trigger via source generators. | ||
| /// </summary> | ||
| /// <param name="name">The name to greet.</param> | ||
| /// <returns>The greeting text.</returns> | ||
| [Function(nameof(ComposeGreeting))] | ||
| public static string ComposeGreeting([ActivityTrigger] string name) | ||
| { | ||
| return $"Hello, {name}!"; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Starts the generated orchestration using a generated scheduling extension method. | ||
| /// </summary> | ||
| /// <param name="req">The HTTP request.</param> | ||
| /// <param name="client">The durable client.</param> | ||
| /// <param name="executionContext">The function execution context.</param> | ||
| /// <returns>The HTTP response.</returns> | ||
| [Function("GeneratedOrchestration_HttpStart")] | ||
| public static async Task<HttpResponseData> StartGeneratedOrchestrationAsync( | ||
| [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, | ||
| [DurableClient] DurableTaskClient client, | ||
| FunctionContext executionContext) | ||
| { | ||
| ILogger logger = executionContext.GetLogger("GeneratedOrchestration_HttpStart"); | ||
|
|
||
| GeneratorRequest? request = await TryReadRequestAsync(req); | ||
| string instanceId = await client.ScheduleNewGeneratedOrchestrationInstanceAsync( | ||
| request ?? new GeneratorRequest(GeneratedOrchestration.DefaultName)); | ||
|
|
||
| logger.LogInformation("Started generated orchestration with ID = '{InstanceId}'.", instanceId); | ||
| return client.CreateCheckStatusResponse(req, instanceId); | ||
| } | ||
|
|
||
| static async Task<GeneratorRequest?> TryReadRequestAsync(HttpRequestData req) | ||
| { | ||
| if (req.Body.CanSeek) | ||
| { | ||
| req.Body.Seek(0, SeekOrigin.Begin); | ||
| } | ||
|
|
||
| using StreamReader reader = new(req.Body); | ||
| string body = await reader.ReadToEndAsync(); | ||
| if (string.IsNullOrWhiteSpace(body)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| return JsonSerializer.Deserialize<GeneratorRequest>(body); | ||
| } | ||
| catch (JsonException) | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.