From 87fbd614d65bb54d2db465ed6b292b34ced6620d Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Tue, 4 Aug 2026 15:25:28 -0700 Subject: [PATCH 01/22] Change log level for starting response streaming from info to debug (#2514) --- .../deda4452-2843-4944-b72c-3af33e780432.json | 18 ++++++++++++++++++ .../Internal/StreamingResponseBodyFeature.cs | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 .autover/changes/deda4452-2843-4944-b72c-3af33e780432.json diff --git a/.autover/changes/deda4452-2843-4944-b72c-3af33e780432.json b/.autover/changes/deda4452-2843-4944-b72c-3af33e780432.json new file mode 100644 index 000000000..ccb0d6c42 --- /dev/null +++ b/.autover/changes/deda4452-2843-4944-b72c-3af33e780432.json @@ -0,0 +1,18 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.AspNetCoreServer", + "Type": "Patch", + "ChangelogMessages": [ + "Change log level for starting response streaming from info to debug" + ] + }, + { + "Name": "Amazon.Lambda.AspNetCoreServer.Hosting", + "Type": "Patch", + "ChangelogMessages": [ + "Change log level for starting response streaming from info to debug" + ] + } + ] +} \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs index 86678b50e..de05f48a7 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs @@ -85,7 +85,7 @@ internal StreamingResponseBodyFeature( /// public async Task StartAsync(CancellationToken cancellationToken = default) { - _logger?.LogInformation("Starting response streaming"); + _logger?.LogDebug("Starting response streaming"); if (_started) return; From 3a7de4c92c12a79e3e8e3962fb2dae65a02c0739 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Thu, 6 Aug 2026 11:16:37 -0700 Subject: [PATCH 02/22] Update README.md files for Response Streaming --- .../README.md | 31 ++++++++++++++++ Libraries/src/Amazon.Lambda.Core/README.md | 35 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/README.md b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/README.md index 0098747bf..ac550e4d1 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/README.md +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/README.md @@ -87,6 +87,37 @@ builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi, options => }); ``` +### Response streaming + +You can stream the ASP.NET Core response back to the caller incrementally instead of buffering the entire payload. This raises the maximum response size beyond the standard 6 MB buffered limit and lets clients start receiving data sooner. Enable it by setting `EnableResponseStreaming` to `true`: + +```csharp +builder.Services.AddAWSLambdaHosting(LambdaEventSource.RestApi, options => +{ + options.EnableResponseStreaming = true; +}); +``` + +When enabled, the hosting layer builds the HTTP prelude from the response's status code, headers, and cookies and streams the body through the Lambda response stream. Standard results such as `Results.Json(...)` and `Results.Text(...)` continue to work unchanged. + +#### Configuring API Gateway for streaming + +A streaming function requires a different API Gateway integration than a buffered one. In your CloudFormation/`serverless.template`, the `x-amazon-apigateway-integration` must point the integration URI at the `/response-streaming-invocations` path (instead of the buffered `/invocations` path) and set `responseTransferMode` to `STREAM`: + +```json +"x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "1.0", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2021-11-15/functions/${AspNetCoreFunction.Arn}/response-streaming-invocations" + }, + "responseTransferMode": "STREAM" +} +``` + +For more details and end-to-end examples, see [Announcing response streaming for .NET on AWS Lambda](https://aws.amazon.com/blogs/developer/announcing-response-streaming-for-net-on-aws-lambda/). + ### Customizing request and response marshalling Callbacks let you inspect or modify the ASP.NET Core feature objects after the Lambda event has been marshalled into them. The second parameter is the raw Lambda request or response object — cast it to the appropriate type for your event source (`APIGatewayHttpApiV2ProxyRequest` for `HttpApi`, `APIGatewayProxyRequest` for `RestApi`, `ApplicationLoadBalancerRequest` for `ApplicationLoadBalancer`). diff --git a/Libraries/src/Amazon.Lambda.Core/README.md b/Libraries/src/Amazon.Lambda.Core/README.md index 62e1b993a..519ab7f47 100644 --- a/Libraries/src/Amazon.Lambda.Core/README.md +++ b/Libraries/src/Amazon.Lambda.Core/README.md @@ -84,6 +84,41 @@ public string ToUpper(string input, ILambdaContext context) } ``` +## Response Streaming + +This package includes types under the `Amazon.Lambda.Core.ResponseStreaming` namespace that let a handler stream its response back incrementally instead of buffering the entire payload. This raises the maximum response size beyond the standard 6 MB buffered limit and lets callers receive data as soon as it is produced. + +Use `LambdaResponseStreamFactory` to create a write-only `LambdaResponseStream` (a `System.IO.Stream`) and write to it with any standard stream consumer, such as `StreamWriter`. Once a handler creates a response stream, all output must be written to the stream and the handler's return value is ignored. + +```csharp +using Amazon.Lambda.Core.ResponseStreaming; + +public async Task StreamHandler(string input, ILambdaContext context) +{ + await using var responseStream = LambdaResponseStreamFactory.CreateStream(); + using var writer = new StreamWriter(responseStream); + + for (var i = 0; i < 5; i++) + { + await writer.WriteLineAsync($"Chunk {i}"); + await writer.FlushAsync(); + } +} +``` + +When the function is invoked through a Lambda Function URL or API Gateway, use `CreateHttpStream(HttpResponseStreamPrelude)` instead. The prelude sets the HTTP status code, headers, and cookies and is sent as the first chunk before the response body. + +```csharp +var prelude = new HttpResponseStreamPrelude +{ + StatusCode = HttpStatusCode.OK, + Headers = { ["Content-Type"] = "text/plain" } +}; +await using var responseStream = LambdaResponseStreamFactory.CreateHttpStream(prelude); +``` + +Response streaming also requires a current version of the `Amazon.Lambda.RuntimeSupport` package. For more details and end-to-end examples, see [Announcing response streaming for .NET on AWS Lambda](https://aws.amazon.com/blogs/developer/announcing-response-streaming-for-net-on-aws-lambda/). + ## ILambdaSerializer The `Amazon.Lambda.Core.ILambdaSerializer` interface allows you to implement a custom serializer to convert between arbitrary types and Lambda streams. From 2343b5d20f0863d7bf529b7539419e1ff5297600 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Fri, 14 Aug 2026 11:30:43 -0400 Subject: [PATCH 03/22] Add durable execution conformance test harness (.NET, step suite) (#2517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add durable execution conformance test harness (.NET, step suite) Wire the .NET Durable Execution SDK into the language-neutral aws-durable-execution-conformance-tests runner. The runner deploys a SAM template, invokes each mapped Lambda, and validates the durable execution result and event history against language-agnostic requirement specs. - Conformance/ under the durable integration tests: per-suite template_step.yaml mapping functions to requirement ids via TestingMetadata.TestDescription, plus one executable handler project per requirement referencing the in-repo SDK directly. - Full step suite implemented (1-1 .. 1-20); retry-across-invocation tests use a DynamoDB AttemptsTable. Verified 20/20 PASSED against real AWS. - scripts/: build_examples.sh (dotnet publish -> publish//), discover_suites.py (CI matrix), inject_execution_role.py (CI role). - conformance-tests.yml workflow: per-suite matrix, OIDC creds, pip-install runner, inject role, JUnit upload. - Exclude Conformance/** from the parent test project's compile glob so the standalone handler types don't collide. * Port remaining 8 conformance suites (.NET) — all 9 suites green Add handlers + SAM templates for wait, child, callback, invoke, parallel, map, wait_for_callback, and wait_for_condition, completing every durable execution conformance suite for .NET. Handlers ported from the internal DurableExecutionsSDKTestingFramework reference, with ProjectReferences retargeted to the in-repo SDK. Suite coverage (verified end-to-end against real AWS, us-east-1): - step 20, wait 5, child 18, callback 19, invoke 16, wait_for_condition 13, wait_for_callback 15, parallel 22, map 20. - 122 passed, 0 failed. 6 requirements declared NotImplemented (custom-serdes gaps: 3-14, 5-16, 8-15, 9-14, 9-19, 9-20) — the .NET SDK has no per-operation serdes slot; all payloads use the one registered ILambdaSerializer. Notes: - Retry-across-invocation tests (child 3-7, 3-12) use the AttemptsTable DynamoDB table, like the step suite. - invoke deploys two callee target functions (InvokeEchoTarget, InvokeFailTarget) wired via AWSSDK.Lambda; the tenancy test reuses the echo binary under a second logical id (InvokeEchoTargetTenant) produced by build_examples.sh aliasing. - child 3-11/3-17 log through the durable context logger (records carry durableExecutionArn, which the runner filters on); 3-11 disables replay-aware filtering so the ReplayChildren re-execution is also observed. * Default conformance region to us-west-2 to match CI accounts The aws-dotnet-ci test-runner accounts are us-west-2, and durable execution is available there, so align the conformance workflow's default region (still overridable via the CONFORMANCE_AWS_REGION repo variable). * Gate conformance CI on missing coverage (--fail-on failed+uncovered) The runner + its test-requirements are pinned to @main, so new upstream requirements are pulled automatically. With the default --fail-on (failed), a new requirement with no .NET handler reports UNCOVERED and the run stays green — silently missing coverage. Switch to failed+uncovered so a new requirement turns CI red, prompting a handler (or a NotImplemented declaration). Declared gaps report NOT_IMPLEMENTED and never block. Also fix stale secret/variable names in the README CI section (CONFORMANCE_* not TEST_ROLE_ARN/AWS_REGION) and document the coverage gate + what to do when it fires. * Fix conformance CI: mark build_examples.sh executable, pin action SHAs - Set the git executable bit (100755) on build_examples.sh so the 'Publish conformance handlers' step no longer fails with 'Permission denied' (exit 126) when invoked as ./scripts/build_examples.sh. - Pin all GitHub Actions in conformance-tests.yml to full commit SHAs (checkout, setup-dotnet, setup-python, setup-sam, upload-artifact), resolving the Semgrep mutable-action-tag findings. --- .github/workflows/conformance-tests.yml | 145 ++++++ ...a.DurableExecution.IntegrationTests.csproj | 4 + .../Conformance/.gitignore | 15 + .../Conformance/README.md | 157 ++++++ .../CallbackAfterWait.csproj | 19 + .../callback/CallbackAfterWait/Function.cs | 31 ++ .../CallbackBasic/CallbackBasic.csproj | 19 + .../callback/CallbackBasic/Function.cs | 30 ++ .../CallbackConcurrent.csproj | 19 + .../callback/CallbackConcurrent/Function.cs | 34 ++ .../CallbackConcurrentReversed.csproj | 19 + .../CallbackConcurrentReversed/Function.cs | 34 ++ .../CallbackCustomSerdes.csproj | 19 + .../callback/CallbackCustomSerdes/Function.cs | 72 +++ .../CallbackCustomSerdesNumber.csproj | 19 + .../CallbackCustomSerdesNumber/Function.cs | 45 ++ .../CallbackDuringWait.csproj | 19 + .../callback/CallbackDuringWait/Function.cs | 31 ++ .../CallbackFailure/CallbackFailure.csproj | 19 + .../callback/CallbackFailure/Function.cs | 30 ++ .../CallbackFailureCaught.csproj | 19 + .../CallbackFailureCaught/Function.cs | 41 ++ .../CallbackHeartbeatAlive.csproj | 19 + .../CallbackHeartbeatAlive/Function.cs | 32 ++ .../CallbackHeartbeatTimeout.csproj | 19 + .../CallbackHeartbeatTimeout/Function.cs | 32 ++ .../CallbackResolvesFirst.csproj | 19 + .../CallbackResolvesFirst/Function.cs | 31 ++ .../CallbackSequential.csproj | 19 + .../callback/CallbackSequential/Function.cs | 34 ++ .../CallbackThenStep/CallbackThenStep.csproj | 19 + .../callback/CallbackThenStep/Function.cs | 38 ++ .../CallbackTimeout/CallbackTimeout.csproj | 19 + .../callback/CallbackTimeout/Function.cs | 32 ++ .../CallbackTimeoutAfterStep.csproj | 19 + .../CallbackTimeoutAfterStep/Function.cs | 40 ++ .../CallbackTimeoutAfterWait.csproj | 19 + .../CallbackTimeoutAfterWait/Function.cs | 33 ++ .../CallbackTimeoutCaught.csproj | 19 + .../CallbackTimeoutCaught/Function.cs | 43 ++ .../CallbackWithName/CallbackWithName.csproj | 19 + .../callback/CallbackWithName/Function.cs | 30 ++ .../child/ChildBasic/ChildBasic.csproj | 19 + .../Conformance/child/ChildBasic/Function.cs | 40 ++ .../child/ChildError/ChildError.csproj | 19 + .../Conformance/child/ChildError/Function.cs | 44 ++ .../ChildErrorCaught/ChildErrorCaught.csproj | 19 + .../child/ChildErrorCaught/Function.cs | 58 +++ .../ChildErrorNoStep/ChildErrorNoStep.csproj | 19 + .../child/ChildErrorNoStep/Function.cs | 34 ++ .../ChildInterrupted/ChildInterrupted.csproj | 20 + .../child/ChildInterrupted/Function.cs | 71 +++ .../ChildLargePayload.csproj | 19 + .../child/ChildLargePayload/Function.cs | 56 +++ .../ChildMultipleSteps.csproj | 19 + .../child/ChildMultipleSteps/Function.cs | 47 ++ .../child/ChildNested/ChildNested.csproj | 19 + .../Conformance/child/ChildNested/Function.cs | 52 ++ .../ChildPrintOnly/ChildPrintOnly.csproj | 19 + .../child/ChildPrintOnly/Function.cs | 44 ++ .../child/ChildReplay/ChildReplay.csproj | 19 + .../Conformance/child/ChildReplay/Function.cs | 42 ++ .../ChildReturnsNull/ChildReturnsNull.csproj | 19 + .../child/ChildReturnsNull/Function.cs | 34 ++ .../ChildStepAndWait/ChildStepAndWait.csproj | 19 + .../child/ChildStepAndWait/Function.cs | 42 ++ .../ChildStepRetry/ChildStepRetry.csproj | 20 + .../child/ChildStepRetry/Function.cs | 78 +++ .../ChildStepRetryExhaustion.csproj | 19 + .../ChildStepRetryExhaustion/Function.cs | 48 ++ .../ChildStepWaitAfter.csproj | 19 + .../child/ChildStepWaitAfter/Function.cs | 51 ++ .../ChildWaitReplay/ChildWaitReplay.csproj | 19 + .../child/ChildWaitReplay/Function.cs | 42 ++ .../child/ChildWithName/ChildWithName.csproj | 19 + .../child/ChildWithName/Function.cs | 44 ++ .../invoke/InvokeBasic/Function.cs | 30 ++ .../invoke/InvokeBasic/InvokeBasic.csproj | 20 + .../invoke/InvokeComplexObject/Function.cs | 31 ++ .../InvokeComplexObject.csproj | 20 + .../InvokeCustomPayloadSerdes/Function.cs | 39 ++ .../InvokeCustomPayloadSerdes.csproj | 20 + .../invoke/InvokeEchoTarget/Function.cs | 30 ++ .../InvokeEchoTarget/InvokeEchoTarget.csproj | 19 + .../invoke/InvokeFailTarget/Function.cs | 30 ++ .../InvokeFailTarget/InvokeFailTarget.csproj | 19 + .../invoke/InvokeInChildContext/Function.cs | 36 ++ .../InvokeInChildContext.csproj | 20 + .../invoke/InvokeLargePayload/Function.cs | 34 ++ .../InvokeLargePayload.csproj | 20 + .../Conformance/invoke/InvokeNull/Function.cs | 31 ++ .../invoke/InvokeNull/InvokeNull.csproj | 20 + .../invoke/InvokeReplayRethrows/Function.cs | 41 ++ .../InvokeReplayRethrows.csproj | 20 + .../invoke/InvokeReplaySkips/Function.cs | 34 ++ .../InvokeReplaySkips.csproj | 20 + .../invoke/InvokeSequential/Function.cs | 33 ++ .../InvokeSequential/InvokeSequential.csproj | 20 + .../invoke/InvokeTargetFails/Function.cs | 30 ++ .../InvokeTargetFails.csproj | 20 + .../InvokeTargetFailsCaught/Function.cs | 39 ++ .../InvokeTargetFailsCaught.csproj | 20 + .../invoke/InvokeThenStep/Function.cs | 39 ++ .../InvokeThenStep/InvokeThenStep.csproj | 20 + .../invoke/InvokeWithName/Function.cs | 34 ++ .../InvokeWithName/InvokeWithName.csproj | 20 + .../invoke/InvokeWithTenantId/Function.cs | 38 ++ .../InvokeWithTenantId.csproj | 20 + .../invoke/StepThenInvoke/Function.cs | 39 ++ .../StepThenInvoke/StepThenInvoke.csproj | 20 + .../Conformance/map/MapBasic/Function.cs | 37 ++ .../Conformance/map/MapBasic/MapBasic.csproj | 19 + .../Conformance/map/MapConcurrent/Function.cs | 35 ++ .../map/MapConcurrent/MapConcurrent.csproj | 19 + .../Conformance/map/MapEmpty/Function.cs | 35 ++ .../Conformance/map/MapEmpty/MapEmpty.csproj | 19 + .../Conformance/map/MapFailFast/Function.cs | 59 +++ .../map/MapFailFast/MapFailFast.csproj | 19 + .../map/MapFailThenWait/Function.cs | 63 +++ .../MapFailThenWait/MapFailThenWait.csproj | 19 + .../Conformance/map/MapFlat/Function.cs | 41 ++ .../Conformance/map/MapFlat/MapFlat.csproj | 19 + .../Conformance/map/MapItemIndex/Function.cs | 36 ++ .../map/MapItemIndex/MapItemIndex.csproj | 19 + .../Conformance/map/MapItemNamer/Function.cs | 42 ++ .../map/MapItemNamer/MapItemNamer.csproj | 19 + .../Conformance/map/MapItemsOnly/Function.cs | 36 ++ .../map/MapItemsOnly/MapItemsOnly.csproj | 19 + .../map/MapLargeResult/Function.cs | 42 ++ .../map/MapLargeResult/MapLargeResult.csproj | 19 + .../map/MapMinSuccessful/Function.cs | 53 ++ .../MapMinSuccessful/MapMinSuccessful.csproj | 19 + .../map/MapSuspendIteration/Function.cs | 43 ++ .../MapSuspendIteration.csproj | 19 + .../Conformance/map/MapThenWait/Function.cs | 36 ++ .../map/MapThenWait/MapThenWait.csproj | 19 + .../map/MapThrowIfError/Function.cs | 44 ++ .../MapThrowIfError/MapThrowIfError.csproj | 19 + .../map/MapToleratedExceeded/Function.cs | 58 +++ .../MapToleratedExceeded.csproj | 19 + .../map/MapToleratedPct/Function.cs | 59 +++ .../MapToleratedPct/MapToleratedPct.csproj | 19 + .../map/MapToleratedWithin/Function.cs | 59 +++ .../MapToleratedWithin.csproj | 19 + .../parallel/ParallelAccessors/Function.cs | 50 ++ .../ParallelAccessors.csproj | 19 + .../parallel/ParallelAllFail/Function.cs | 59 +++ .../ParallelAllFail/ParallelAllFail.csproj | 19 + .../ParallelBadConcurrency/Function.cs | 39 ++ .../ParallelBadConcurrency.csproj | 19 + .../parallel/ParallelBasic/Function.cs | 39 ++ .../ParallelBasic/ParallelBasic.csproj | 19 + .../parallel/ParallelBranchesOnly/Function.cs | 38 ++ .../ParallelBranchesOnly.csproj | 19 + .../ParallelCombinedConfig/Function.cs | 63 +++ .../ParallelCombinedConfig.csproj | 19 + .../parallel/ParallelConcurrent/Function.cs | 40 ++ .../ParallelConcurrent.csproj | 19 + .../parallel/ParallelEmpty/Function.cs | 34 ++ .../ParallelEmpty/ParallelEmpty.csproj | 19 + .../parallel/ParallelFailFast/Function.cs | 59 +++ .../ParallelFailFast/ParallelFailFast.csproj | 19 + .../Function.cs | 58 +++ .../ParallelFailureExceedsTolerance.csproj | 19 + .../ParallelFailurePercentage/Function.cs | 59 +++ .../ParallelFailurePercentage.csproj | 19 + .../Function.cs | 60 +++ .../ParallelFailurePercentageExact.csproj | 19 + .../parallel/ParallelFlat/Function.cs | 43 ++ .../parallel/ParallelFlat/ParallelFlat.csproj | 19 + .../ParallelHeterogeneous/Function.cs | 39 ++ .../ParallelHeterogeneous.csproj | 19 + .../ParallelMinNotReached/Function.cs | 59 +++ .../ParallelMinNotReached.csproj | 19 + .../ParallelMinSuccessful/Function.cs | 58 +++ .../ParallelMinSuccessful.csproj | 19 + .../ParallelNamedBranches/Function.cs | 39 ++ .../ParallelNamedBranches.csproj | 19 + .../parallel/ParallelNested/Function.cs | 52 ++ .../ParallelNested/ParallelNested.csproj | 19 + .../parallel/ParallelRethrow/Function.cs | 45 ++ .../ParallelRethrow/ParallelRethrow.csproj | 19 + .../ParallelToleratedFailure/Function.cs | 59 +++ .../ParallelToleratedFailure.csproj | 19 + .../parallel/ParallelWithWait/Function.cs | 43 ++ .../ParallelWithWait/ParallelWithWait.csproj | 19 + .../Conformance/scripts/build_examples.sh | 108 +++++ .../Conformance/scripts/discover_suites.py | 53 ++ .../scripts/inject_execution_role.py | 128 +++++ .../step/StepAndWaitReplay/Function.cs | 37 ++ .../StepAndWaitReplay.csproj | 19 + .../step/StepAtMostOnceNoRetry/Function.cs | 47 ++ .../StepAtMostOnceNoRetry.csproj | 19 + .../step/StepAtMostOnceWithRetry/Function.cs | 82 ++++ .../StepAtMostOnceWithRetry.csproj | 20 + .../Conformance/step/StepBasic/Function.cs | 35 ++ .../step/StepBasic/StepBasic.csproj | 19 + .../step/StepComplexObject/Function.cs | 68 +++ .../StepComplexObject.csproj | 19 + .../step/StepCustomSerdes/Function.cs | 39 ++ .../StepCustomSerdes/StepCustomSerdes.csproj | 19 + .../step/StepDefaultRetry/Function.cs | 68 +++ .../StepDefaultRetry/StepDefaultRetry.csproj | 20 + .../step/StepErrorCaught/Function.cs | 53 ++ .../StepErrorCaught/StepErrorCaught.csproj | 19 + .../Conformance/step/StepLogging/Function.cs | 39 ++ .../step/StepLogging/StepLogging.csproj | 19 + .../Conformance/step/StepNested/Function.cs | 42 ++ .../step/StepNested/StepNested.csproj | 19 + .../step/StepNullResult/Function.cs | 35 ++ .../step/StepNullResult/StepNullResult.csproj | 19 + .../step/StepReplayRethrowsFailed/Function.cs | 52 ++ .../StepReplayRethrowsFailed.csproj | 19 + .../step/StepReplaySkipsSucceeded/Function.cs | 39 ++ .../StepReplaySkipsSucceeded.csproj | 19 + .../step/StepRetryCustomConfig/Function.cs | 72 +++ .../StepRetryCustomConfig.csproj | 20 + .../step/StepRetryExhaustion/Function.cs | 43 ++ .../StepRetryExhaustion.csproj | 19 + .../step/StepRetryNonRetryable/Function.cs | 50 ++ .../StepRetryNonRetryable.csproj | 19 + .../StepRetrySpecificException/Function.cs | 77 +++ .../StepRetrySpecificException.csproj | 20 + .../step/StepWithError/Function.cs | 39 ++ .../step/StepWithError/StepWithError.csproj | 19 + .../Conformance/step/StepWithName/Function.cs | 36 ++ .../step/StepWithName/StepWithName.csproj | 19 + .../step/StepWithRetry/Function.cs | 73 +++ .../step/StepWithRetry/StepWithRetry.csproj | 20 + .../Conformance/template_callback.yaml | 375 +++++++++++++++ .../Conformance/template_child.yaml | 374 +++++++++++++++ .../Conformance/template_invoke.yaml | 417 ++++++++++++++++ .../Conformance/template_map.yaml | 351 ++++++++++++++ .../Conformance/template_parallel.yaml | 414 ++++++++++++++++ .../Conformance/template_step.yaml | 452 ++++++++++++++++++ .../Conformance/template_wait.yaml | 123 +++++ .../template_wait_for_callback.yaml | 303 ++++++++++++ .../template_wait_for_condition.yaml | 267 +++++++++++ .../Conformance/wait/WaitBasic/Function.cs | 29 ++ .../wait/WaitBasic/WaitBasic.csproj | 19 + .../wait/WaitLongDuration/Function.cs | 29 ++ .../WaitLongDuration/WaitLongDuration.csproj | 19 + .../wait/WaitMinutesDuration/Function.cs | 29 ++ .../WaitMinutesDuration.csproj | 19 + .../wait/WaitMultipleSequential/Function.cs | 37 ++ .../WaitMultipleSequential.csproj | 19 + .../Conformance/wait/WaitWithName/Function.cs | 29 ++ .../wait/WaitWithName/WaitWithName.csproj | 19 + .../WaitForCallbackAfterWait/Function.cs | 44 ++ .../WaitForCallbackAfterWait.csproj | 19 + .../WaitForCallbackBasic/Function.cs | 36 ++ .../WaitForCallbackBasic.csproj | 19 + .../WaitForCallbackComplexResult/Function.cs | 42 ++ .../WaitForCallbackComplexResult.csproj | 19 + .../WaitForCallbackFailure/Function.cs | 36 ++ .../WaitForCallbackFailure.csproj | 19 + .../WaitForCallbackFailureCaught/Function.cs | 42 ++ .../WaitForCallbackFailureCaught.csproj | 19 + .../WaitForCallbackHeartbeatAlive/Function.cs | 37 ++ .../WaitForCallbackHeartbeatAlive.csproj | 19 + .../Function.cs | 37 ++ .../WaitForCallbackHeartbeatTimeout.csproj | 19 + .../WaitForCallbackInChild/Function.cs | 40 ++ .../WaitForCallbackInChild.csproj | 19 + .../WaitForCallbackNoName/Function.cs | 34 ++ .../WaitForCallbackNoName.csproj | 19 + .../WaitForCallbackNullResult/Function.cs | 36 ++ .../WaitForCallbackNullResult.csproj | 19 + .../WaitForCallbackSequential/Function.cs | 42 ++ .../WaitForCallbackSequential.csproj | 19 + .../WaitForCallbackSubmitterRetry/Function.cs | 42 ++ .../WaitForCallbackSubmitterRetry.csproj | 19 + .../WaitForCallbackTimeout/Function.cs | 37 ++ .../WaitForCallbackTimeout.csproj | 19 + .../WaitForCallbackTimeoutCaught/Function.cs | 43 ++ .../WaitForCallbackTimeoutCaught.csproj | 19 + .../WaitForCallbackWithName/Function.cs | 35 ++ .../WaitForCallbackWithName.csproj | 19 + .../WaitForConditionBasic/Function.cs | 42 ++ .../WaitForConditionBasic.csproj | 19 + .../WaitForConditionCheckThrows/Function.cs | 41 ++ .../WaitForConditionCheckThrows.csproj | 19 + .../Function.cs | 48 ++ .../WaitForConditionCheckThrowsCaught.csproj | 19 + .../WaitForConditionComplexObject/Function.cs | 56 +++ .../WaitForConditionComplexObject.csproj | 19 + .../Function.cs | 42 ++ .../WaitForConditionCustomInitialState.csproj | 19 + .../WaitForConditionCustomSerdes/Function.cs | 41 ++ .../WaitForConditionCustomSerdes.csproj | 19 + .../WaitForConditionFixedDelay/Function.cs | 44 ++ .../WaitForConditionFixedDelay.csproj | 19 + .../WaitForConditionImmediate/Function.cs | 41 ++ .../WaitForConditionImmediate.csproj | 19 + .../WaitForConditionMaxAttempts/Function.cs | 43 ++ .../WaitForConditionMaxAttempts.csproj | 19 + .../Function.cs | 54 +++ .../WaitForConditionMultipleSequential.csproj | 19 + .../WaitForConditionNullResult/Function.cs | 41 ++ .../WaitForConditionNullResult.csproj | 19 + .../WaitForConditionThenStep/Function.cs | 49 ++ .../WaitForConditionThenStep.csproj | 19 + .../WaitForConditionWithName/Function.cs | 43 ++ .../WaitForConditionWithName.csproj | 19 + 304 files changed, 12712 insertions(+) create mode 100644 .github/workflows/conformance-tests.yml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/.gitignore create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackAfterWait/CallbackAfterWait.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackAfterWait/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackBasic/CallbackBasic.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackBasic/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrent/CallbackConcurrent.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrent/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrentReversed/CallbackConcurrentReversed.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrentReversed/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdes/CallbackCustomSerdes.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdes/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdesNumber/CallbackCustomSerdesNumber.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdesNumber/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackDuringWait/CallbackDuringWait.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackDuringWait/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailure/CallbackFailure.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailure/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailureCaught/CallbackFailureCaught.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailureCaught/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatAlive/CallbackHeartbeatAlive.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatAlive/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatTimeout/CallbackHeartbeatTimeout.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatTimeout/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackResolvesFirst/CallbackResolvesFirst.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackResolvesFirst/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackSequential/CallbackSequential.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackSequential/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackThenStep/CallbackThenStep.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackThenStep/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeout/CallbackTimeout.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeout/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterStep/CallbackTimeoutAfterStep.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterStep/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterWait/CallbackTimeoutAfterWait.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterWait/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutCaught/CallbackTimeoutCaught.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutCaught/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackWithName/CallbackWithName.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackWithName/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildBasic/ChildBasic.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildBasic/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildError/ChildError.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildError/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorCaught/ChildErrorCaught.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorCaught/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorNoStep/ChildErrorNoStep.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorNoStep/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildInterrupted/ChildInterrupted.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildInterrupted/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildLargePayload/ChildLargePayload.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildLargePayload/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildMultipleSteps/ChildMultipleSteps.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildMultipleSteps/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildNested/ChildNested.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildNested/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildPrintOnly/ChildPrintOnly.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildPrintOnly/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReplay/ChildReplay.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReplay/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReturnsNull/ChildReturnsNull.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReturnsNull/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepAndWait/ChildStepAndWait.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepAndWait/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetry/ChildStepRetry.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetry/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetryExhaustion/ChildStepRetryExhaustion.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetryExhaustion/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepWaitAfter/ChildStepWaitAfter.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepWaitAfter/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWaitReplay/ChildWaitReplay.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWaitReplay/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWithName/ChildWithName.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWithName/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeBasic/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeBasic/InvokeBasic.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeComplexObject/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeComplexObject/InvokeComplexObject.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeCustomPayloadSerdes/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeCustomPayloadSerdes/InvokeCustomPayloadSerdes.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeEchoTarget/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeEchoTarget/InvokeEchoTarget.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeFailTarget/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeFailTarget/InvokeFailTarget.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeInChildContext/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeInChildContext/InvokeInChildContext.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeLargePayload/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeLargePayload/InvokeLargePayload.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeNull/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeNull/InvokeNull.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplayRethrows/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplayRethrows/InvokeReplayRethrows.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplaySkips/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplaySkips/InvokeReplaySkips.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeSequential/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeSequential/InvokeSequential.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFails/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFails/InvokeTargetFails.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFailsCaught/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFailsCaught/InvokeTargetFailsCaught.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeThenStep/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeThenStep/InvokeThenStep.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithName/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithName/InvokeWithName.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithTenantId/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithTenantId/InvokeWithTenantId.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/StepThenInvoke/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/StepThenInvoke/StepThenInvoke.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapBasic/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapBasic/MapBasic.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapConcurrent/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapConcurrent/MapConcurrent.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapEmpty/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapEmpty/MapEmpty.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailFast/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailFast/MapFailFast.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailThenWait/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailThenWait/MapFailThenWait.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFlat/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFlat/MapFlat.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemIndex/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemIndex/MapItemIndex.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemNamer/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemNamer/MapItemNamer.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemsOnly/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemsOnly/MapItemsOnly.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapLargeResult/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapLargeResult/MapLargeResult.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapMinSuccessful/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapMinSuccessful/MapMinSuccessful.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapSuspendIteration/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapSuspendIteration/MapSuspendIteration.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThenWait/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThenWait/MapThenWait.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThrowIfError/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThrowIfError/MapThrowIfError.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedExceeded/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedExceeded/MapToleratedExceeded.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedPct/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedPct/MapToleratedPct.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedWithin/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedWithin/MapToleratedWithin.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAccessors/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAccessors/ParallelAccessors.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAllFail/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAllFail/ParallelAllFail.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBadConcurrency/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBadConcurrency/ParallelBadConcurrency.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBasic/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBasic/ParallelBasic.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBranchesOnly/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBranchesOnly/ParallelBranchesOnly.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelCombinedConfig/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelCombinedConfig/ParallelCombinedConfig.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelConcurrent/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelConcurrent/ParallelConcurrent.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelEmpty/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelEmpty/ParallelEmpty.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailFast/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailFast/ParallelFailFast.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailureExceedsTolerance/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailureExceedsTolerance/ParallelFailureExceedsTolerance.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentage/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentage/ParallelFailurePercentage.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentageExact/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentageExact/ParallelFailurePercentageExact.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFlat/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFlat/ParallelFlat.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelHeterogeneous/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelHeterogeneous/ParallelHeterogeneous.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinNotReached/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinNotReached/ParallelMinNotReached.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinSuccessful/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinSuccessful/ParallelMinSuccessful.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNamedBranches/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNamedBranches/ParallelNamedBranches.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNested/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNested/ParallelNested.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelRethrow/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelRethrow/ParallelRethrow.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelToleratedFailure/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelToleratedFailure/ParallelToleratedFailure.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelWithWait/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelWithWait/ParallelWithWait.csproj create mode 100755 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/build_examples.sh create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/discover_suites.py create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/inject_execution_role.py create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAndWaitReplay/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAndWaitReplay/StepAndWaitReplay.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceNoRetry/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceNoRetry/StepAtMostOnceNoRetry.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceWithRetry/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceWithRetry/StepAtMostOnceWithRetry.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepBasic/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepBasic/StepBasic.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepComplexObject/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepComplexObject/StepComplexObject.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepCustomSerdes/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepCustomSerdes/StepCustomSerdes.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepDefaultRetry/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepDefaultRetry/StepDefaultRetry.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepErrorCaught/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepErrorCaught/StepErrorCaught.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepLogging/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepLogging/StepLogging.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNested/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNested/StepNested.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNullResult/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNullResult/StepNullResult.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplayRethrowsFailed/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplayRethrowsFailed/StepReplayRethrowsFailed.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplaySkipsSucceeded/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplaySkipsSucceeded/StepReplaySkipsSucceeded.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryCustomConfig/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryCustomConfig/StepRetryCustomConfig.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryExhaustion/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryExhaustion/StepRetryExhaustion.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryNonRetryable/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryNonRetryable/StepRetryNonRetryable.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetrySpecificException/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetrySpecificException/StepRetrySpecificException.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithError/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithError/StepWithError.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithName/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithName/StepWithName.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithRetry/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithRetry/StepWithRetry.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_callback.yaml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_child.yaml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_invoke.yaml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_map.yaml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_parallel.yaml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_step.yaml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait.yaml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait_for_callback.yaml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait_for_condition.yaml create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitBasic/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitBasic/WaitBasic.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitLongDuration/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitLongDuration/WaitLongDuration.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMinutesDuration/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMinutesDuration/WaitMinutesDuration.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMultipleSequential/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMultipleSequential/WaitMultipleSequential.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitWithName/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitWithName/WaitWithName.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackAfterWait/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackAfterWait/WaitForCallbackAfterWait.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackBasic/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackBasic/WaitForCallbackBasic.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackComplexResult/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackComplexResult/WaitForCallbackComplexResult.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailure/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailure/WaitForCallbackFailure.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailureCaught/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailureCaught/WaitForCallbackFailureCaught.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatAlive/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatAlive/WaitForCallbackHeartbeatAlive.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatTimeout/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatTimeout/WaitForCallbackHeartbeatTimeout.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackInChild/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackInChild/WaitForCallbackInChild.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNoName/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNoName/WaitForCallbackNoName.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNullResult/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNullResult/WaitForCallbackNullResult.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSequential/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSequential/WaitForCallbackSequential.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSubmitterRetry/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSubmitterRetry/WaitForCallbackSubmitterRetry.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeout/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeout/WaitForCallbackTimeout.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeoutCaught/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeoutCaught/WaitForCallbackTimeoutCaught.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackWithName/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackWithName/WaitForCallbackWithName.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionBasic/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionBasic/WaitForConditionBasic.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrows/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrows/WaitForConditionCheckThrows.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrowsCaught/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrowsCaught/WaitForConditionCheckThrowsCaught.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionComplexObject/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionComplexObject/WaitForConditionComplexObject.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomInitialState/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomInitialState/WaitForConditionCustomInitialState.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomSerdes/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomSerdes/WaitForConditionCustomSerdes.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionFixedDelay/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionFixedDelay/WaitForConditionFixedDelay.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionImmediate/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionImmediate/WaitForConditionImmediate.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMaxAttempts/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMaxAttempts/WaitForConditionMaxAttempts.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMultipleSequential/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMultipleSequential/WaitForConditionMultipleSequential.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionNullResult/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionNullResult/WaitForConditionNullResult.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionThenStep/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionThenStep/WaitForConditionThenStep.csproj create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionWithName/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionWithName/WaitForConditionWithName.csproj diff --git a/.github/workflows/conformance-tests.yml b/.github/workflows/conformance-tests.yml new file mode 100644 index 000000000..ef1d07649 --- /dev/null +++ b/.github/workflows/conformance-tests.yml @@ -0,0 +1,145 @@ +name: Durable Execution Conformance Tests + +# Full-integration conformance run for the .NET Durable Execution SDK: publishes +# the .NET handlers, installs the language-agnostic runner from the +# aws-durable-execution-conformance-tests repo, then deploys + invokes + +# validates one SAM stack per suite. Suites are discovered from the +# template_.yaml files under the Conformance directory (see +# scripts/discover_suites.py) and each runs as its own parallel matrix job, so +# adding a suite only requires shipping its template + handlers. + +on: + pull_request: + branches: [dev, master] + paths: + - "Libraries/src/Amazon.Lambda.DurableExecution/**" + - "Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/**" + - ".github/workflows/conformance-tests.yml" + workflow_dispatch: + +concurrency: + group: ${{ github.head_ref || github.ref_name || github.run_id }}-conformance + cancel-in-progress: true + +permissions: + contents: read + id-token: write # Required for AWS OIDC credentials + +env: + CONFORMANCE_DIR: Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance + RUNNER_PIP_SPEC: "git+https://github.com/aws/aws-durable-execution-conformance-tests.git@main#subdirectory=packages/aws-durable-execution-conformance-tests" + +jobs: + discover_suites: + name: discover conformance suites + runs-on: ubuntu-latest + outputs: + suites: ${{ steps.discover.outputs.suites }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Discover suites from templates + id: discover + working-directory: ${{ env.CONFORMANCE_DIR }} + run: echo "suites=$(python3 scripts/discover_suites.py)" >> "$GITHUB_OUTPUT" + + conformance: + name: conformance (${{ matrix.suite }}) + needs: discover_suites + runs-on: ubuntu-latest + # Global lock per suite stack: runs from different branches/PRs share the + # persistent conformance-dotnet- stacks, so deploys to the same stack + # must never overlap. Queued (not cancelled) so every run still executes. + concurrency: + group: conformance-stack-${{ matrix.suite }} + cancel-in-progress: false + strategy: + fail-fast: false + matrix: + suite: ${{ fromJSON(needs.discover_suites.outputs.suites) }} + defaults: + run: + working-directory: Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup .NET + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + dotnet-version: "8.0.x" + + - name: Setup Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.14" + + - name: Setup SAM CLI + uses: aws-actions/setup-sam@f84ec7d548307efafe33230528756de3c5841a17 # v2 + with: + use-installer: true + + - name: Publish conformance handlers + run: ./scripts/build_examples.sh ${{ matrix.suite }} + + - name: Install conformance runner + run: pip install "${RUNNER_PIP_SPEC}" + + - name: Get AWS Credentials + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 + with: + # SAM-capable deploy role (CloudFormation / S3 / IAM / Lambda / DynamoDB). + role-to-assume: ${{ secrets.CONFORMANCE_DEPLOY_ROLE_ARN }} + role-session-name: githubConformanceTest + aws-region: ${{ vars.CONFORMANCE_AWS_REGION || 'us-west-2' }} + + - name: Inject Lambda execution role into template + env: + ROLE_ARN: ${{ secrets.CONFORMANCE_LAMBDA_EXECUTION_ROLE_ARN }} + run: | + if [ -z "$ROLE_ARN" ]; then + echo "CONFORMANCE_LAMBDA_EXECUTION_ROLE_ARN not set; template will create its own role." + exit 0 + fi + # Point every function at the pre-existing execution role and drop the + # self-created DurableFunctionRole. Mutates only the CI checkout; the + # checked-in template stays self-contained for local runs. + python3 scripts/inject_execution_role.py \ + --template template_${{ matrix.suite }}.yaml \ + --role-arn "$ROLE_ARN" + + - name: Compute stack-safe suite slug + run: | + # CloudFormation stack names allow only [a-zA-Z][-a-zA-Z0-9]*; + # suite names like wait_for_condition contain underscores. + echo "SUITE_SLUG=$(echo '${{ matrix.suite }}' | tr '_' '-')" >> "$GITHUB_ENV" + + - name: Run conformance suite + # --fail-on failed+uncovered: the runner (and its test-requirements) are + # pinned to @main, so when upstream adds a new requirement to a suite it + # is pulled automatically. Without this, a new requirement with no .NET + # handler reports UNCOVERED and the run stays green — silently missing + # coverage. failed+uncovered turns that red so we notice and either add a + # handler or declare it under TestingMetadata.NotImplemented. Declared + # gaps report NOT_IMPLEMENTED, which never blocks. + run: | + python -m aws_durable_execution_conformance_tests.app \ + --template template_${{ matrix.suite }}.yaml \ + --language dotnet \ + --suite ${{ matrix.suite }} \ + --name conformance-dotnet-${SUITE_SLUG} \ + --region ${{ vars.CONFORMANCE_AWS_REGION || 'us-west-2' }} \ + --history-dir history-${{ matrix.suite }} \ + --report junit \ + --report-file report-${{ matrix.suite }} \ + --fail-on failed+uncovered \ + --no-cleanup + + - name: Upload conformance report + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: conformance-report-${{ matrix.suite }} + path: | + ${{ env.CONFORMANCE_DIR }}/report-${{ matrix.suite }}.xml + ${{ env.CONFORMANCE_DIR }}/history-${{ matrix.suite }}/ + if-no-files-found: warn diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Amazon.Lambda.DurableExecution.IntegrationTests.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Amazon.Lambda.DurableExecution.IntegrationTests.csproj index f76ef51be..698d8d7d0 100644 --- a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Amazon.Lambda.DurableExecution.IntegrationTests.csproj +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Amazon.Lambda.DurableExecution.IntegrationTests.csproj @@ -18,6 +18,10 @@ + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/.gitignore b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/.gitignore new file mode 100644 index 000000000..d38315295 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/.gitignore @@ -0,0 +1,15 @@ +# Published handler artifacts produced by scripts/build_examples.sh +publish/ + +# Conformance runner output +history-*/ +report-*.xml +report-*.json + +# SAM build/deploy scratch +.aws-sam/ +samconfig.toml + +# .NET build output +**/bin/ +**/obj/ diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md new file mode 100644 index 000000000..d4ce87efd --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md @@ -0,0 +1,157 @@ +# Durable Execution Conformance Tests (.NET) + +This directory wires the .NET Durable Execution SDK into the language-neutral +[`aws-durable-execution-conformance-tests`](https://github.com/aws/aws-durable-execution-conformance-tests) +runner. The runner is a Python tool that deploys a SAM template, invokes each +mapped Lambda, and validates the durable execution **result** and **event +history** against language-agnostic requirement specs. + +## How it works + +- Each requirement (e.g. `1-1`) has a YAML spec in the runner's + `test-requirements//` directory describing the expected result and + execution history. +- For every requirement we implement, there is a small handler project under + `//` (executable model: `Main` + `LambdaBootstrap`, + `AssemblyName=bootstrap`). Each project references the in-repo SDK directly. +- `template_.yaml` maps each function to its requirement id(s) via + `TestingMetadata.TestDescription: ["1-1"]` and deploys it on the `dotnet8` + managed runtime. +- The runner reads `TestingMetadata`, deploys the template, invokes each + function (sync or async depending on the requirement), then asserts. + +Handlers are published ahead of time into `publish//`; the SAM +template's `BuildMethod: makefile` copies the pre-built `bootstrap` into the +deploy artifact. + +## Layout + +``` +Conformance/ +├── README.md +├── template_step.yaml # one template per suite; functions -> requirement ids +├── scripts/ +│ ├── build_examples.sh # dotnet publish each handler -> publish// +│ ├── discover_suites.py # emits the CI matrix (suites with template + handlers) +│ └── inject_execution_role.py# CI: point functions at a pre-existing role +└── step/ # one dir per suite; one subdir per handler + ├── StepBasic/ # 1-1 + ├── StepWithName/ # 1-2 + └── ... # 1-3 .. 1-20 +``` + +## Coverage + +All nine suites are implemented (one handler project per requirement id): + +| Suite | Ids | Handlers | +|-------|-----|----------| +| `step` | 1-1 .. 1-20 | 20 | +| `wait` | 2-1 .. 2-5 | 5 | +| `child` | 3-1 .. 3-13, 3-15 .. 3-18 | 17 | +| `callback` | 4-1 .. 4-19 | 19 | +| `invoke` | 5-1 .. 5-15 | 15 (+2 target functions, +1 tenancy alias) | +| `wait_for_condition` | 6-1 .. 6-13 | 13 | +| `wait_for_callback` | 7-1 .. 7-15 | 15 | +| `parallel` | 8-1 .. 8-22 (8-15 n/a) | 21 | +| `map` | 9-1 .. 9-18 (9-14 n/a) | 17 | + +A few requirement ids have no .NET handler because the SDK intentionally lacks +the feature they exercise (e.g. per-item / whole-result serdes slots in `map`); +those are documented in the relevant `template_.yaml` and reported as +`NOT_IMPLEMENTED` (non-blocking) rather than silently omitted. + +### Handlers that need extra resources + +- **Retry-across-invocation tests** (`step` 1-11/1-13/1-14/1-15/1-18, `child` + 3-7/3-12) count attempts across separate invocations, which the replay model + cannot hold in memory, so they use the `AttemptsTable` DynamoDB table declared + in the template (`AWSSDK.DynamoDBv2`). +- **`invoke` targets** — the suite deploys two callee functions + (`InvokeEchoTarget`, `InvokeFailTarget`) that the workflow handlers invoke via + `AWSSDK.Lambda`; ARNs are wired through env vars with `Fn::GetAtt`. The + tenancy test (5-8) reuses the echo target's binary under a second logical id + (`InvokeEchoTargetTenant`, `PER_TENANT` isolation) — `build_examples.sh` + produces that publish dir by aliasing (there is no separate source project). + +## Prerequisites + +- .NET 8 SDK +- Python 3.14+ and the conformance runner: + ```bash + pip install "git+https://github.com/aws/aws-durable-execution-conformance-tests.git@main#subdirectory=packages/aws-durable-execution-conformance-tests" + ``` +- [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) +- AWS credentials for an account allowed to deploy + invoke (CloudFormation, IAM, + Lambda, DynamoDB). Prefix commands with `unset AWS_PROFILE` to use `[default]`. + +## Running locally + +From this directory (swap `step` for any suite name): + +```bash +# 1. Publish the suite's handlers into publish// +# (omit the arg to publish every suite) +./scripts/build_examples.sh step + +# 2. Deploy + invoke + validate the suite +unset AWS_PROFILE +python -m aws_durable_execution_conformance_tests.app \ + --template template_step.yaml \ + --language dotnet \ + --suite step \ + --name conformance-dotnet-step \ + --region us-east-1 \ + --history-dir history-step \ + --report console +``` + +> On Windows, set `PYTHONUTF8=1` — the runner prints `✅`/`❌`, which crashes the +> summary printer under the default cp1252 console encoding. + +The checked-in template is self-contained (it creates its own +`DurableFunctionRole`). CI instead injects a pre-existing execution role with +`scripts/inject_execution_role.py`. + +## CI + +`.github/workflows/conformance-tests.yml` runs one matrix job per discovered +suite: publish handlers → install the runner → assume the deploy role via OIDC → +inject the execution role → run the suite → upload the JUnit report. It requires +the repository secret `CONFORMANCE_DEPLOY_ROLE_ARN` (a SAM-capable deploy role; +provisioned by the `aws-dotnet-ci` CDK), and optionally +`CONFORMANCE_LAMBDA_EXECUTION_ROLE_ARN` (a pre-created Lambda execution role) and +the `CONFORMANCE_AWS_REGION` variable (defaults to `us-west-2`). + +### Coverage gate (keeping up with upstream) + +The runner and its `test-requirements/` are pinned to +[`aws-durable-execution-conformance-tests@main`](https://github.com/aws/aws-durable-execution-conformance-tests), +so **new upstream requirements are pulled automatically** on every run. CI runs +with `--fail-on failed+uncovered`, so a newly-added requirement that has no .NET +handler reports `UNCOVERED` and **turns the run red** — that's the signal to add +a handler (or declare it `NotImplemented`). Without that flag the default only +blocks on `FAILED`, and missing coverage would pass silently. Requirements +declared under `TestingMetadata.NotImplemented` report `NOT_IMPLEMENTED`, which +never blocks — so intentional SDK gaps stay green while genuinely-new +requirements fail loudly. + +## Adding a suite + +1. Add `//` handler projects (one per requirement). +2. Add `template_.yaml` mapping each function to its requirement id(s). +3. Declare any intentional gaps under a function's + `TestingMetadata.NotImplemented` (reported `NOT_IMPLEMENTED`, non-blocking). + +`discover_suites.py` picks it up automatically, so it becomes a new CI matrix job. + +## When CI goes red on a new upstream requirement + +`--fail-on failed+uncovered` means an `UNCOVERED` requirement fails the run. +When that happens, for the reported id (e.g. a new `1-21`): + +1. Read its spec in the runner's `test-requirements//.yaml`. +2. Either **add a handler** — a new `//` project + a resource in + `template_.yaml` with `TestDescription: [""]` — or, if the .NET SDK + genuinely can't satisfy it, **declare it** under any function's + `TestingMetadata.NotImplemented` with a reason. diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackAfterWait/CallbackAfterWait.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackAfterWait/CallbackAfterWait.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackAfterWait/CallbackAfterWait.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackAfterWait/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackAfterWait/Function.cs new file mode 100644 index 000000000..fbc604130 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackAfterWait/Function.cs @@ -0,0 +1,31 @@ +// 4-9: CreateCallback then wait then await callback +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackAfterWait; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: input); + await context.WaitAsync(TimeSpan.FromSeconds(5)); + var result = await callback.GetResultAsync(); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackBasic/CallbackBasic.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackBasic/CallbackBasic.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackBasic/CallbackBasic.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackBasic/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackBasic/Function.cs new file mode 100644 index 000000000..5791f3a02 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackBasic/Function.cs @@ -0,0 +1,30 @@ +// 4-1: Create callback basic +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackBasic; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: input); + var result = await callback.GetResultAsync(); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrent/CallbackConcurrent.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrent/CallbackConcurrent.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrent/CallbackConcurrent.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrent/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrent/Function.cs new file mode 100644 index 000000000..9b875420a --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrent/Function.cs @@ -0,0 +1,34 @@ +// 4-18: Concurrent callbacks (create A, create B, await A, await B) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackConcurrent; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string[] input, IDurableContext context) + { + var callbackA = await context.CreateCallbackAsync(name: input[0]); + var callbackB = await context.CreateCallbackAsync(name: input[1]); + + var resultA = await callbackA.GetResultAsync(); + var resultB = await callbackB.GetResultAsync(); + + return $"{resultA}:{resultB}"; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrentReversed/CallbackConcurrentReversed.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrentReversed/CallbackConcurrentReversed.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrentReversed/CallbackConcurrentReversed.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrentReversed/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrentReversed/Function.cs new file mode 100644 index 000000000..df864ca9c --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackConcurrentReversed/Function.cs @@ -0,0 +1,34 @@ +// 4-19: Concurrent callbacks reversed (create A, create B, await B, await A) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackConcurrentReversed; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string[] input, IDurableContext context) + { + var callbackA = await context.CreateCallbackAsync(name: input[0]); + var callbackB = await context.CreateCallbackAsync(name: input[1]); + + var resultB = await callbackB.GetResultAsync(); + var resultA = await callbackA.GetResultAsync(); + + return $"{resultA}:{resultB}"; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdes/CallbackCustomSerdes.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdes/CallbackCustomSerdes.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdes/CallbackCustomSerdes.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdes/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdes/Function.cs new file mode 100644 index 000000000..3d9ad2e4a --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdes/Function.cs @@ -0,0 +1,72 @@ +// 4-15: Custom serdes (JSON object with timestamp conversion) +using System.Text.Json.Serialization; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackCustomSerdes; + +public class CallbackPayload +{ + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + [JsonPropertyName("timestamp")] + public string Timestamp { get; set; } = string.Empty; +} + +public class ReceivedData +{ + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + [JsonPropertyName("timestamp")] + public long Timestamp { get; set; } +} + +public class WorkflowResult +{ + [JsonPropertyName("received")] + public ReceivedData Received { get; set; } = new(); +} + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: input); + var result = await callback.GetResultAsync(); + + var epoch = DateTimeOffset.Parse(result.Timestamp).ToUnixTimeSeconds(); + + return new WorkflowResult + { + Received = new ReceivedData + { + Id = result.Id, + Message = result.Message, + Timestamp = epoch + } + }; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdesNumber/CallbackCustomSerdesNumber.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdesNumber/CallbackCustomSerdesNumber.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdesNumber/CallbackCustomSerdesNumber.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdesNumber/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdesNumber/Function.cs new file mode 100644 index 000000000..d1e68ed68 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackCustomSerdesNumber/Function.cs @@ -0,0 +1,45 @@ +// 4-16: Custom serdes (number to structured result) +using System.Text.Json.Serialization; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackCustomSerdesNumber; + +public class WorkflowResult +{ + [JsonPropertyName("count")] + public int Count { get; set; } + + [JsonPropertyName("doubled")] + public int Doubled { get; set; } +} + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: input); + var result = await callback.GetResultAsync(); + + return new WorkflowResult + { + Count = result, + Doubled = result * 2 + }; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackDuringWait/CallbackDuringWait.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackDuringWait/CallbackDuringWait.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackDuringWait/CallbackDuringWait.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackDuringWait/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackDuringWait/Function.cs new file mode 100644 index 000000000..7e6f8469f --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackDuringWait/Function.cs @@ -0,0 +1,31 @@ +// 4-10: CreateCallback then 5s wait then await +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackDuringWait; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: input); + await context.WaitAsync(TimeSpan.FromSeconds(5)); + var result = await callback.GetResultAsync(); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailure/CallbackFailure.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailure/CallbackFailure.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailure/CallbackFailure.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailure/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailure/Function.cs new file mode 100644 index 000000000..5c94a1eef --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailure/Function.cs @@ -0,0 +1,30 @@ +// 4-6: Callback failure +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackFailure; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: input); + var result = await callback.GetResultAsync(); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailureCaught/CallbackFailureCaught.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailureCaught/CallbackFailureCaught.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailureCaught/CallbackFailureCaught.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailureCaught/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailureCaught/Function.cs new file mode 100644 index 000000000..1e1beae82 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackFailureCaught/Function.cs @@ -0,0 +1,41 @@ +// 4-13: Catch callback failure and continue +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackFailureCaught; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: input); + + string result; + try + { + result = await callback.GetResultAsync(); + } + catch (CallbackFailedException) + { + result = "callback_failed_caught"; + } + + await context.WaitAsync(TimeSpan.FromSeconds(2)); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatAlive/CallbackHeartbeatAlive.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatAlive/CallbackHeartbeatAlive.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatAlive/CallbackHeartbeatAlive.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatAlive/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatAlive/Function.cs new file mode 100644 index 000000000..55e86b2d6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatAlive/Function.cs @@ -0,0 +1,32 @@ +// 4-5: Heartbeat keeps callback alive +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackHeartbeatAlive; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync( + name: input, + config: new CallbackConfig { HeartbeatTimeout = TimeSpan.FromSeconds(10) }); + var result = await callback.GetResultAsync(); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatTimeout/CallbackHeartbeatTimeout.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatTimeout/CallbackHeartbeatTimeout.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatTimeout/CallbackHeartbeatTimeout.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatTimeout/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatTimeout/Function.cs new file mode 100644 index 000000000..7dda2e150 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackHeartbeatTimeout/Function.cs @@ -0,0 +1,32 @@ +// 4-4: Create callback heartbeat timeout +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackHeartbeatTimeout; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync( + name: input, + config: new CallbackConfig { HeartbeatTimeout = TimeSpan.FromSeconds(5) }); + var result = await callback.GetResultAsync(); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackResolvesFirst/CallbackResolvesFirst.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackResolvesFirst/CallbackResolvesFirst.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackResolvesFirst/CallbackResolvesFirst.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackResolvesFirst/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackResolvesFirst/Function.cs new file mode 100644 index 000000000..6eb3bf693 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackResolvesFirst/Function.cs @@ -0,0 +1,31 @@ +// 4-12: Callback resolves first, then wait, then return +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackResolvesFirst; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: input); + var result = await callback.GetResultAsync(); + await context.WaitAsync(TimeSpan.FromSeconds(2)); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackSequential/CallbackSequential.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackSequential/CallbackSequential.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackSequential/CallbackSequential.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackSequential/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackSequential/Function.cs new file mode 100644 index 000000000..71ac300ca --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackSequential/Function.cs @@ -0,0 +1,34 @@ +// 4-17: Sequential callbacks (A then B) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackSequential; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string[] input, IDurableContext context) + { + var callbackA = await context.CreateCallbackAsync(name: input[0]); + var resultA = await callbackA.GetResultAsync(); + + var callbackB = await context.CreateCallbackAsync(name: input[1]); + var resultB = await callbackB.GetResultAsync(); + + return $"{resultA}:{resultB}"; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackThenStep/CallbackThenStep.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackThenStep/CallbackThenStep.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackThenStep/CallbackThenStep.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackThenStep/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackThenStep/Function.cs new file mode 100644 index 000000000..019a416e4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackThenStep/Function.cs @@ -0,0 +1,38 @@ +// 4-7: CreateCallback then step then await +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackThenStep; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: input); + + var stepResult = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return "step_done"; + }); + + var callbackResult = await callback.GetResultAsync(); + return callbackResult; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeout/CallbackTimeout.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeout/CallbackTimeout.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeout/CallbackTimeout.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeout/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeout/Function.cs new file mode 100644 index 000000000..c33ad8919 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeout/Function.cs @@ -0,0 +1,32 @@ +// 4-3: Create callback timeout +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackTimeout; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync( + name: input, + config: new CallbackConfig { Timeout = TimeSpan.FromSeconds(5) }); + var result = await callback.GetResultAsync(); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterStep/CallbackTimeoutAfterStep.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterStep/CallbackTimeoutAfterStep.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterStep/CallbackTimeoutAfterStep.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterStep/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterStep/Function.cs new file mode 100644 index 000000000..36815665e --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterStep/Function.cs @@ -0,0 +1,40 @@ +// 4-8: CreateCallback (5s timeout) then step then await - times out +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackTimeoutAfterStep; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync( + name: input, + config: new CallbackConfig { Timeout = TimeSpan.FromSeconds(5) }); + + var stepResult = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return "step_done"; + }); + + var callbackResult = await callback.GetResultAsync(); + return callbackResult; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterWait/CallbackTimeoutAfterWait.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterWait/CallbackTimeoutAfterWait.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterWait/CallbackTimeoutAfterWait.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterWait/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterWait/Function.cs new file mode 100644 index 000000000..3af2249f9 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutAfterWait/Function.cs @@ -0,0 +1,33 @@ +// 4-11: CreateCallback (3s timeout) then 6s wait then await +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackTimeoutAfterWait; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync( + name: input, + config: new CallbackConfig { Timeout = TimeSpan.FromSeconds(3) }); + await context.WaitAsync(TimeSpan.FromSeconds(6)); + var result = await callback.GetResultAsync(); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutCaught/CallbackTimeoutCaught.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutCaught/CallbackTimeoutCaught.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutCaught/CallbackTimeoutCaught.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutCaught/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutCaught/Function.cs new file mode 100644 index 000000000..8c436c4ac --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackTimeoutCaught/Function.cs @@ -0,0 +1,43 @@ +// 4-14: Catch callback timeout and continue +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackTimeoutCaught; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync( + name: input, + config: new CallbackConfig { Timeout = TimeSpan.FromSeconds(3) }); + + string result; + try + { + result = await callback.GetResultAsync(); + } + catch (CallbackTimeoutException) + { + result = "callback_timeout_caught"; + } + + await context.WaitAsync(TimeSpan.FromSeconds(2)); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackWithName/CallbackWithName.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackWithName/CallbackWithName.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackWithName/CallbackWithName.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackWithName/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackWithName/Function.cs new file mode 100644 index 000000000..7181746ff --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/callback/CallbackWithName/Function.cs @@ -0,0 +1,30 @@ +// 4-2: Create callback with explicit name +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace CallbackWithName; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var callback = await context.CreateCallbackAsync(name: "approval"); + var result = await callback.GetResultAsync(); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildBasic/ChildBasic.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildBasic/ChildBasic.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildBasic/ChildBasic.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildBasic/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildBasic/Function.cs new file mode 100644 index 000000000..6471ecf1b --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildBasic/Function.cs @@ -0,0 +1,40 @@ +// 3-1: Child context basic - single step inside child context +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildBasic; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return input; + }); + + return stepResult; + }, config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildError/ChildError.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildError/ChildError.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildError/ChildError.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildError/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildError/Function.cs new file mode 100644 index 000000000..c418cc3bb --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildError/Function.cs @@ -0,0 +1,44 @@ +// 3-4: Child context error - step inside child throws (no retry), execution fails +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildError; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + throw new InvalidOperationException("step failed"); + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.None + }); + + return stepResult; + }, name: "error-child", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorCaught/ChildErrorCaught.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorCaught/ChildErrorCaught.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorCaught/ChildErrorCaught.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorCaught/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorCaught/Function.cs new file mode 100644 index 000000000..ea8aef1f7 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorCaught/Function.cs @@ -0,0 +1,58 @@ +// 3-5: Child context error caught - child with failing step is caught, recovery step returns input +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildErrorCaught; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + try + { + await context.RunInChildContextAsync(async (childContext, _ct) => + { + await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + throw new InvalidOperationException("step failed"); + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.None + }); + + return "unreachable"; + }, config: new ChildContextConfig { SubType = "RunInChildContext" }); + } + catch (Exception) + { + // Error caught, continue with recovery + } + + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return input; + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorNoStep/ChildErrorNoStep.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorNoStep/ChildErrorNoStep.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorNoStep/ChildErrorNoStep.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorNoStep/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorNoStep/Function.cs new file mode 100644 index 000000000..0672dcc25 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildErrorNoStep/Function.cs @@ -0,0 +1,34 @@ +// 3-15: Child context error without step - error thrown directly in child body +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildErrorNoStep; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + await Task.CompletedTask; + throw new InvalidOperationException("error in child body"); + }, name: "error-no-step", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildInterrupted/ChildInterrupted.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildInterrupted/ChildInterrupted.csproj new file mode 100644 index 000000000..f858202a6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildInterrupted/ChildInterrupted.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildInterrupted/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildInterrupted/Function.cs new file mode 100644 index 000000000..d36341020 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildInterrupted/Function.cs @@ -0,0 +1,71 @@ +// 3-12: Child context interrupted and re-executed +// Uses DynamoDB to track attempts; first invocation is interrupted, second succeeds +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildInterrupted; + +public class Function +{ + private static readonly AmazonDynamoDBClient DdbClient = new(); + private static readonly string TableName = Environment.GetEnvironmentVariable("ATTEMPTS_TABLE_NAME") ?? "Attempts"; + + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var executionId = context.ExecutionContext.DurableExecutionArn; + + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + var response = await DdbClient.UpdateItemAsync(new UpdateItemRequest + { + TableName = TableName, + Key = new Dictionary + { + ["executionId"] = new AttributeValue { S = executionId } + }, + UpdateExpression = "SET attemptCount = if_not_exists(attemptCount, :zero) + :inc", + ExpressionAttributeValues = new Dictionary + { + [":zero"] = new AttributeValue { N = "0" }, + [":inc"] = new AttributeValue { N = "1" } + }, + ReturnValues = ReturnValue.UPDATED_NEW + }); + + var attemptCount = int.Parse(response.Attributes["attemptCount"].N); + + if (attemptCount < 2) + { + await Task.Delay(1000); + Environment.Exit(1); + } + + return input; + }); + + return stepResult; + }, config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildLargePayload/ChildLargePayload.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildLargePayload/ChildLargePayload.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildLargePayload/ChildLargePayload.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildLargePayload/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildLargePayload/Function.cs new file mode 100644 index 000000000..58f74c313 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildLargePayload/Function.cs @@ -0,0 +1,56 @@ +// 3-11: Child context large payload (ReplayChildren mode) +// The step returns a small value; the child context body builds a large +// (>256KB) result from it, triggering ReplayChildren mode. A wait after the +// child forces a suspend/replay cycle so the child body runs twice. +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; +using Microsoft.Extensions.Logging; + +namespace ChildLargePayload; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + // Log the input via the durable logger (records carry + // durableExecutionArn — the conformance runner filters on that). + // Disable replay-aware filtering so the ReplayChildren re-execution + // also emits the line: the requirement expects it logged twice. + childContext.ConfigureLogger(new LoggerConfig { ModeAware = false }); + childContext.Logger.LogInformation("{Input}", input); + + // Step returns a SMALL value. + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return new string('A', 50 * 1024); // ~50KB seed + }); + + // Build a large result (>256KB) from the small step result. + return string.Concat(Enumerable.Repeat(stepResult, 6)); // ~300KB + }, name: "large-data-processor", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + // Wait after the child forces a suspend/replay cycle. + await context.WaitAsync(TimeSpan.FromSeconds(2)); + + return new { success = true, dataSize = result.Length }; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildMultipleSteps/ChildMultipleSteps.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildMultipleSteps/ChildMultipleSteps.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildMultipleSteps/ChildMultipleSteps.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildMultipleSteps/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildMultipleSteps/Function.cs new file mode 100644 index 000000000..44c1a0108 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildMultipleSteps/Function.cs @@ -0,0 +1,47 @@ +// 3-3: Child context with multiple sequential steps +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildMultipleSteps; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var step1Result = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return input; + }); + + var step2Result = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return step1Result; + }); + + return step2Result; + }, name: "multi-steps", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildNested/ChildNested.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildNested/ChildNested.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildNested/ChildNested.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildNested/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildNested/Function.cs new file mode 100644 index 000000000..f44200826 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildNested/Function.cs @@ -0,0 +1,52 @@ +// 3-6: Nested child contexts - outer child has step + inner child, inner child has step +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildNested; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (outerChild, _ct1) => + { + var outerStep = await outerChild.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return input; + }); + + var innerResult = await outerChild.RunInChildContextAsync(async (innerChild, _ct2) => + { + var innerStep = await innerChild.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return outerStep; + }); + + return innerStep; + }, name: "inner", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return innerResult; + }, name: "outer", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildPrintOnly/ChildPrintOnly.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildPrintOnly/ChildPrintOnly.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildPrintOnly/ChildPrintOnly.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildPrintOnly/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildPrintOnly/Function.cs new file mode 100644 index 000000000..b01417f9b --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildPrintOnly/Function.cs @@ -0,0 +1,44 @@ +// 3-17: Child context with durable logger only (verify no re-execution on replay) +// Child logs via the replay-aware durable logger and returns input (no durable +// ops), followed by a wait. Replay-aware filtering suppresses the line on the +// replay pass, so the input is logged exactly once. +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; +using Microsoft.Extensions.Logging; + +namespace ChildPrintOnly; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + await Task.CompletedTask; + // Durable logger (records carry durableExecutionArn — the conformance + // runner filters on that). Default replay-aware filtering suppresses + // the line on replay, so the input is logged exactly once. + childContext.Logger.LogInformation("{Input}", input); + return input; + }, name: "print-only", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + await context.WaitAsync(TimeSpan.FromSeconds(2)); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReplay/ChildReplay.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReplay/ChildReplay.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReplay/ChildReplay.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReplay/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReplay/Function.cs new file mode 100644 index 000000000..07f7b9413 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReplay/Function.cs @@ -0,0 +1,42 @@ +// 3-9: Child context replay (cached result) - child with step, followed by wait +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildReplay; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var childResult = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return input; + }); + + return stepResult; + }, config: new ChildContextConfig { SubType = "RunInChildContext" }); + + await context.WaitAsync(TimeSpan.FromSeconds(2)); + + return childResult; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReturnsNull/ChildReturnsNull.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReturnsNull/ChildReturnsNull.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReturnsNull/ChildReturnsNull.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReturnsNull/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReturnsNull/Function.cs new file mode 100644 index 000000000..461baf25b --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildReturnsNull/Function.cs @@ -0,0 +1,34 @@ +// 3-16: Child context returning null - child returns null without any durable operation +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildReturnsNull; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + await Task.CompletedTask; + return null; + }, name: "returns-null", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepAndWait/ChildStepAndWait.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepAndWait/ChildStepAndWait.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepAndWait/ChildStepAndWait.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepAndWait/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepAndWait/Function.cs new file mode 100644 index 000000000..4f7fbf127 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepAndWait/Function.cs @@ -0,0 +1,42 @@ +// 3-10: Child context with step and wait inside +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildStepAndWait; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return input; + }); + + await childContext.WaitAsync(TimeSpan.FromSeconds(2)); + + return stepResult; + }, name: "step-and-wait", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetry/ChildStepRetry.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetry/ChildStepRetry.csproj new file mode 100644 index 000000000..f858202a6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetry/ChildStepRetry.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetry/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetry/Function.cs new file mode 100644 index 000000000..b72ff651b --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetry/Function.cs @@ -0,0 +1,78 @@ +// 3-7: Child context with step retry (fails then succeeds) +// Uses DynamoDB to track attempts across invocations +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildStepRetry; + +public class Function +{ + private static readonly AmazonDynamoDBClient DdbClient = new(); + private static readonly string TableName = Environment.GetEnvironmentVariable("ATTEMPTS_TABLE_NAME") ?? "Attempts"; + + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var executionId = context.ExecutionContext.DurableExecutionArn; + + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + var response = await DdbClient.UpdateItemAsync(new UpdateItemRequest + { + TableName = TableName, + Key = new Dictionary + { + ["executionId"] = new AttributeValue { S = executionId } + }, + UpdateExpression = "SET attemptCount = if_not_exists(attemptCount, :zero) + :inc", + ExpressionAttributeValues = new Dictionary + { + [":zero"] = new AttributeValue { N = "0" }, + [":inc"] = new AttributeValue { N = "1" } + }, + ReturnValues = ReturnValue.UPDATED_NEW + }); + + var attemptCount = int.Parse(response.Attributes["attemptCount"].N); + + if (attemptCount < 2) + { + throw new InvalidOperationException($"Attempt {attemptCount} failed"); + } + return input; + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.FromDelegate((error, attempts) => + { + if (attempts >= 3) + return RetryDecision.DoNotRetry(); + return RetryDecision.RetryAfter(TimeSpan.FromSeconds(1)); + }) + }); + + return stepResult; + }, name: "retry-child", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetryExhaustion/ChildStepRetryExhaustion.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetryExhaustion/ChildStepRetryExhaustion.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetryExhaustion/ChildStepRetryExhaustion.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetryExhaustion/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetryExhaustion/Function.cs new file mode 100644 index 000000000..d58066738 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepRetryExhaustion/Function.cs @@ -0,0 +1,48 @@ +// 3-8: Child context with step retry exhaustion - step always fails, MaxAttempts=2 +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildStepRetryExhaustion; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + throw new InvalidOperationException("Always fails"); + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.Exponential( + maxAttempts: 2, + initialDelay: TimeSpan.FromSeconds(1), + backoffRate: 1, + jitter: JitterStrategy.None) + }); + + return stepResult; + }, name: "exhaust-child", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepWaitAfter/ChildStepWaitAfter.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepWaitAfter/ChildStepWaitAfter.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepWaitAfter/ChildStepWaitAfter.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepWaitAfter/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepWaitAfter/Function.cs new file mode 100644 index 000000000..c6c461196 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildStepWaitAfter/Function.cs @@ -0,0 +1,51 @@ +// 3-18: Child context with step and wait inside, step and wait after +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildStepWaitAfter; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var childResult = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return input; + }); + + await childContext.WaitAsync(TimeSpan.FromSeconds(2)); + + return stepResult; + }, name: "step-wait-after", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + var afterResult = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return childResult; + }); + + await context.WaitAsync(TimeSpan.FromSeconds(2)); + + return afterResult; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWaitReplay/ChildWaitReplay.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWaitReplay/ChildWaitReplay.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWaitReplay/ChildWaitReplay.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWaitReplay/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWaitReplay/Function.cs new file mode 100644 index 000000000..e1a80a6c7 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWaitReplay/Function.cs @@ -0,0 +1,42 @@ +// 3-13: Child context with wait inside - verify replay +// Child context containing only a wait, followed by a step outside the child +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildWaitReplay; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var childResult = await context.RunInChildContextAsync(async (childContext, _ct) => + { + await childContext.WaitAsync(TimeSpan.FromSeconds(2)); + return input; + }, name: "wait-replay", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return childResult; + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWithName/ChildWithName.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWithName/ChildWithName.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWithName/ChildWithName.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWithName/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWithName/Function.cs new file mode 100644 index 000000000..0dc2c2e5b --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/child/ChildWithName/Function.cs @@ -0,0 +1,44 @@ +// 3-2: Child context with name - named child context +using System.Text.Json; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ChildWithName; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(JsonElement input, IDurableContext context) + { + var name = input.GetProperty("name").GetString()!; + var value = input.GetProperty("value").GetString()!; + + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var stepResult = await childContext.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return value; + }); + + return stepResult; + }, name: name, config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeBasic/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeBasic/Function.cs new file mode 100644 index 000000000..c9b74124c --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeBasic/Function.cs @@ -0,0 +1,30 @@ +// 5-1: Invoke basic (target function succeeds) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeBasic; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + var result = await context.InvokeAsync(targetFunctionName, input); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeBasic/InvokeBasic.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeBasic/InvokeBasic.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeBasic/InvokeBasic.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeComplexObject/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeComplexObject/Function.cs new file mode 100644 index 000000000..ec8d23f4a --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeComplexObject/Function.cs @@ -0,0 +1,31 @@ +// 5-3: Invoke returning complex object (nested JSON) +using System.Text.Json; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeComplexObject; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(JsonElement input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + var result = await context.InvokeAsync(targetFunctionName, input); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeComplexObject/InvokeComplexObject.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeComplexObject/InvokeComplexObject.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeComplexObject/InvokeComplexObject.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeCustomPayloadSerdes/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeCustomPayloadSerdes/Function.cs new file mode 100644 index 000000000..e27ac8c70 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeCustomPayloadSerdes/Function.cs @@ -0,0 +1,39 @@ +// 5-15: Invoke with custom payload serdes (custom serializer for outgoing payload) +// Note: The .NET SDK does not have a per-invoke serdes API like the JS SDK. +// Instead, we achieve the same effect by transforming the payload before invoking, +// since the transformed payload is what gets sent to the target function. +using System.Text.Json; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeCustomPayloadSerdes; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(JsonElement input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + + // Custom payload serdes: transform the "data" field to uppercase before sending + var data = input.GetProperty("data").GetString()!; + var transformedPayload = data.ToUpperInvariant(); + + var result = await context.InvokeAsync(targetFunctionName, transformedPayload); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeCustomPayloadSerdes/InvokeCustomPayloadSerdes.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeCustomPayloadSerdes/InvokeCustomPayloadSerdes.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeCustomPayloadSerdes/InvokeCustomPayloadSerdes.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeEchoTarget/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeEchoTarget/Function.cs new file mode 100644 index 000000000..c7bac3796 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeEchoTarget/Function.cs @@ -0,0 +1,30 @@ +// Echo target: Returns whatever input it receives +using System.Text.Json; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeEchoTarget; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(JsonElement? input, IDurableContext context) + { + await Task.Delay(1000); + return input; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeEchoTarget/InvokeEchoTarget.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeEchoTarget/InvokeEchoTarget.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeEchoTarget/InvokeEchoTarget.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeFailTarget/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeFailTarget/Function.cs new file mode 100644 index 000000000..e3664ed44 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeFailTarget/Function.cs @@ -0,0 +1,30 @@ +// Fail target: Always throws an error +using System.Text.Json; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeFailTarget; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(JsonElement? input, IDurableContext context) + { + await Task.Delay(1000); + throw new InvalidOperationException("target failed"); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeFailTarget/InvokeFailTarget.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeFailTarget/InvokeFailTarget.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeFailTarget/InvokeFailTarget.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeInChildContext/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeInChildContext/Function.cs new file mode 100644 index 000000000..1921ee4e5 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeInChildContext/Function.cs @@ -0,0 +1,36 @@ +// 5-13: Invoke inside child context +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeInChildContext; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var invokeResult = await childContext.InvokeAsync(targetFunctionName, input); + return invokeResult; + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeInChildContext/InvokeInChildContext.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeInChildContext/InvokeInChildContext.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeInChildContext/InvokeInChildContext.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeLargePayload/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeLargePayload/Function.cs new file mode 100644 index 000000000..e96de1add --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeLargePayload/Function.cs @@ -0,0 +1,34 @@ +// 5-7: Invoke large payload (payload near size limit) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeLargePayload; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + + // Generate a large payload (~200KB) + var largePayload = new string('x', 200_000); + + var result = await context.InvokeAsync(targetFunctionName, largePayload); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeLargePayload/InvokeLargePayload.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeLargePayload/InvokeLargePayload.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeLargePayload/InvokeLargePayload.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeNull/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeNull/Function.cs new file mode 100644 index 000000000..b580baaea --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeNull/Function.cs @@ -0,0 +1,31 @@ +// 5-4: Invoke returning null (target echoes null input) +using System.Text.Json; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeNull; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + var result = await context.InvokeAsync(targetFunctionName, null); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeNull/InvokeNull.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeNull/InvokeNull.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeNull/InvokeNull.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplayRethrows/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplayRethrows/Function.cs new file mode 100644 index 000000000..4b20eff32 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplayRethrows/Function.cs @@ -0,0 +1,41 @@ +// 5-10: Invoke replay re-throws (failed invoke error re-thrown from cache) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeReplayRethrows; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FAIL_FUNCTION_NAME")!; + + try + { + await context.InvokeAsync(targetFunctionName, input); + } + catch (InvokeException) + { + // Caught on first replay, continue + } + + await context.WaitAsync(TimeSpan.FromSeconds(1)); + + return "completed_after_caught_error"; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplayRethrows/InvokeReplayRethrows.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplayRethrows/InvokeReplayRethrows.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplayRethrows/InvokeReplayRethrows.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplaySkips/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplaySkips/Function.cs new file mode 100644 index 000000000..97735897a --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplaySkips/Function.cs @@ -0,0 +1,34 @@ +// 5-9: Invoke replay skips (invoke result cached on replay) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeReplaySkips; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + + var result = await context.InvokeAsync(targetFunctionName, input); + + await context.WaitAsync(TimeSpan.FromSeconds(1)); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplaySkips/InvokeReplaySkips.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplaySkips/InvokeReplaySkips.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeReplaySkips/InvokeReplaySkips.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeSequential/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeSequential/Function.cs new file mode 100644 index 000000000..3b4dbb735 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeSequential/Function.cs @@ -0,0 +1,33 @@ +// 5-14: Multiple sequential invokes +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeSequential; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + + var result1 = await context.InvokeAsync(targetFunctionName, $"first:{input}"); + var result2 = await context.InvokeAsync(targetFunctionName, $"second:{result1}"); + + return result2; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeSequential/InvokeSequential.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeSequential/InvokeSequential.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeSequential/InvokeSequential.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFails/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFails/Function.cs new file mode 100644 index 000000000..688b9e54d --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFails/Function.cs @@ -0,0 +1,30 @@ +// 5-5: Invoke target fails (execution fails with InvokeError) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeTargetFails; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FAIL_FUNCTION_NAME")!; + var result = await context.InvokeAsync(targetFunctionName, input); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFails/InvokeTargetFails.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFails/InvokeTargetFails.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFails/InvokeTargetFails.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFailsCaught/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFailsCaught/Function.cs new file mode 100644 index 000000000..07e7982cd --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFailsCaught/Function.cs @@ -0,0 +1,39 @@ +// 5-6: Invoke target fails, caught (try/catch, execution succeeds) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeTargetFailsCaught; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FAIL_FUNCTION_NAME")!; + + try + { + await context.InvokeAsync(targetFunctionName, input); + } + catch (InvokeException ex) + { + return $"caught: {ex.Message}"; + } + + return "unexpected_success"; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFailsCaught/InvokeTargetFailsCaught.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFailsCaught/InvokeTargetFailsCaught.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeTargetFailsCaught/InvokeTargetFailsCaught.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeThenStep/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeThenStep/Function.cs new file mode 100644 index 000000000..e574b9d80 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeThenStep/Function.cs @@ -0,0 +1,39 @@ +// 5-12: Invoke then step (invoke result used by subsequent step) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeThenStep; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + + var invokeResult = await context.InvokeAsync(targetFunctionName, input); + + var stepResult = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return $"step_processed:{invokeResult}"; + }); + + return stepResult; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeThenStep/InvokeThenStep.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeThenStep/InvokeThenStep.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeThenStep/InvokeThenStep.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithName/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithName/Function.cs new file mode 100644 index 000000000..94d04d6b3 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithName/Function.cs @@ -0,0 +1,34 @@ +// 5-2: Invoke with name (explicit name parameter) +using System.Text.Json; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeWithName; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(JsonElement input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + var name = input.GetProperty("name").GetString()!; + var payload = input.GetProperty("payload").GetString()!; + + var result = await context.InvokeAsync(targetFunctionName, payload, name: name); + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithName/InvokeWithName.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithName/InvokeWithName.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithName/InvokeWithName.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithTenantId/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithTenantId/Function.cs new file mode 100644 index 000000000..ffad758ba --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithTenantId/Function.cs @@ -0,0 +1,38 @@ +// 5-8: Invoke with tenantId (tenant-isolated invocation) +using System.Text.Json; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace InvokeWithTenantId; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(JsonElement input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + var tenantId = input.GetProperty("tenantId").GetString()!; + var payload = input.GetProperty("payload").GetString()!; + + var result = await context.InvokeAsync( + targetFunctionName, + payload, + config: new InvokeConfig { TenantId = tenantId }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithTenantId/InvokeWithTenantId.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithTenantId/InvokeWithTenantId.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/InvokeWithTenantId/InvokeWithTenantId.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/StepThenInvoke/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/StepThenInvoke/Function.cs new file mode 100644 index 000000000..0e28b76ef --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/StepThenInvoke/Function.cs @@ -0,0 +1,39 @@ +// 5-11: Step then invoke (sequential operations) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepThenInvoke; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var targetFunctionName = Environment.GetEnvironmentVariable("TARGET_FUNCTION_NAME")!; + + var stepResult = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return $"processed:{input}"; + }); + + var invokeResult = await context.InvokeAsync(targetFunctionName, stepResult); + + return invokeResult; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/StepThenInvoke/StepThenInvoke.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/StepThenInvoke/StepThenInvoke.csproj new file mode 100644 index 000000000..fede088c4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/invoke/StepThenInvoke/StepThenInvoke.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapBasic/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapBasic/Function.cs new file mode 100644 index 000000000..fe6a0f7af --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapBasic/Function.cs @@ -0,0 +1,37 @@ +// 9-1: Map basic (one step per item, all succeed) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapBasic; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync?, List>(Workflow, input, context); + + private async Task> Workflow(List? input, IDurableContext context) + { + var items = input is { Count: > 0 } ? input : new List { "World", "Kiro" }; + + var result = await context.MapAsync( + items, + async (ctx, item, index, all, ct) => + await ctx.StepAsync(async (_, _ct) => $"Hello, {item}!"), + name: "map", + config: new MapConfig { MaxConcurrency = 1 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapBasic/MapBasic.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapBasic/MapBasic.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapBasic/MapBasic.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapConcurrent/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapConcurrent/Function.cs new file mode 100644 index 000000000..c203e4945 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapConcurrent/Function.cs @@ -0,0 +1,35 @@ +// 9-11: Map real concurrency (MaxConcurrency=2) preserves index-ordered results +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapConcurrent; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var result = await context.MapAsync( + new List { "r0", "r1", "r2" }, + async (ctx, item, index, all, ct) => item, + name: "concurrent", + config: new MapConfig { MaxConcurrency = 2 }); + + // Results are guaranteed index-ordered regardless of completion order. + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapConcurrent/MapConcurrent.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapConcurrent/MapConcurrent.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapConcurrent/MapConcurrent.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapEmpty/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapEmpty/Function.cs new file mode 100644 index 000000000..4e081df24 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapEmpty/Function.cs @@ -0,0 +1,35 @@ +// 9-4: Map with an empty items list completes immediately with an empty results list +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapEmpty; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync?, List>(Workflow, input, context); + + private async Task> Workflow(List? input, IDurableContext context) + { + var items = input ?? new List(); + + var result = await context.MapAsync( + items, + async (ctx, item, index, all, ct) => item, + name: "empty"); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapEmpty/MapEmpty.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapEmpty/MapEmpty.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapEmpty/MapEmpty.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailFast/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailFast/Function.cs new file mode 100644 index 000000000..b150228e5 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailFast/Function.cs @@ -0,0 +1,59 @@ +// 9-5: Map fail-fast via ToleratedFailureCount=0 stops after the first item failure +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapFailFast; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.MapAsync( + new List { "ok", "fail", "never" }, + async (ctx, item, index, all, ct) => + { + if (item == "fail") throw new Exception("item failed"); + return item; + }, + name: "failfast", + config: new MapConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 0 } + }); + + // TotalCount counts only dispatched items (never-dispatched items are + // excluded from the batch result), matching the JS SDK's totalCount. + return new + { + completionReason = ToWireReason(result.CompletionReason), + status = result.HasFailure ? "FAILED" : "SUCCEEDED", + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailFast/MapFailFast.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailFast/MapFailFast.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailFast/MapFailFast.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailThenWait/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailThenWait/Function.cs new file mode 100644 index 000000000..4f4f4d079 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailThenWait/Function.cs @@ -0,0 +1,63 @@ +// 9-18: Suspension after a map that completed with a failure (replay skips the completed map) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapFailThenWait; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + // With ToleratedFailureCount=1 both items run (item 1 fails, recorded). + // The map does not rethrow; a durable wait then suspends the execution. + var result = await context.MapAsync( + new List { "ok", "fail" }, + async (ctx, item, index, all, ct) => + { + if (item == "fail") throw new Exception("item failed"); + return item; + }, + name: "fail-then-wait", + config: new MapConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 1 } + }); + + // Suspend after the map (which recorded a failure); on replay the + // completed map is skipped. + await context.WaitAsync(TimeSpan.FromSeconds(1)); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + status = result.HasFailure ? "FAILED" : "SUCCEEDED", + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailThenWait/MapFailThenWait.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailThenWait/MapFailThenWait.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFailThenWait/MapFailThenWait.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFlat/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFlat/Function.cs new file mode 100644 index 000000000..0222e9c46 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFlat/Function.cs @@ -0,0 +1,41 @@ +// 9-12: Map with FLAT nesting (virtual iteration contexts) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapFlat; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + // With FLAT nesting each item's step is checkpointed directly under the + // parent Map context; no per-iteration MapIteration context events. + var result = await context.MapAsync( + new List { "fa", "fb" }, + async (ctx, item, index, all, ct) => + await ctx.StepAsync(async (_, _ct) => item), + name: "flat", + config: new MapConfig + { + MaxConcurrency = 1, + NestingType = NestingType.Flat + }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFlat/MapFlat.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFlat/MapFlat.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapFlat/MapFlat.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemIndex/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemIndex/Function.cs new file mode 100644 index 000000000..aacf91dfc --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemIndex/Function.cs @@ -0,0 +1,36 @@ +// 9-3: Map function receives item and index (returns item + index directly, no inner step) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapItemIndex; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync?, List>(Workflow, input, context); + + private async Task> Workflow(List? input, IDurableContext context) + { + var items = input is { Count: > 0 } ? input : new List { 10, 20, 30 }; + + var result = await context.MapAsync( + items, + async (ctx, item, index, all, ct) => item + index, + name: "indexed", + config: new MapConfig { MaxConcurrency = 1 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemIndex/MapItemIndex.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemIndex/MapItemIndex.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemIndex/MapItemIndex.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemNamer/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemNamer/Function.cs new file mode 100644 index 000000000..de79fa68d --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemNamer/Function.cs @@ -0,0 +1,42 @@ +// 9-13: Map with a custom item namer +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapItemNamer; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync?, List>(Workflow, input, context); + + private async Task> Workflow(List? input, IDurableContext context) + { + var items = input is { Count: > 0 } ? input : new List { 1, 2 }; + + // The item namer names each iteration from its item; it affects + // observability (the iteration operation name) but not results. + var result = await context.MapAsync( + items, + async (ctx, item, index, all, ct) => item * 10, + name: "named-items", + config: new MapConfig + { + MaxConcurrency = 1, + ItemNamer = (item, index) => $"item-{item}" + }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemNamer/MapItemNamer.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemNamer/MapItemNamer.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemNamer/MapItemNamer.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemsOnly/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemsOnly/Function.cs new file mode 100644 index 000000000..1a8d96ca0 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemsOnly/Function.cs @@ -0,0 +1,36 @@ +// 9-2: Map items-only form (no operation name), each item returns directly +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapItemsOnly; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync?, List>(Workflow, input, context); + + private async Task> Workflow(List? input, IDurableContext context) + { + var items = input is { Count: > 0 } ? input : new List { 1, 2 }; + + // Items-only form: no operation name argument. + var result = await context.MapAsync( + items, + async (ctx, item, index, all, ct) => item * 2, + config: new MapConfig { MaxConcurrency = 1 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemsOnly/MapItemsOnly.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemsOnly/MapItemsOnly.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapItemsOnly/MapItemsOnly.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapLargeResult/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapLargeResult/Function.cs new file mode 100644 index 000000000..27ea863ca --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapLargeResult/Function.cs @@ -0,0 +1,42 @@ +// 9-16: Map with a large aggregate result (exceeds the checkpoint size threshold) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapLargeResult; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + // Each iteration returns ~70KB; 4 items -> ~280KB aggregate, exceeding + // the 256KB checkpoint threshold. + var big = new string('x', 70000); + + var result = await context.MapAsync( + new List { 0, 1, 2, 3 }, + async (ctx, item, index, all, ct) => big, + name: "large", + config: new MapConfig { MaxConcurrency = 1 }); + + return new + { + successCount = result.SuccessCount, + totalCount = result.TotalCount + }; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapLargeResult/MapLargeResult.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapLargeResult/MapLargeResult.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapLargeResult/MapLargeResult.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapMinSuccessful/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapMinSuccessful/Function.cs new file mode 100644 index 000000000..947de7688 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapMinSuccessful/Function.cs @@ -0,0 +1,53 @@ +// 9-7: Map min-successful early completion +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapMinSuccessful; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.MapAsync( + new List { "s0", "s1", "s2", "s3" }, + async (ctx, item, index, all, ct) => item, + name: "min-successful", + config: new MapConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { MinSuccessful = 2 } + }); + + // After 2 successes the threshold is reached; items 2 and 3 are never + // started, so TotalCount (dispatched items only) is 2. + return new + { + completionReason = ToWireReason(result.CompletionReason), + successCount = result.SuccessCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapMinSuccessful/MapMinSuccessful.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapMinSuccessful/MapMinSuccessful.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapMinSuccessful/MapMinSuccessful.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapSuspendIteration/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapSuspendIteration/Function.cs new file mode 100644 index 000000000..d87ced647 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapSuspendIteration/Function.cs @@ -0,0 +1,43 @@ +// 9-15: Map suspends inside an iteration; replay skips the completed iteration +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapSuspendIteration; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var result = await context.MapAsync( + new List { "r0", "r1" }, + async (ctx, item, index, all, ct) => + { + // Iteration 1 issues a durable wait before its step, suspending + // the whole execution mid-map. On replay iteration 0 is skipped. + if (index == 1) + { + await ctx.WaitAsync(TimeSpan.FromSeconds(1)); + } + return await ctx.StepAsync(async (_, _ct) => item); + }, + name: "suspend", + config: new MapConfig { MaxConcurrency = 1 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapSuspendIteration/MapSuspendIteration.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapSuspendIteration/MapSuspendIteration.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapSuspendIteration/MapSuspendIteration.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThenWait/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThenWait/Function.cs new file mode 100644 index 000000000..56fdf9433 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThenWait/Function.cs @@ -0,0 +1,36 @@ +// 9-17: Suspension after a successful map (replay skips the completed map) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapThenWait; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var result = await context.MapAsync( + new List { "a", "b" }, + async (ctx, item, index, all, ct) => item.ToUpperInvariant(), + name: "then-wait", + config: new MapConfig { MaxConcurrency = 1 }); + + // Suspend after the map; on replay the completed map is skipped. + await context.WaitAsync(TimeSpan.FromSeconds(1)); + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThenWait/MapThenWait.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThenWait/MapThenWait.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThenWait/MapThenWait.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThrowIfError/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThrowIfError/Function.cs new file mode 100644 index 000000000..974546723 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThrowIfError/Function.cs @@ -0,0 +1,44 @@ +// 9-6: Map throw-if-error propagates an item failure to the execution +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapThrowIfError; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var result = await context.MapAsync( + new List { "fail", "never" }, + async (ctx, item, index, all, ct) => + { + if (item == "fail") throw new Exception("item failed"); + return item; + }, + name: "throwing", + config: new MapConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 0 } + }); + + // Rethrows the first item failure; uncaught, the execution fails. + result.ThrowIfError(); + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThrowIfError/MapThrowIfError.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThrowIfError/MapThrowIfError.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapThrowIfError/MapThrowIfError.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedExceeded/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedExceeded/Function.cs new file mode 100644 index 000000000..bc60c60b5 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedExceeded/Function.cs @@ -0,0 +1,58 @@ +// 9-9: Map tolerated-failure-count exceeded (stops early) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapToleratedExceeded; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.MapAsync( + new List { "f0", "f1", "never" }, + async (ctx, item, index, all, ct) => + { + if (item != "never") throw new Exception("item failed"); + return item; + }, + name: "tolerated-exceeded", + config: new MapConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 1 } + }); + + // Items 0 and 1 fail (failure count 2 exceeds the tolerance of 1), so + // item 2 is never started. + return new + { + completionReason = ToWireReason(result.CompletionReason), + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedExceeded/MapToleratedExceeded.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedExceeded/MapToleratedExceeded.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedExceeded/MapToleratedExceeded.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedPct/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedPct/Function.cs new file mode 100644 index 000000000..34c72ef75 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedPct/Function.cs @@ -0,0 +1,59 @@ +// 9-10: Map tolerated-failure-percentage exceeded (stops early) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapToleratedPct; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + // .NET's ToleratedFailurePercentage uses a 0.0-1.0 scale (the JS/Python + // SDKs use 25), so 25% is expressed as 0.25. + var result = await context.MapAsync( + new List { "f0", "f1", "never", "never" }, + async (ctx, item, index, all, ct) => + { + if (item != "never") throw new Exception("item failed"); + return item; + }, + name: "tolerated-pct", + config: new MapConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailurePercentage = 0.25 } + }); + + // Items 0 and 1 fail (2/4 = 50% exceeds 25%), so items 2 and 3 are never started. + return new + { + completionReason = ToWireReason(result.CompletionReason), + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedPct/MapToleratedPct.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedPct/MapToleratedPct.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedPct/MapToleratedPct.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedWithin/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedWithin/Function.cs new file mode 100644 index 000000000..4dd6febe4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedWithin/Function.cs @@ -0,0 +1,59 @@ +// 9-8: Map tolerated-failure-count within tolerance (all items complete) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace MapToleratedWithin; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.MapAsync( + new List { "s0", "fail", "s2" }, + async (ctx, item, index, all, ct) => + { + if (item == "fail") throw new Exception("item failed"); + return item; + }, + name: "tolerated", + config: new MapConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 1 } + }); + + // One failure does not exceed the tolerance of 1, so all items run; + // status is FAILED because at least one item failed. + return new + { + completionReason = ToWireReason(result.CompletionReason), + status = result.HasFailure ? "FAILED" : "SUCCEEDED", + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedWithin/MapToleratedWithin.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedWithin/MapToleratedWithin.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/map/MapToleratedWithin/MapToleratedWithin.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAccessors/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAccessors/Function.cs new file mode 100644 index 000000000..1e16ccf8f --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAccessors/Function.cs @@ -0,0 +1,50 @@ +// 8-20: Parallel result accessors (HasFailure, Succeeded, Failed, GetErrors) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelAccessors; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => "ok", + async (ctx, ct) => throw new Exception("branch failed"), + async (ctx, ct) => "ok2" + }; + + var result = await context.ParallelAsync( + branches, + name: "accessors", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 1 } + }); + + return new + { + hasFailure = result.HasFailure, + successCount = result.Succeeded.Count, + failureCount = result.Failed.Count, + errorCount = result.GetErrors().Count + }; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAccessors/ParallelAccessors.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAccessors/ParallelAccessors.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAccessors/ParallelAccessors.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAllFail/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAllFail/Function.cs new file mode 100644 index 000000000..f6c6de167 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAllFail/Function.cs @@ -0,0 +1,59 @@ +// 8-16: Parallel where all branches fail +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelAllFail; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => throw new Exception("fail-1"), + async (ctx, ct) => throw new Exception("fail-2"), + async (ctx, ct) => throw new Exception("fail-3") + }; + + var result = await context.ParallelAsync( + branches, + name: "all-fail", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 3 } + }); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + status = result.HasFailure ? "FAILED" : "SUCCEEDED", + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAllFail/ParallelAllFail.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAllFail/ParallelAllFail.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelAllFail/ParallelAllFail.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBadConcurrency/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBadConcurrency/Function.cs new file mode 100644 index 000000000..207af7997 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBadConcurrency/Function.cs @@ -0,0 +1,39 @@ +// 8-19: Parallel with invalid MaxConcurrency=0 (throws ArgumentOutOfRangeException) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelBadConcurrency; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => "a", + async (ctx, ct) => "b" + }; + + var result = await context.ParallelAsync( + branches, + name: "bad-concurrency", + config: new ParallelConfig { MaxConcurrency = 0 }); + + return "unreachable"; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBadConcurrency/ParallelBadConcurrency.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBadConcurrency/ParallelBadConcurrency.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBadConcurrency/ParallelBadConcurrency.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBasic/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBasic/Function.cs new file mode 100644 index 000000000..608d8c9e0 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBasic/Function.cs @@ -0,0 +1,39 @@ +// 8-1: Parallel basic with steps in branches +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelBasic; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => await ctx.StepAsync(async (_, _ct) => "task-1"), + async (ctx, ct) => await ctx.StepAsync(async (_, _ct) => "task-2") + }; + + var result = await context.ParallelAsync( + branches, + name: "parallel", + config: new ParallelConfig { MaxConcurrency = 1 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBasic/ParallelBasic.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBasic/ParallelBasic.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBasic/ParallelBasic.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBranchesOnly/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBranchesOnly/Function.cs new file mode 100644 index 000000000..6eb00765f --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBranchesOnly/Function.cs @@ -0,0 +1,38 @@ +// 8-2: Parallel branches only (no inner steps) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelBranchesOnly; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => "alpha", + async (ctx, ct) => "beta" + }; + + var result = await context.ParallelAsync( + branches, + config: new ParallelConfig { MaxConcurrency = 1 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBranchesOnly/ParallelBranchesOnly.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBranchesOnly/ParallelBranchesOnly.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelBranchesOnly/ParallelBranchesOnly.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelCombinedConfig/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelCombinedConfig/Function.cs new file mode 100644 index 000000000..196f68ec9 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelCombinedConfig/Function.cs @@ -0,0 +1,63 @@ +// 8-18: Parallel with combined MinSuccessful and ToleratedFailureCount +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelCombinedConfig; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => throw new Exception("fail-1"), + async (ctx, ct) => throw new Exception("fail-2"), + async (ctx, ct) => "ok", + async (ctx, ct) => "ok" + }; + + var result = await context.ParallelAsync( + branches, + name: "combined", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig + { + MinSuccessful = 3, + ToleratedFailureCount = 1 + } + }); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelCombinedConfig/ParallelCombinedConfig.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelCombinedConfig/ParallelCombinedConfig.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelCombinedConfig/ParallelCombinedConfig.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelConcurrent/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelConcurrent/Function.cs new file mode 100644 index 000000000..2c32536dd --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelConcurrent/Function.cs @@ -0,0 +1,40 @@ +// 8-11: Parallel with maxConcurrency=2 +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelConcurrent; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => "r0", + async (ctx, ct) => "r1", + async (ctx, ct) => "r2" + }; + + var result = await context.ParallelAsync( + branches, + name: "concurrent", + config: new ParallelConfig { MaxConcurrency = 2 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelConcurrent/ParallelConcurrent.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelConcurrent/ParallelConcurrent.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelConcurrent/ParallelConcurrent.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelEmpty/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelEmpty/Function.cs new file mode 100644 index 000000000..e2d6614f3 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelEmpty/Function.cs @@ -0,0 +1,34 @@ +// 8-5: Parallel with empty branches list +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelEmpty; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var branches = new List>>(); + + var result = await context.ParallelAsync( + branches, + name: "empty"); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelEmpty/ParallelEmpty.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelEmpty/ParallelEmpty.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelEmpty/ParallelEmpty.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailFast/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailFast/Function.cs new file mode 100644 index 000000000..fd29622ea --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailFast/Function.cs @@ -0,0 +1,59 @@ +// 8-6: Parallel fail-fast (ToleratedFailureCount=0) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelFailFast; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => "ok", + async (ctx, ct) => throw new Exception("branch failed"), + async (ctx, ct) => "never" + }; + + var result = await context.ParallelAsync( + branches, + name: "fail-fast", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 0 } + }); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + status = result.HasFailure ? "FAILED" : "SUCCEEDED", + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailFast/ParallelFailFast.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailFast/ParallelFailFast.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailFast/ParallelFailFast.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailureExceedsTolerance/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailureExceedsTolerance/Function.cs new file mode 100644 index 000000000..6898e0c18 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailureExceedsTolerance/Function.cs @@ -0,0 +1,58 @@ +// 8-10: Parallel where failures exceed tolerance +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelFailureExceedsTolerance; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => throw new Exception("fail-1"), + async (ctx, ct) => throw new Exception("fail-2"), + async (ctx, ct) => "never" + }; + + var result = await context.ParallelAsync( + branches, + name: "exceed", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 1 } + }); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailureExceedsTolerance/ParallelFailureExceedsTolerance.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailureExceedsTolerance/ParallelFailureExceedsTolerance.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailureExceedsTolerance/ParallelFailureExceedsTolerance.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentage/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentage/Function.cs new file mode 100644 index 000000000..4baddec32 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentage/Function.cs @@ -0,0 +1,59 @@ +// 8-13: Parallel with ToleratedFailurePercentage=0.25 (exceeded) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelFailurePercentage; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => throw new Exception("fail-1"), + async (ctx, ct) => throw new Exception("fail-2"), + async (ctx, ct) => "ok", + async (ctx, ct) => "ok" + }; + + var result = await context.ParallelAsync( + branches, + name: "pct", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailurePercentage = 0.25 } + }); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentage/ParallelFailurePercentage.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentage/ParallelFailurePercentage.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentage/ParallelFailurePercentage.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentageExact/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentageExact/Function.cs new file mode 100644 index 000000000..7749c4afd --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentageExact/Function.cs @@ -0,0 +1,60 @@ +// 8-22: Parallel with ToleratedFailurePercentage=0.25 (exactly at threshold, not exceeded) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelFailurePercentageExact; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => throw new Exception("fail-1"), + async (ctx, ct) => "ok", + async (ctx, ct) => "ok", + async (ctx, ct) => "ok" + }; + + var result = await context.ParallelAsync( + branches, + name: "pct-exact", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailurePercentage = 0.25 } + }); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + status = result.HasFailure ? "FAILED" : "SUCCEEDED", + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentageExact/ParallelFailurePercentageExact.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentageExact/ParallelFailurePercentageExact.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFailurePercentageExact/ParallelFailurePercentageExact.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFlat/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFlat/Function.cs new file mode 100644 index 000000000..257373934 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFlat/Function.cs @@ -0,0 +1,43 @@ +// 8-12: Parallel with flat nesting type +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelFlat; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => await ctx.StepAsync(async (_, _ct) => "fa"), + async (ctx, ct) => await ctx.StepAsync(async (_, _ct) => "fb") + }; + + var result = await context.ParallelAsync( + branches, + name: "flat", + config: new ParallelConfig + { + MaxConcurrency = 1, + NestingType = NestingType.Flat + }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFlat/ParallelFlat.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFlat/ParallelFlat.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelFlat/ParallelFlat.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelHeterogeneous/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelHeterogeneous/Function.cs new file mode 100644 index 000000000..ffd09860d --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelHeterogeneous/Function.cs @@ -0,0 +1,39 @@ +// 8-4: Parallel with heterogeneous return types +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelHeterogeneous; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => "hello", + async (ctx, ct) => 42, + async (ctx, ct) => new { k = "v" } + }; + + var result = await context.ParallelAsync( + branches, + config: new ParallelConfig { MaxConcurrency = 1 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelHeterogeneous/ParallelHeterogeneous.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelHeterogeneous/ParallelHeterogeneous.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelHeterogeneous/ParallelHeterogeneous.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinNotReached/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinNotReached/Function.cs new file mode 100644 index 000000000..7b0155062 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinNotReached/Function.cs @@ -0,0 +1,59 @@ +// 8-17: Parallel where MinSuccessful is not reached +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelMinNotReached; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => "ok", + async (ctx, ct) => throw new Exception("branch failed"), + async (ctx, ct) => "ok" + }; + + var result = await context.ParallelAsync( + branches, + name: "min-not-reached", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { MinSuccessful = 3 } + }); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + status = result.HasFailure ? "FAILED" : "SUCCEEDED", + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinNotReached/ParallelMinNotReached.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinNotReached/ParallelMinNotReached.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinNotReached/ParallelMinNotReached.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinSuccessful/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinSuccessful/Function.cs new file mode 100644 index 000000000..edce54400 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinSuccessful/Function.cs @@ -0,0 +1,58 @@ +// 8-8: Parallel with MinSuccessful completion config +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelMinSuccessful; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => "s0", + async (ctx, ct) => "s1", + async (ctx, ct) => "s2", + async (ctx, ct) => "s3" + }; + + var result = await context.ParallelAsync( + branches, + name: "min-success", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { MinSuccessful = 2 } + }); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + successCount = result.SuccessCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinSuccessful/ParallelMinSuccessful.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinSuccessful/ParallelMinSuccessful.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelMinSuccessful/ParallelMinSuccessful.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNamedBranches/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNamedBranches/Function.cs new file mode 100644 index 000000000..813cf3f05 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNamedBranches/Function.cs @@ -0,0 +1,39 @@ +// 8-3: Parallel with named branches using DurableBranch +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelNamedBranches; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var branches = new List> + { + new DurableBranch("first", async (ctx, ct) => "one"), + new DurableBranch("second", async (ctx, ct) => "two") + }; + + var result = await context.ParallelAsync( + branches, + name: "named-parallel", + config: new ParallelConfig { MaxConcurrency = 1 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNamedBranches/ParallelNamedBranches.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNamedBranches/ParallelNamedBranches.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNamedBranches/ParallelNamedBranches.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNested/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNested/Function.cs new file mode 100644 index 000000000..012da96e0 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNested/Function.cs @@ -0,0 +1,52 @@ +// 8-21: Nested parallel (outer parallel contains inner parallel) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelNested; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>>(Workflow, input, context); + + private async Task>> Workflow(object? input, IDurableContext context) + { + var outerBranches = new List>>> + { + async (ctx, ct) => + { + var innerBranches = new List>> + { + async (innerCtx, innerCt) => await innerCtx.StepAsync(async (_, _ct) => "i1"), + async (innerCtx, innerCt) => await innerCtx.StepAsync(async (_, _ct) => "i2") + }; + + var innerResult = await ctx.ParallelAsync( + innerBranches, + name: "inner", + config: new ParallelConfig { MaxConcurrency = 1 }); + + return innerResult.GetResults().ToList(); + } + }; + + var outerResult = await context.ParallelAsync( + outerBranches, + name: "outer", + config: new ParallelConfig { MaxConcurrency = 1 }); + + return outerResult.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNested/ParallelNested.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNested/ParallelNested.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelNested/ParallelNested.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelRethrow/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelRethrow/Function.cs new file mode 100644 index 000000000..8019cf968 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelRethrow/Function.cs @@ -0,0 +1,45 @@ +// 8-7: Parallel rethrow (ThrowIfError propagates failure) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelRethrow; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => throw new Exception("branch error"), + async (ctx, ct) => "never" + }; + + var result = await context.ParallelAsync( + branches, + name: "rethrow", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 0 } + }); + + result.ThrowIfError(); + + return "unreachable"; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelRethrow/ParallelRethrow.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelRethrow/ParallelRethrow.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelRethrow/ParallelRethrow.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelToleratedFailure/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelToleratedFailure/Function.cs new file mode 100644 index 000000000..0c31a7469 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelToleratedFailure/Function.cs @@ -0,0 +1,59 @@ +// 8-9: Parallel with ToleratedFailureCount=1 (one failure tolerated) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelToleratedFailure; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => "ok", + async (ctx, ct) => throw new Exception("branch failed"), + async (ctx, ct) => "ok2" + }; + + var result = await context.ParallelAsync( + branches, + name: "tolerant", + config: new ParallelConfig + { + MaxConcurrency = 1, + CompletionConfig = new CompletionConfig { ToleratedFailureCount = 1 } + }); + + return new + { + completionReason = ToWireReason(result.CompletionReason), + status = result.HasFailure ? "FAILED" : "SUCCEEDED", + successCount = result.SuccessCount, + failureCount = result.FailureCount, + totalCount = result.TotalCount + }; + } + + private static string ToWireReason(CompletionReason reason) => reason switch + { + CompletionReason.AllCompleted => "ALL_COMPLETED", + CompletionReason.MinSuccessfulReached => "MIN_SUCCESSFUL_REACHED", + CompletionReason.FailureToleranceExceeded => "FAILURE_TOLERANCE_EXCEEDED", + _ => reason.ToString() + }; +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelToleratedFailure/ParallelToleratedFailure.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelToleratedFailure/ParallelToleratedFailure.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelToleratedFailure/ParallelToleratedFailure.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelWithWait/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelWithWait/Function.cs new file mode 100644 index 000000000..dd68997e9 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelWithWait/Function.cs @@ -0,0 +1,43 @@ +// 8-14: Parallel with WaitAsync in a branch +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace ParallelWithWait; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync>(Workflow, input, context); + + private async Task> Workflow(object? input, IDurableContext context) + { + var branches = new List>> + { + async (ctx, ct) => await ctx.StepAsync(async (_, _ct) => "b0"), + async (ctx, ct) => + { + await ctx.WaitAsync(TimeSpan.FromSeconds(2)); + return "b1"; + } + }; + + var result = await context.ParallelAsync( + branches, + name: "wait-branch", + config: new ParallelConfig { MaxConcurrency = 1 }); + + return result.GetResults().ToList(); + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelWithWait/ParallelWithWait.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelWithWait/ParallelWithWait.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/parallel/ParallelWithWait/ParallelWithWait.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/build_examples.sh b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/build_examples.sh new file mode 100755 index 000000000..4ce59e368 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/build_examples.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# Build .NET Durable Execution conformance test handlers into publish// +# directories that the SAM templates deploy via the makefile BuildMethod. +# +# Each handler project references the in-repo SDK directly (../../../../src/...), +# so no SDK copy/pack step is needed. Each project publishes a self-contained +# `bootstrap` executable for the dotnet8 managed runtime. +# +# Usage: +# ./build_examples.sh [operation...] +# +# Operations (default: every suite directory found next to the templates): +# step wait callback child invoke parallel map wait_for_callback wait_for_condition +# +# Examples: +# ./build_examples.sh step +# ./build_examples.sh + +set -e +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONFORMANCE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +PUBLISH_DIR="${CONFORMANCE_DIR}/publish" + +# Remaining args are operations; default to every suite dir that has handlers. +if [[ $# -gt 0 ]]; then + OPERATIONS=("$@") +else + OPERATIONS=() + for dir in "${CONFORMANCE_DIR}"/*/; do + op="$(basename "$dir")" + [[ "$op" == publish ]] && continue + [[ "$op" == scripts ]] && continue + # Only treat dirs that actually contain handler projects as suites. + if find "$dir" -name "*.csproj" -print -quit | grep -q .; then + OPERATIONS+=("$op") + fi + done +fi + +echo "Building .NET conformance test handlers..." >&2 +echo " Output: ${PUBLISH_DIR}" >&2 +echo " Operations: ${OPERATIONS[*]}" >&2 +echo "" >&2 + +rm -rf "${PUBLISH_DIR}" +mkdir -p "${PUBLISH_DIR}" + +for op in "${OPERATIONS[@]}"; do + OP_DIR="${CONFORMANCE_DIR}/${op}" + if [[ ! -d "${OP_DIR}" ]]; then + echo "Warning: Operation directory '${op}/' not found, skipping." >&2 + continue + fi + + echo "=== Building operation: ${op} ===" >&2 + + while IFS= read -r csproj; do + PROJECT_NAME="$(basename "${csproj}" .csproj)" + echo " Publishing ${PROJECT_NAME}..." >&2 + dotnet publish "${csproj}" \ + -c Release \ + -f net8.0 \ + --self-contained false \ + -o "${PUBLISH_DIR}/${PROJECT_NAME}" >&2 + + # Makefile that SAM's makefile BuildMethod invokes: copy the pre-built + # publish output into the SAM artifact directory (the bootstrap binary + # is already produced above). + cat > "${PUBLISH_DIR}/${PROJECT_NAME}/Makefile" <&2 +done + +# --- Alias binaries reused under a second function logical id --- +# Some templates register the same binary as a second Lambda (e.g. a tenancy- +# enabled echo target). SAM's makefile build target is keyed on the function +# logical id, so each alias needs its own publish dir + matching Makefile target. +# Format: ":". Only aliased when the source was +# actually published in this run (i.e. its suite was selected). +ALIASES=("InvokeEchoTarget:InvokeEchoTargetTenant") +for pair in "${ALIASES[@]}"; do + src="${pair%%:*}" + alias="${pair##*:}" + if [[ -d "${PUBLISH_DIR}/${src}" ]]; then + echo " Aliasing ${src} -> ${alias}..." >&2 + rm -rf "${PUBLISH_DIR}/${alias}" + cp -r "${PUBLISH_DIR}/${src}" "${PUBLISH_DIR}/${alias}" + cat > "${PUBLISH_DIR}/${alias}/Makefile" <&2 +echo " Published to: ${PUBLISH_DIR}" >&2 diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/discover_suites.py b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/discover_suites.py new file mode 100644 index 000000000..27a504e6f --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/discover_suites.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Discover conformance suites from template files. + +A suite is discovered when there is both a template_.yaml file and a +sibling / directory containing at least one handler project (*.csproj). +Prints a compact JSON array consumed by the GitHub Actions matrix. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +# The conformance root is the parent of this scripts/ directory. +CONFORMANCE_DIR = Path(__file__).resolve().parents[1] +TEMPLATE_PREFIX = "template_" +TEMPLATE_SUFFIX = ".yaml" + + +def discover_suites(conformance_dir: Path = CONFORMANCE_DIR) -> tuple[str, ...]: + """Return sorted suites with matching templates and non-empty handler dirs.""" + templates = sorted(conformance_dir.glob(f"{TEMPLATE_PREFIX}*{TEMPLATE_SUFFIX}")) + if not templates: + raise SystemExit(f"No {TEMPLATE_PREFIX}{TEMPLATE_SUFFIX} files found") + + suites: list[str] = [] + for template in templates: + suite = template.name[len(TEMPLATE_PREFIX) : -len(TEMPLATE_SUFFIX)] + if not suite: + raise SystemExit(f"Invalid conformance template name: {template.name}") + + handlers_dir = conformance_dir / suite + if not handlers_dir.is_dir(): + raise SystemExit( + f"Template {template.name} has no matching handler directory: {handlers_dir}" + ) + + if not list(handlers_dir.glob("**/*.csproj")): + raise SystemExit( + f"No handler projects found for suite {suite}: {handlers_dir}" + ) + + suites.append(suite) + + return tuple(suites) + + +def main() -> None: + print(json.dumps(discover_suites(), separators=(",", ":"))) + + +if __name__ == "__main__": + main() diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/inject_execution_role.py b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/inject_execution_role.py new file mode 100644 index 000000000..129470ba6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/inject_execution_role.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""Inject a pre-existing Lambda execution role into a conformance SAM template. + +Rewrites the given template in place so that every AWS::Serverless::Function +uses the provided execution role ARN, and removes the self-created +DurableFunctionRole resource. Used by CI to avoid creating an IAM role per +deploy; the checked-in template stays self-contained for local runs. + +CloudFormation short-form intrinsic tags (!Sub, !GetAtt, !Ref, ...) are +preserved through a tag-aware load/dump round-trip. + +Usage: + python3 scripts/inject_execution_role.py --template template_step.yaml \ + --role-arn arn:aws:iam::123456789012:role/my-execution-role + +Requires PyYAML (already a dependency of the conformance runner). +""" + +from __future__ import annotations + +import argparse +import sys + +import yaml + +SELF_CREATED_ROLE = "DurableFunctionRole" +FUNCTION_TYPE = "AWS::Serverless::Function" + + +class CfnTag: + """Opaque holder for a CloudFormation short-form tag (e.g. !Sub, !GetAtt).""" + + def __init__(self, tag: str, value: object) -> None: + self.tag = tag + self.value = value + + +class CfnLoader(yaml.SafeLoader): + """SafeLoader that wraps unknown (CloudFormation) tags instead of failing.""" + + +def _construct_cfn_tag(loader: CfnLoader, suffix: str, node: yaml.Node) -> CfnTag: + if isinstance(node, yaml.ScalarNode): + value: object = loader.construct_scalar(node) + elif isinstance(node, yaml.SequenceNode): + value = loader.construct_sequence(node, deep=True) + else: + value = loader.construct_mapping(node, deep=True) + return CfnTag(node.tag, value) + + +CfnLoader.add_multi_constructor("!", _construct_cfn_tag) + + +class CfnDumper(yaml.SafeDumper): + """SafeDumper that re-emits wrapped CloudFormation tags verbatim.""" + + +def _represent_cfn_tag(dumper: CfnDumper, data: CfnTag) -> yaml.Node: + if isinstance(data.value, str): + return dumper.represent_scalar(data.tag, data.value) + if isinstance(data.value, list): + return dumper.represent_sequence(data.tag, data.value) + return dumper.represent_mapping(data.tag, data.value) + + +CfnDumper.add_representer(CfnTag, _represent_cfn_tag) + + +def _safe_load_cfn(stream: object) -> object: + """Safely load a CloudFormation template. + + Equivalent to yaml.safe_load (CfnLoader extends yaml.SafeLoader) while + additionally preserving CloudFormation short-form tags. + """ + loader = CfnLoader(stream) + try: + return loader.get_single_data() + finally: + loader.dispose() + + +def inject(template_path: str, role_arn: str) -> int: + """Rewrite template_path in place; return the number of functions updated.""" + with open(template_path, encoding="utf-8") as f: + doc = _safe_load_cfn(f) + + resources = doc.get("Resources") + if not isinstance(resources, dict): + raise SystemExit(f"{template_path}: no Resources section found") + + resources.pop(SELF_CREATED_ROLE, None) + + updated = 0 + for resource in resources.values(): + if resource.get("Type") == FUNCTION_TYPE: + resource.setdefault("Properties", {})["Role"] = role_arn + updated += 1 + + if updated == 0: + raise SystemExit(f"{template_path}: no {FUNCTION_TYPE} resources found") + + with open(template_path, "w", encoding="utf-8") as f: + yaml.dump(doc, f, Dumper=CfnDumper, sort_keys=False) + + return updated + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument( + "--template", + required=True, + help="Path to the SAM template to rewrite in place.", + ) + parser.add_argument( + "--role-arn", + required=True, + help="Execution role ARN to set on every serverless function.", + ) + args = parser.parse_args() + + updated = inject(args.template, args.role_arn) + print(f"Injected execution role into {updated} functions in {args.template}") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAndWaitReplay/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAndWaitReplay/Function.cs new file mode 100644 index 000000000..fb041da42 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAndWaitReplay/Function.cs @@ -0,0 +1,37 @@ +// 1-8: Step and wait with replay +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepAndWaitReplay; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return "computed"; + }); + + await context.WaitAsync(TimeSpan.FromSeconds(2)); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAndWaitReplay/StepAndWaitReplay.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAndWaitReplay/StepAndWaitReplay.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAndWaitReplay/StepAndWaitReplay.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceNoRetry/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceNoRetry/Function.cs new file mode 100644 index 000000000..019096708 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceNoRetry/Function.cs @@ -0,0 +1,47 @@ +// 1-17: Step with AtMostOncePerRetry semantics (interrupted, no retry) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; +using Microsoft.Extensions.Logging; + +namespace StepAtMostOnceNoRetry; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.StepAsync( + async (stepContext, _ct) => + { + await Task.CompletedTask; + // Log input via durable step logger (records carry durableExecutionArn + // — the conformance runner filters on that structured field). + stepContext.Logger.LogInformation("{Input}", input); + // Simulate Lambda crash + Environment.Exit(1); + return "unreachable"; + }, + name: "at_most_once_flaky_step", + config: new StepConfig + { + Semantics = StepSemantics.AtMostOncePerRetry, + RetryStrategy = RetryStrategy.None + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceNoRetry/StepAtMostOnceNoRetry.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceNoRetry/StepAtMostOnceNoRetry.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceNoRetry/StepAtMostOnceNoRetry.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceWithRetry/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceWithRetry/Function.cs new file mode 100644 index 000000000..97a5a5539 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceWithRetry/Function.cs @@ -0,0 +1,82 @@ +// 1-18: Step with AtMostOncePerRetry semantics (with retry, succeeds on second attempt) +// Uses DynamoDB to track attempts across invocations +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; +using Microsoft.Extensions.Logging; + +namespace StepAtMostOnceWithRetry; + +public class Function +{ + private static readonly AmazonDynamoDBClient DdbClient = new(); + private static readonly string TableName = Environment.GetEnvironmentVariable("ATTEMPTS_TABLE_NAME") ?? "Attempts"; + + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var executionId = context.ExecutionContext.DurableExecutionArn; + + var result = await context.StepAsync( + async (stepContext, _ct) => + { + // Atomically increment attempt counter in DynamoDB + var response = await DdbClient.UpdateItemAsync(new UpdateItemRequest + { + TableName = TableName, + Key = new Dictionary + { + ["executionId"] = new AttributeValue { S = executionId } + }, + UpdateExpression = "SET attemptCount = if_not_exists(attemptCount, :zero) + :inc", + ExpressionAttributeValues = new Dictionary + { + [":zero"] = new AttributeValue { N = "0" }, + [":inc"] = new AttributeValue { N = "1" } + }, + ReturnValues = ReturnValue.UPDATED_NEW + }); + + var attemptCount = int.Parse(response.Attributes["attemptCount"].N); + + if (attemptCount < 2) + { + // Log input via durable step logger (structured record with + // durableExecutionArn — matched by the conformance runner). + stepContext.Logger.LogInformation("{Input}", input); + // First attempt: simulate Lambda crash + Environment.Exit(1); + } + // Second attempt (retry): log and succeed + stepContext.Logger.LogInformation("{Input}", input); + return "succeeded on second attempt"; + }, + config: new StepConfig + { + Semantics = StepSemantics.AtMostOncePerRetry, + RetryStrategy = RetryStrategy.FromDelegate((error, attempts) => + { + if (attempts >= 3) + return RetryDecision.DoNotRetry(); + return RetryDecision.RetryAfter(TimeSpan.FromSeconds(1)); + }) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceWithRetry/StepAtMostOnceWithRetry.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceWithRetry/StepAtMostOnceWithRetry.csproj new file mode 100644 index 000000000..f858202a6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepAtMostOnceWithRetry/StepAtMostOnceWithRetry.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepBasic/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepBasic/Function.cs new file mode 100644 index 000000000..2619e9846 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepBasic/Function.cs @@ -0,0 +1,35 @@ +// 1-1: Step basic (succeeds on first attempt) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepBasic; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return $"Hello, {input}!"; + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepBasic/StepBasic.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepBasic/StepBasic.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepBasic/StepBasic.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepComplexObject/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepComplexObject/Function.cs new file mode 100644 index 000000000..4186da180 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepComplexObject/Function.cs @@ -0,0 +1,68 @@ +// 1-4: Returning complex object +using System.Text.Json.Serialization; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepComplexObject; + +public class InputEvent +{ + public string Name { get; set; } = ""; + public List Tags { get; set; } = new(); +} + +public class UserInfo +{ + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("tags")] + public List Tags { get; set; } = new(); +} + +public class OutputResult +{ + [JsonPropertyName("user")] + public UserInfo User { get; set; } = new(); + + [JsonPropertyName("count")] + public int Count { get; set; } +} + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(InputEvent input, IDurableContext context) + { + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return new OutputResult + { + User = new UserInfo + { + Name = input.Name, + Tags = input.Tags + }, + Count = input.Tags.Count + }; + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepComplexObject/StepComplexObject.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepComplexObject/StepComplexObject.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepComplexObject/StepComplexObject.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepCustomSerdes/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepCustomSerdes/Function.cs new file mode 100644 index 000000000..7f043ef18 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepCustomSerdes/Function.cs @@ -0,0 +1,39 @@ +// 1-6: Custom serdes (per-step) - transforms string to uppercase +// Note: The .NET SDK does not have a per-step serdes API like the JS SDK. +// Instead, we achieve the same effect by transforming the value within the step +// function itself, since the step result is what gets checkpointed. +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepCustomSerdes; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + // Simulate custom serdes by transforming to uppercase + return input.ToUpperInvariant(); + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepCustomSerdes/StepCustomSerdes.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepCustomSerdes/StepCustomSerdes.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepCustomSerdes/StepCustomSerdes.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepDefaultRetry/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepDefaultRetry/Function.cs new file mode 100644 index 000000000..7ea318ea7 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepDefaultRetry/Function.cs @@ -0,0 +1,68 @@ +// 1-13: Default retry strategy (uses DynamoDB to track attempts) +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepDefaultRetry; + +public class Function +{ + private static readonly AmazonDynamoDBClient DdbClient = new(); + private static readonly string TableName = Environment.GetEnvironmentVariable("ATTEMPTS_TABLE_NAME") ?? "Attempts"; + + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var executionId = context.ExecutionContext.DurableExecutionArn; + + // Step with no explicit retry config — uses SDK default + var result = await context.StepAsync( + async (_, _ct) => + { + var response = await DdbClient.UpdateItemAsync(new UpdateItemRequest + { + TableName = TableName, + Key = new Dictionary + { + ["executionId"] = new AttributeValue { S = executionId } + }, + UpdateExpression = "SET attemptCount = if_not_exists(attemptCount, :zero) + :inc", + ExpressionAttributeValues = new Dictionary + { + [":zero"] = new AttributeValue { N = "0" }, + [":inc"] = new AttributeValue { N = "1" } + }, + ReturnValues = ReturnValue.UPDATED_NEW + }); + + var attemptCount = int.Parse(response.Attributes["attemptCount"].N); + + if (attemptCount < 3) + { + throw new InvalidOperationException($"Attempt {attemptCount} failed"); + } + return "recovered"; + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.Default + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepDefaultRetry/StepDefaultRetry.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepDefaultRetry/StepDefaultRetry.csproj new file mode 100644 index 000000000..f858202a6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepDefaultRetry/StepDefaultRetry.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepErrorCaught/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepErrorCaught/Function.cs new file mode 100644 index 000000000..e43a17ea6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepErrorCaught/Function.cs @@ -0,0 +1,53 @@ +// 1-20: Error caught and handled (try/catch) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepErrorCaught; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + try + { + await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + throw new InvalidOperationException("Something went wrong"); + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.None + }); + } + catch (StepException) + { + // Error caught, continue with fallback + } + + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return "fallback_result"; + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepErrorCaught/StepErrorCaught.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepErrorCaught/StepErrorCaught.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepErrorCaught/StepErrorCaught.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepLogging/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepLogging/Function.cs new file mode 100644 index 000000000..4b1e5f091 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepLogging/Function.cs @@ -0,0 +1,39 @@ +// 1-7: Step with context logger +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; +using Microsoft.Extensions.Logging; + +namespace StepLogging; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.StepAsync( + async (stepContext, _ct) => + { + await Task.CompletedTask; + stepContext.Logger.LogInformation($"Greeting step started for: {input}"); + var greeting = $"Hello, {input}!"; + stepContext.Logger.LogInformation($"Greeting step completed with: {greeting}"); + return greeting; + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepLogging/StepLogging.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepLogging/StepLogging.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepLogging/StepLogging.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNested/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNested/Function.cs new file mode 100644 index 000000000..b86bfc1c3 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNested/Function.cs @@ -0,0 +1,42 @@ +// 1-3: Sequential steps where second depends on first +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepNested; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result1 = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return "first"; + }); + + var result2 = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return $"{result1}_second"; + }); + + return result2; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNested/StepNested.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNested/StepNested.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNested/StepNested.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNullResult/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNullResult/Function.cs new file mode 100644 index 000000000..ce2d51d46 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNullResult/Function.cs @@ -0,0 +1,35 @@ +// 1-5: Undefined/null result +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepNullResult; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return null; + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNullResult/StepNullResult.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNullResult/StepNullResult.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepNullResult/StepNullResult.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplayRethrowsFailed/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplayRethrowsFailed/Function.cs new file mode 100644 index 000000000..b79f72297 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplayRethrowsFailed/Function.cs @@ -0,0 +1,52 @@ +// 1-10: Replay re-throws failed step +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; +using Microsoft.Extensions.Logging; + +namespace StepReplayRethrowsFailed; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + string errorMessage = ""; + + try + { + await context.StepAsync( + async (stepContext, _ct) => + { + await Task.CompletedTask; + stepContext.Logger.LogInformation("step executed"); + throw new InvalidOperationException("Something went wrong"); + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.None + }); + } + catch (StepException ex) + { + errorMessage = ex.Message; + } + + await context.WaitAsync(TimeSpan.FromSeconds(1)); + + return $"caught: {errorMessage}"; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplayRethrowsFailed/StepReplayRethrowsFailed.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplayRethrowsFailed/StepReplayRethrowsFailed.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplayRethrowsFailed/StepReplayRethrowsFailed.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplaySkipsSucceeded/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplaySkipsSucceeded/Function.cs new file mode 100644 index 000000000..770eca1dc --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplaySkipsSucceeded/Function.cs @@ -0,0 +1,39 @@ +// 1-9: Replay skips succeeded step +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; +using Microsoft.Extensions.Logging; + +namespace StepReplaySkipsSucceeded; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.StepAsync( + async (stepContext, _ct) => + { + await Task.CompletedTask; + stepContext.Logger.LogInformation("step executed"); + return "cached_value"; + }); + + await context.WaitAsync(TimeSpan.FromSeconds(1)); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplaySkipsSucceeded/StepReplaySkipsSucceeded.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplaySkipsSucceeded/StepReplaySkipsSucceeded.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepReplaySkipsSucceeded/StepReplaySkipsSucceeded.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryCustomConfig/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryCustomConfig/Function.cs new file mode 100644 index 000000000..1c9e8bf7d --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryCustomConfig/Function.cs @@ -0,0 +1,72 @@ +// 1-14: Retry with custom config (fixed interval and backoff) +// Uses DynamoDB to track attempts across invocations +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepRetryCustomConfig; + +public class Function +{ + private static readonly AmazonDynamoDBClient DdbClient = new(); + private static readonly string TableName = Environment.GetEnvironmentVariable("ATTEMPTS_TABLE_NAME") ?? "Attempts"; + + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var executionId = context.ExecutionContext.DurableExecutionArn; + + var result = await context.StepAsync( + async (_, _ct) => + { + var response = await DdbClient.UpdateItemAsync(new UpdateItemRequest + { + TableName = TableName, + Key = new Dictionary + { + ["executionId"] = new AttributeValue { S = executionId } + }, + UpdateExpression = "SET attemptCount = if_not_exists(attemptCount, :zero) + :inc", + ExpressionAttributeValues = new Dictionary + { + [":zero"] = new AttributeValue { N = "0" }, + [":inc"] = new AttributeValue { N = "1" } + }, + ReturnValues = ReturnValue.UPDATED_NEW + }); + + var attemptCount = int.Parse(response.Attributes["attemptCount"].N); + + if (attemptCount < 3) + { + throw new InvalidOperationException($"Attempt {attemptCount} failed"); + } + return "finally succeeded"; + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.Exponential( + maxAttempts: 5, + initialDelay: TimeSpan.FromSeconds(2), + backoffRate: 3, + jitter: JitterStrategy.None) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryCustomConfig/StepRetryCustomConfig.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryCustomConfig/StepRetryCustomConfig.csproj new file mode 100644 index 000000000..f858202a6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryCustomConfig/StepRetryCustomConfig.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryExhaustion/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryExhaustion/Function.cs new file mode 100644 index 000000000..b9b358d89 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryExhaustion/Function.cs @@ -0,0 +1,43 @@ +// 1-12: Retry exhaustion (max attempts) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepRetryExhaustion; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + throw new InvalidOperationException("Always fails"); + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.Exponential( + maxAttempts: 4, + initialDelay: TimeSpan.FromSeconds(1), + backoffRate: 1, + jitter: JitterStrategy.None) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryExhaustion/StepRetryExhaustion.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryExhaustion/StepRetryExhaustion.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryExhaustion/StepRetryExhaustion.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryNonRetryable/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryNonRetryable/Function.cs new file mode 100644 index 000000000..16e0fa427 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryNonRetryable/Function.cs @@ -0,0 +1,50 @@ +// 1-16: Retry specific exception (non-retryable fails) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepRetryNonRetryable; + +public class TransientError : Exception +{ + public TransientError(string message) : base(message) { } +} + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + throw new TransientError("Temporary failure"); + }, + config: new StepConfig + { + // Only retry ArgumentException, not TransientError + RetryStrategy = RetryStrategy.FromDelegate((error, attempts) => + { + if (error is ArgumentException && attempts < 3) + return RetryDecision.RetryAfter(TimeSpan.FromSeconds(1)); + return RetryDecision.DoNotRetry(); + }) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryNonRetryable/StepRetryNonRetryable.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryNonRetryable/StepRetryNonRetryable.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetryNonRetryable/StepRetryNonRetryable.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetrySpecificException/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetrySpecificException/Function.cs new file mode 100644 index 000000000..2eb2554fc --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetrySpecificException/Function.cs @@ -0,0 +1,77 @@ +// 1-15: Retry specific exception (uses DynamoDB to track attempts) +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepRetrySpecificException; + +public class TransientError : Exception +{ + public TransientError(string message) : base(message) { } +} + +public class Function +{ + private static readonly AmazonDynamoDBClient DdbClient = new(); + private static readonly string TableName = Environment.GetEnvironmentVariable("ATTEMPTS_TABLE_NAME") ?? "Attempts"; + + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var executionId = context.ExecutionContext.DurableExecutionArn; + + var result = await context.StepAsync( + async (_, _ct) => + { + var response = await DdbClient.UpdateItemAsync(new UpdateItemRequest + { + TableName = TableName, + Key = new Dictionary + { + ["executionId"] = new AttributeValue { S = executionId } + }, + UpdateExpression = "SET attemptCount = if_not_exists(attemptCount, :zero) + :inc", + ExpressionAttributeValues = new Dictionary + { + [":zero"] = new AttributeValue { N = "0" }, + [":inc"] = new AttributeValue { N = "1" } + }, + ReturnValues = ReturnValue.UPDATED_NEW + }); + + var attemptCount = int.Parse(response.Attributes["attemptCount"].N); + + if (attemptCount < 2) + { + throw new TransientError("Temporary failure"); + } + return "recovered from transient"; + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.FromDelegate((error, attempts) => + { + if (error is TransientError && attempts < 3) + return RetryDecision.RetryAfter(TimeSpan.FromSeconds(1)); + return RetryDecision.DoNotRetry(); + }) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetrySpecificException/StepRetrySpecificException.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetrySpecificException/StepRetrySpecificException.csproj new file mode 100644 index 000000000..f858202a6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepRetrySpecificException/StepRetrySpecificException.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithError/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithError/Function.cs new file mode 100644 index 000000000..60b1f5a42 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithError/Function.cs @@ -0,0 +1,39 @@ +// 1-19: Step with error (fails permanently) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepWithError; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + throw new InvalidOperationException("Something went wrong"); + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.None + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithError/StepWithError.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithError/StepWithError.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithError/StepWithError.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithName/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithName/Function.cs new file mode 100644 index 000000000..609de396d --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithName/Function.cs @@ -0,0 +1,36 @@ +// 1-2: Step with explicit name parameter +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepWithName; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return $"Hello, {input}!"; + }, + name: "custom_step_name"); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithName/StepWithName.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithName/StepWithName.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithName/StepWithName.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithRetry/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithRetry/Function.cs new file mode 100644 index 000000000..c5553bc79 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithRetry/Function.cs @@ -0,0 +1,73 @@ +// 1-11: Step with retry (fails then succeeds) +// Uses DynamoDB to track attempts across invocations +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace StepWithRetry; + +public class Function +{ + private static readonly AmazonDynamoDBClient DdbClient = new(); + private static readonly string TableName = Environment.GetEnvironmentVariable("ATTEMPTS_TABLE_NAME") ?? "Attempts"; + + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var executionId = context.ExecutionContext.DurableExecutionArn; + + var result = await context.StepAsync( + async (_, _ct) => + { + var response = await DdbClient.UpdateItemAsync(new UpdateItemRequest + { + TableName = TableName, + Key = new Dictionary + { + ["executionId"] = new AttributeValue { S = executionId } + }, + UpdateExpression = "SET attemptCount = if_not_exists(attemptCount, :zero) + :inc", + ExpressionAttributeValues = new Dictionary + { + [":zero"] = new AttributeValue { N = "0" }, + [":inc"] = new AttributeValue { N = "1" } + }, + ReturnValues = ReturnValue.UPDATED_NEW + }); + + var attemptCount = int.Parse(response.Attributes["attemptCount"].N); + + if (attemptCount < 2) + { + throw new InvalidOperationException($"Attempt {attemptCount} failed"); + } + return "Operation succeeded"; + }, + config: new StepConfig + { + RetryStrategy = RetryStrategy.FromDelegate((error, attempts) => + { + if (attempts >= 3) + return RetryDecision.DoNotRetry(); + return RetryDecision.RetryAfter(TimeSpan.FromSeconds(1)); + }) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithRetry/StepWithRetry.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithRetry/StepWithRetry.csproj new file mode 100644 index 000000000..f858202a6 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/step/StepWithRetry/StepWithRetry.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_callback.yaml b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_callback.yaml new file mode 100644 index 000000000..c12ca348a --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_callback.yaml @@ -0,0 +1,375 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - .NET (Callback) +Globals: + Function: + Runtime: dotnet8 + Timeout: 60 + MemorySize: 512 + + +Resources: + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + Resource: '*' + + CallbackBasic: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-1"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackBasic/ + Handler: bootstrap + Description: Create callback basic + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackWithName: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-2"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackWithName/ + Handler: bootstrap + Description: Create callback with explicit name + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackTimeout: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-3"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackTimeout/ + Handler: bootstrap + Description: Create callback timeout + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackHeartbeatTimeout: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-4"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackHeartbeatTimeout/ + Handler: bootstrap + Description: Create callback heartbeat timeout + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackHeartbeatAlive: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-5"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackHeartbeatAlive/ + Handler: bootstrap + Description: Heartbeat keeps callback alive + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackFailure: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-6"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackFailure/ + Handler: bootstrap + Description: Callback failure + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackThenStep: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-7"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackThenStep/ + Handler: bootstrap + Description: CreateCallback then step then await + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackTimeoutAfterStep: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-8"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackTimeoutAfterStep/ + Handler: bootstrap + Description: CreateCallback (5s timeout) then step then await - times out + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackAfterWait: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-9"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackAfterWait/ + Handler: bootstrap + Description: CreateCallback then wait then await callback + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackDuringWait: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-10"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackDuringWait/ + Handler: bootstrap + Description: CreateCallback then 5s wait then await + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackTimeoutAfterWait: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-11"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackTimeoutAfterWait/ + Handler: bootstrap + Description: CreateCallback (3s timeout) then 6s wait then await + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackResolvesFirst: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-12"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackResolvesFirst/ + Handler: bootstrap + Description: Callback resolves first, then wait, then return + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackFailureCaught: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-13"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackFailureCaught/ + Handler: bootstrap + Description: Catch callback failure and continue + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackTimeoutCaught: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-14"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackTimeoutCaught/ + Handler: bootstrap + Description: Catch callback timeout and continue + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackCustomSerdes: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-15"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackCustomSerdes/ + Handler: bootstrap + Description: Custom serdes (JSON object) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackCustomSerdesNumber: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-16"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackCustomSerdesNumber/ + Handler: bootstrap + Description: Custom serdes (string to number) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackSequential: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-17"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackSequential/ + Handler: bootstrap + Description: Sequential callbacks (A then B) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackConcurrent: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-18"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackConcurrent/ + Handler: bootstrap + Description: Concurrent callbacks (create A, create B, await A, await B) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + CallbackConcurrentReversed: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["4-19"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/CallbackConcurrentReversed/ + Handler: bootstrap + Description: Concurrent callbacks reversed (create A, create B, await B, await A) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_child.yaml b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_child.yaml new file mode 100644 index 000000000..4bb91c2db --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_child.yaml @@ -0,0 +1,374 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - .NET (Child) +Globals: + Function: + Runtime: dotnet8 + Timeout: 60 + MemorySize: 512 + + +Resources: + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + Resource: '*' + - PolicyName: DynamoDBPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + Resource: + Fn::GetAtt: + - AttemptsTable + - Arn + + AttemptsTable: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - AttributeName: executionId + AttributeType: S + KeySchema: + - AttributeName: executionId + KeyType: HASH + BillingMode: PAY_PER_REQUEST + + ChildBasic: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-1"] + NotImplemented: + - id: "3-14" + reason: "Child context with custom serdes: .NET has no per-operation serdes slot; all payloads use the one registered ILambdaSerializer (matches the Java approach)." + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildBasic/ + Handler: bootstrap + Description: Child context basic - single step inside child context + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildWithName: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-2"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildWithName/ + Handler: bootstrap + Description: Child context with name - named child context + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildMultipleSteps: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-3"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildMultipleSteps/ + Handler: bootstrap + Description: Child context with multiple sequential steps + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildError: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-4"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildError/ + Handler: bootstrap + Description: Child context error - step inside child throws + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildErrorCaught: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-5"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildErrorCaught/ + Handler: bootstrap + Description: Child context error caught - recovery step returns input + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildNested: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-6"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildNested/ + Handler: bootstrap + Description: Nested child contexts - outer and inner child with steps + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildStepRetry: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-7"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildStepRetry/ + Handler: bootstrap + Description: Child context with step retry (fails then succeeds) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + ATTEMPTS_TABLE_NAME: + Ref: AttemptsTable + + ChildStepRetryExhaustion: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-8"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildStepRetryExhaustion/ + Handler: bootstrap + Description: Child context with step retry exhaustion + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildReplay: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-9"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildReplay/ + Handler: bootstrap + Description: Child context replay (cached result) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildStepAndWait: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-10"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildStepAndWait/ + Handler: bootstrap + Description: Child context with step and wait inside + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildLargePayload: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-11"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildLargePayload/ + Handler: bootstrap + Description: Child context large payload (ReplayChildren mode) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildInterrupted: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-12"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildInterrupted/ + Handler: bootstrap + Description: Child context interrupted and re-executed + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + ATTEMPTS_TABLE_NAME: + Ref: AttemptsTable + + ChildWaitReplay: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-13"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildWaitReplay/ + Handler: bootstrap + Description: Child context with wait inside - verify replay + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildErrorNoStep: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-15"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildErrorNoStep/ + Handler: bootstrap + Description: Child context error without step + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildReturnsNull: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-16"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildReturnsNull/ + Handler: bootstrap + Description: Child context returning null + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildPrintOnly: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-17"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildPrintOnly/ + Handler: bootstrap + Description: Child context with print only (no durable operations) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ChildStepWaitAfter: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["3-18"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ChildStepWaitAfter/ + Handler: bootstrap + Description: Child context with step and wait inside, step and wait after + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_invoke.yaml b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_invoke.yaml new file mode 100644 index 000000000..747dbe078 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_invoke.yaml @@ -0,0 +1,417 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - .NET (Invoke) +Globals: + Function: + Runtime: dotnet8 + Timeout: 60 + MemorySize: 512 + + +Resources: + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + - lambda:InvokeFunction + Resource: '*' + + InvokeEchoTarget: + Type: AWS::Serverless::Function + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeEchoTarget/ + Handler: bootstrap + Description: Echo target function - returns whatever input it receives + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + InvokeFailTarget: + Type: AWS::Serverless::Function + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeFailTarget/ + Handler: bootstrap + Description: Fail target function - always throws an error + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + InvokeBasic: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-1"] + NotImplemented: + - id: "5-16" + reason: "Invoke with custom result serdes: .NET has no per-operation serdes slot; the invoke result is deserialized via the one registered ILambdaSerializer (matches the Java approach)." + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeBasic/ + Handler: bootstrap + Description: Invoke basic (target function succeeds) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeWithName: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-2"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeWithName/ + Handler: bootstrap + Description: Invoke with explicit name parameter + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeComplexObject: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-3"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeComplexObject/ + Handler: bootstrap + Description: Invoke returning complex object (nested JSON) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeNull: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-4"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeNull/ + Handler: bootstrap + Description: Invoke returning null (target echoes null input) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeTargetFails: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-5"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeTargetFails/ + Handler: bootstrap + Description: Invoke target fails (execution fails with InvokeError) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FAIL_FUNCTION_NAME: + !Sub "${InvokeFailTarget.Arn}:$LATEST" + + InvokeTargetFailsCaught: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-6"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeTargetFailsCaught/ + Handler: bootstrap + Description: Invoke target fails, caught (try/catch, execution succeeds) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FAIL_FUNCTION_NAME: + !Sub "${InvokeFailTarget.Arn}:$LATEST" + + InvokeLargePayload: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-7"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeLargePayload/ + Handler: bootstrap + Description: Invoke large payload (payload near size limit) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeEchoTargetTenant: + Type: AWS::Serverless::Function + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeEchoTargetTenant/ + Handler: bootstrap + Description: Echo target function with tenancy enabled + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + TenancyConfig: + TenantIsolationMode: PER_TENANT + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + InvokeWithTenantId: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-8"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeWithTenantId/ + Handler: bootstrap + Description: Invoke with tenantId (tenant-isolated invocation) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTargetTenant.Arn}:$LATEST" + + InvokeReplaySkips: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-9"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeReplaySkips/ + Handler: bootstrap + Description: Invoke replay skips (invoke result cached on replay) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeReplayRethrows: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-10"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeReplayRethrows/ + Handler: bootstrap + Description: Invoke replay re-throws (failed invoke error re-thrown from cache) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FAIL_FUNCTION_NAME: + !Sub "${InvokeFailTarget.Arn}:$LATEST" + + StepThenInvoke: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-11"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepThenInvoke/ + Handler: bootstrap + Description: Step then invoke (sequential operations) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeThenStep: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-12"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeThenStep/ + Handler: bootstrap + Description: Invoke then step (invoke result used by subsequent step) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeInChildContext: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-13"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeInChildContext/ + Handler: bootstrap + Description: Invoke inside child context + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeSequential: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-14"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeSequential/ + Handler: bootstrap + Description: Multiple sequential invokes + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" + + InvokeCustomPayloadSerdes: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["5-15"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/InvokeCustomPayloadSerdes/ + Handler: bootstrap + Description: Invoke with custom payload serdes (uppercases outgoing payload) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + TARGET_FUNCTION_NAME: + !Sub "${InvokeEchoTarget.Arn}:$LATEST" diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_map.yaml b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_map.yaml new file mode 100644 index 000000000..2c74104f3 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_map.yaml @@ -0,0 +1,351 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - .NET (Map) +Globals: + Function: + Runtime: dotnet8 + Timeout: 60 + MemorySize: 512 + +# Coverage note: .NET's MapConfig exposes MaxConcurrency, CompletionConfig, +# NestingType, and ItemNamer, but no item-level or whole-result serdes slots +# (per-item checkpoint payloads use the registered ILambdaSerializer). Tests +# 9-14 (per-item serdes), 9-19 and 9-20 (operation-level serdes) therefore have +# no .NET example and are left uncovered by design, matching the Java approach. + +Resources: + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + Resource: '*' + + MapBasic: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-1"] + NotImplemented: + - id: "9-14" + reason: "Per-item serdes: .NET MapConfig has no item-level serializer slot; all payloads use the one registered ILambdaSerializer (matches the Java approach)." + - id: "9-19" + reason: "Operation-level serdes: .NET MapConfig has no whole-result serializer slot; all payloads use the one registered ILambdaSerializer." + - id: "9-20" + reason: "Operation-level serdes across replay: same gap as 9-19 — no whole-result serializer slot in .NET MapConfig." + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapBasic/ + Handler: bootstrap + Description: Map basic (one step per item, all succeed) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapItemsOnly: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-2"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapItemsOnly/ + Handler: bootstrap + Description: Map items-only form (no operation name) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapItemIndex: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-3"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapItemIndex/ + Handler: bootstrap + Description: Map function receives item and index + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapEmpty: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-4"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapEmpty/ + Handler: bootstrap + Description: Map with an empty items list + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapFailFast: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-5"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapFailFast/ + Handler: bootstrap + Description: Map fail-fast (tolerated-failure-count=0) stops after first failure + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapThrowIfError: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-6"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapThrowIfError/ + Handler: bootstrap + Description: Map throw-if-error propagates an item failure to the execution + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapMinSuccessful: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-7"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapMinSuccessful/ + Handler: bootstrap + Description: Map min-successful early completion + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapToleratedWithin: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-8"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapToleratedWithin/ + Handler: bootstrap + Description: Map tolerated-failure-count within tolerance (all items complete) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapToleratedExceeded: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-9"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapToleratedExceeded/ + Handler: bootstrap + Description: Map tolerated-failure-count exceeded (stops early) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapToleratedPct: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-10"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapToleratedPct/ + Handler: bootstrap + Description: Map tolerated-failure-percentage exceeded (stops early) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapConcurrent: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-11"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapConcurrent/ + Handler: bootstrap + Description: Map real concurrency preserves index-ordered results + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapFlat: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-12"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapFlat/ + Handler: bootstrap + Description: Map with FLAT nesting (virtual iteration contexts) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapItemNamer: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-13"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapItemNamer/ + Handler: bootstrap + Description: Map with a custom item namer + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapSuspendIteration: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-15"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapSuspendIteration/ + Handler: bootstrap + Description: Map suspends inside an iteration; replay skips the completed iteration + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapLargeResult: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-16"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapLargeResult/ + Handler: bootstrap + Description: Map with a large aggregate result (exceeds checkpoint size threshold) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapThenWait: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-17"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapThenWait/ + Handler: bootstrap + Description: Suspension after a successful map (replay skips the completed map) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + MapFailThenWait: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["9-18"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/MapFailThenWait/ + Handler: bootstrap + Description: Suspension after a map that completed with a failure + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_parallel.yaml b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_parallel.yaml new file mode 100644 index 000000000..589382ca8 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_parallel.yaml @@ -0,0 +1,414 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - .NET (Parallel) +Globals: + Function: + Runtime: dotnet8 + Timeout: 60 + MemorySize: 512 + + +Resources: + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + Resource: '*' + + ParallelBasic: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-1"] + NotImplemented: + - id: "8-15" + reason: "Parallel with custom per-item serdes: .NET ParallelConfig has no item serializer slot; all branch payloads use the one registered ILambdaSerializer (matches the Java approach)." + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelBasic/ + Handler: bootstrap + Description: Parallel basic with steps in branches + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelBranchesOnly: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-2"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelBranchesOnly/ + Handler: bootstrap + Description: Parallel branches only (no inner steps) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelNamedBranches: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-3"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelNamedBranches/ + Handler: bootstrap + Description: Parallel with named branches using DurableBranch + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelHeterogeneous: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-4"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelHeterogeneous/ + Handler: bootstrap + Description: Parallel with heterogeneous return types + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelEmpty: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-5"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelEmpty/ + Handler: bootstrap + Description: Parallel with empty branches list + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelFailFast: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-6"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelFailFast/ + Handler: bootstrap + Description: Parallel fail-fast with ToleratedFailureCount=0 + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelRethrow: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-7"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelRethrow/ + Handler: bootstrap + Description: Parallel rethrow (ThrowIfError propagates failure) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelMinSuccessful: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-8"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelMinSuccessful/ + Handler: bootstrap + Description: Parallel with MinSuccessful completion config + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelToleratedFailure: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-9"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelToleratedFailure/ + Handler: bootstrap + Description: Parallel with ToleratedFailureCount=1 (one failure tolerated) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelFailureExceedsTolerance: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-10"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelFailureExceedsTolerance/ + Handler: bootstrap + Description: Parallel where failures exceed tolerance + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelConcurrent: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-11"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelConcurrent/ + Handler: bootstrap + Description: Parallel with maxConcurrency=2 + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelFlat: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-12"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelFlat/ + Handler: bootstrap + Description: Parallel with flat nesting type + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelFailurePercentage: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-13"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelFailurePercentage/ + Handler: bootstrap + Description: Parallel with ToleratedFailurePercentage exceeded + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelWithWait: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-14"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelWithWait/ + Handler: bootstrap + Description: Parallel with WaitAsync in a branch + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelAllFail: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-16"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelAllFail/ + Handler: bootstrap + Description: Parallel where all branches fail + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelMinNotReached: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-17"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelMinNotReached/ + Handler: bootstrap + Description: Parallel where MinSuccessful is not reached + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelCombinedConfig: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-18"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelCombinedConfig/ + Handler: bootstrap + Description: Parallel with combined MinSuccessful and ToleratedFailureCount + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelBadConcurrency: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-19"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelBadConcurrency/ + Handler: bootstrap + Description: Parallel with invalid MaxConcurrency=0 (throws) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelAccessors: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-20"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelAccessors/ + Handler: bootstrap + Description: Parallel result accessors (HasFailure, Succeeded, Failed, GetErrors) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelNested: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-21"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelNested/ + Handler: bootstrap + Description: Nested parallel (outer parallel contains inner parallel) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + ParallelFailurePercentageExact: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["8-22"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/ParallelFailurePercentageExact/ + Handler: bootstrap + Description: Parallel with ToleratedFailurePercentage at exact threshold (not exceeded) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_step.yaml b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_step.yaml new file mode 100644 index 000000000..e94ee26d2 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_step.yaml @@ -0,0 +1,452 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - .NET (Step) + +# Each function is published ahead of time into publish// by +# build_examples.sh, then SAM copies the pre-built bootstrap into the artifact +# directory via the makefile BuildMethod. Functions run on the dotnet8 managed +# runtime with Handler=bootstrap (executable model). +# +# TestingMetadata.TestDescription maps each function to the conformance +# requirement id(s) it exercises. The runner reads this block, loads the +# matching test-requirements/step/.yaml, deploys + invokes the function, +# and validates the durable execution result and history. + +Globals: + Function: + Runtime: dotnet8 + Timeout: 60 + MemorySize: 512 + +Resources: + # Self-created execution role for local runs. CI replaces this with a + # pre-existing role via scripts/inject_execution_role.py. + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + Resource: '*' + - PolicyName: DynamoDBPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + Resource: + Fn::GetAtt: + - AttemptsTable + - Arn + + # Cross-invocation attempt counter used by the retry tests. Retry semantics + # require observing "attempt N" across separate Lambda invocations, which the + # replay model cannot track in-memory — a durable store is required. + AttemptsTable: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - AttributeName: executionId + AttributeType: S + KeySchema: + - AttributeName: executionId + KeyType: HASH + BillingMode: PAY_PER_REQUEST + + StepBasic: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-1"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepBasic/ + Handler: bootstrap + Description: Step basic (succeeds on first attempt) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepWithName: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-2"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepWithName/ + Handler: bootstrap + Description: Step with explicit name parameter + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepNested: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-3"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepNested/ + Handler: bootstrap + Description: Sequential steps where second depends on first + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepComplexObject: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-4"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepComplexObject/ + Handler: bootstrap + Description: Returning complex object with nested structure + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepNullResult: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-5"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepNullResult/ + Handler: bootstrap + Description: Undefined/null result + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepCustomSerdes: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-6"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepCustomSerdes/ + Handler: bootstrap + Description: Custom serdes (per-step) transforms to uppercase + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepLogging: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-7"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepLogging/ + Handler: bootstrap + Description: Step with context logger + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepAndWaitReplay: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-8"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepAndWaitReplay/ + Handler: bootstrap + Description: Step and wait with replay + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepReplaySkipsSucceeded: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-9"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepReplaySkipsSucceeded/ + Handler: bootstrap + Description: Replay skips succeeded step + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepReplayRethrowsFailed: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-10"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepReplayRethrowsFailed/ + Handler: bootstrap + Description: Replay re-throws failed step + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepWithRetry: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-11"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepWithRetry/ + Handler: bootstrap + Description: Step with retry (fails then succeeds) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + ATTEMPTS_TABLE_NAME: + Ref: AttemptsTable + + StepRetryExhaustion: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-12"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepRetryExhaustion/ + Handler: bootstrap + Description: Retry exhaustion (max attempts) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepDefaultRetry: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-13"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepDefaultRetry/ + Handler: bootstrap + Description: Default retry strategy + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + ATTEMPTS_TABLE_NAME: + Ref: AttemptsTable + + StepRetryCustomConfig: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-14"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepRetryCustomConfig/ + Handler: bootstrap + Description: Retry with custom config (fixed interval and backoff) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + ATTEMPTS_TABLE_NAME: + Ref: AttemptsTable + + StepRetrySpecificException: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-15"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepRetrySpecificException/ + Handler: bootstrap + Description: Retry specific exception + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + ATTEMPTS_TABLE_NAME: + Ref: AttemptsTable + + StepRetryNonRetryable: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-16"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepRetryNonRetryable/ + Handler: bootstrap + Description: Retry specific exception (non-retryable fails) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepAtMostOnceNoRetry: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-17"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepAtMostOnceNoRetry/ + Handler: bootstrap + Description: Step with AtMostOncePerRetry semantics (interrupted, no retry) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepAtMostOnceWithRetry: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-18"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepAtMostOnceWithRetry/ + Handler: bootstrap + Description: AtMostOnce interrupted (with retry, succeeds on second attempt) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + Environment: + Variables: + ATTEMPTS_TABLE_NAME: + Ref: AttemptsTable + + StepWithError: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-19"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepWithError/ + Handler: bootstrap + Description: Step with error (fails permanently) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + StepErrorCaught: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["1-20"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/StepErrorCaught/ + Handler: bootstrap + Description: Error caught and handled (try/catch) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait.yaml b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait.yaml new file mode 100644 index 000000000..0069524cb --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait.yaml @@ -0,0 +1,123 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - .NET (Wait) +Globals: + Function: + Runtime: dotnet8 + Timeout: 60 + MemorySize: 512 + + +Resources: + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + Resource: '*' + + WaitBasic: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["2-1"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitBasic/ + Handler: bootstrap + Description: Wait basic (single wait, 2 seconds) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitWithName: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["2-2"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitWithName/ + Handler: bootstrap + Description: Wait with explicit name parameter + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitMultipleSequential: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["2-3"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitMultipleSequential/ + Handler: bootstrap + Description: Multiple sequential waits (two waits, each 2 seconds) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitMinutesDuration: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["2-4"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitMinutesDuration/ + Handler: bootstrap + Description: Wait with different duration units (1 minute) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitLongDuration: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["2-5"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitLongDuration/ + Handler: bootstrap + Description: Wait with long duration (1 hour) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait_for_callback.yaml b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait_for_callback.yaml new file mode 100644 index 000000000..d9e3997a8 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait_for_callback.yaml @@ -0,0 +1,303 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - .NET (WaitForCallback) +Globals: + Function: + Runtime: dotnet8 + Timeout: 60 + MemorySize: 512 + + +Resources: + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + Resource: '*' + + WaitForCallbackBasic: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-1"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackBasic/ + Handler: bootstrap + Description: WaitForCallback basic success via external callback + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackWithName: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-2"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackWithName/ + Handler: bootstrap + Description: WaitForCallback with explicit name + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackNoName: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-3"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackNoName/ + Handler: bootstrap + Description: WaitForCallback with no name (anonymous) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackFailure: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-4"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackFailure/ + Handler: bootstrap + Description: WaitForCallback external failure uncaught + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackTimeout: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-5"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackTimeout/ + Handler: bootstrap + Description: WaitForCallback timeout uncaught + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackFailureCaught: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-6"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackFailureCaught/ + Handler: bootstrap + Description: WaitForCallback failure caught and recovered + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackSubmitterRetry: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-7"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackSubmitterRetry/ + Handler: bootstrap + Description: WaitForCallback submitter retry exhaustion + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackInChild: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-8"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackInChild/ + Handler: bootstrap + Description: WaitForCallback inside a child context + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackSequential: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-9"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackSequential/ + Handler: bootstrap + Description: WaitForCallback sequential (two in sequence) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackAfterWait: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-10"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackAfterWait/ + Handler: bootstrap + Description: WaitForCallback after wait and step + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackComplexResult: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-11"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackComplexResult/ + Handler: bootstrap + Description: WaitForCallback with complex JSON result + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackHeartbeatTimeout: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-12"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackHeartbeatTimeout/ + Handler: bootstrap + Description: WaitForCallback heartbeat timeout uncaught + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackHeartbeatAlive: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-13"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackHeartbeatAlive/ + Handler: bootstrap + Description: WaitForCallback heartbeat keeps callback alive + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackTimeoutCaught: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-14"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackTimeoutCaught/ + Handler: bootstrap + Description: WaitForCallback timeout caught and handled + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForCallbackNullResult: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["7-15"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForCallbackNullResult/ + Handler: bootstrap + Description: WaitForCallback success with null payload + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait_for_condition.yaml b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait_for_condition.yaml new file mode 100644 index 000000000..c82b480e1 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/template_wait_for_condition.yaml @@ -0,0 +1,267 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - .NET (WaitForCondition) +Globals: + Function: + Runtime: dotnet8 + Timeout: 60 + MemorySize: 512 + + +Resources: + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + Resource: '*' + + WaitForConditionBasic: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-1"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionBasic/ + Handler: bootstrap + Description: Wait-for-condition basic (polls until threshold met) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionImmediate: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-2"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionImmediate/ + Handler: bootstrap + Description: Wait-for-condition immediate stop (condition already met) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionWithName: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-3"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionWithName/ + Handler: bootstrap + Description: Wait-for-condition with explicit name parameter + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionCustomInitialState: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-4"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionCustomInitialState/ + Handler: bootstrap + Description: Wait-for-condition with custom initial state + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionFixedDelay: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-5"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionFixedDelay/ + Handler: bootstrap + Description: Wait-for-condition with fixed delay strategy + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionMaxAttempts: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-6"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionMaxAttempts/ + Handler: bootstrap + Description: Wait-for-condition max attempts exceeded (failure) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionCheckThrows: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-7"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionCheckThrows/ + Handler: bootstrap + Description: Wait-for-condition check function throws (uncaught failure) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionCheckThrowsCaught: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-8"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionCheckThrowsCaught/ + Handler: bootstrap + Description: Wait-for-condition check throws caught (recovers) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionComplexObject: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-9"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionComplexObject/ + Handler: bootstrap + Description: Wait-for-condition with complex object state + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionNullResult: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-10"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionNullResult/ + Handler: bootstrap + Description: Wait-for-condition with null result + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionCustomSerdes: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-11"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionCustomSerdes/ + Handler: bootstrap + Description: Wait-for-condition custom serdes (state survives serialization) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionThenStep: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-12"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionThenStep/ + Handler: bootstrap + Description: Wait-for-condition followed by a step + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + WaitForConditionMultipleSequential: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["6-13"] + Metadata: + BuildMethod: makefile + Properties: + CodeUri: publish/WaitForConditionMultipleSequential/ + Handler: bootstrap + Description: Multiple sequential wait-for-condition operations + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitBasic/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitBasic/Function.cs new file mode 100644 index 000000000..e0048e5e3 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitBasic/Function.cs @@ -0,0 +1,29 @@ +// 2-1: Wait basic +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitBasic; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + await context.WaitAsync(TimeSpan.FromSeconds(2)); + return null; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitBasic/WaitBasic.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitBasic/WaitBasic.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitBasic/WaitBasic.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitLongDuration/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitLongDuration/Function.cs new file mode 100644 index 000000000..c705f407b --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitLongDuration/Function.cs @@ -0,0 +1,29 @@ +// 2-5: Wait with long duration (1 hour) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitLongDuration; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + await context.WaitAsync(TimeSpan.FromHours(1)); + return null; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitLongDuration/WaitLongDuration.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitLongDuration/WaitLongDuration.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitLongDuration/WaitLongDuration.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMinutesDuration/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMinutesDuration/Function.cs new file mode 100644 index 000000000..46da14e12 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMinutesDuration/Function.cs @@ -0,0 +1,29 @@ +// 2-4: Wait with different duration units (minutes) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitMinutesDuration; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + await context.WaitAsync(TimeSpan.FromMinutes(1)); + return null; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMinutesDuration/WaitMinutesDuration.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMinutesDuration/WaitMinutesDuration.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMinutesDuration/WaitMinutesDuration.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMultipleSequential/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMultipleSequential/Function.cs new file mode 100644 index 000000000..18c256375 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMultipleSequential/Function.cs @@ -0,0 +1,37 @@ +// 2-3: Multiple sequential waits +using System.Text.Json.Serialization; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitMultipleSequential; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + await context.WaitAsync(TimeSpan.FromSeconds(2), name: "wait-1"); + await context.WaitAsync(TimeSpan.FromSeconds(2), name: "wait-2"); + return new WaitResult { CompletedWaits = 2 }; + } +} + +public class WaitResult +{ + [JsonPropertyName("completedWaits")] + public int CompletedWaits { get; set; } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMultipleSequential/WaitMultipleSequential.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMultipleSequential/WaitMultipleSequential.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitMultipleSequential/WaitMultipleSequential.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitWithName/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitWithName/Function.cs new file mode 100644 index 000000000..174c5654f --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitWithName/Function.cs @@ -0,0 +1,29 @@ +// 2-2: Wait with name +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitWithName; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + await context.WaitAsync(TimeSpan.FromSeconds(2), name: "custom_wait_name"); + return null; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitWithName/WaitWithName.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitWithName/WaitWithName.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait/WaitWithName/WaitWithName.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackAfterWait/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackAfterWait/Function.cs new file mode 100644 index 000000000..824582b7c --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackAfterWait/Function.cs @@ -0,0 +1,44 @@ +// 7-10: WaitForCallback after wait and step +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackAfterWait; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + await context.WaitAsync(TimeSpan.FromSeconds(1)); + + await context.StepAsync( + async (_, _ct) => + { + await Task.CompletedTask; + return "step-data"; + }); + + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackAfterWait/WaitForCallbackAfterWait.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackAfterWait/WaitForCallbackAfterWait.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackAfterWait/WaitForCallbackAfterWait.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackBasic/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackBasic/Function.cs new file mode 100644 index 000000000..f96ce7f64 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackBasic/Function.cs @@ -0,0 +1,36 @@ +// 7-1: WaitForCallback basic (success via external callback) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackBasic; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + // Submitter receives callbackId; does nothing durable. + await Task.CompletedTask; + }, + name: input); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackBasic/WaitForCallbackBasic.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackBasic/WaitForCallbackBasic.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackBasic/WaitForCallbackBasic.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackComplexResult/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackComplexResult/Function.cs new file mode 100644 index 000000000..073798937 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackComplexResult/Function.cs @@ -0,0 +1,42 @@ +// 7-11: WaitForCallback with complex (JSON object) result +using System.Text.Json.Serialization; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackComplexResult; + +public class ApprovalResult +{ + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; +} + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input); + + return result.Status; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackComplexResult/WaitForCallbackComplexResult.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackComplexResult/WaitForCallbackComplexResult.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackComplexResult/WaitForCallbackComplexResult.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailure/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailure/Function.cs new file mode 100644 index 000000000..0b4fea7fe --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailure/Function.cs @@ -0,0 +1,36 @@ +// 7-4: WaitForCallback external failure (uncaught) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackFailure; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + // External system sends failure; do NOT catch. + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailure/WaitForCallbackFailure.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailure/WaitForCallbackFailure.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailure/WaitForCallbackFailure.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailureCaught/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailureCaught/Function.cs new file mode 100644 index 000000000..08b3f7bb4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailureCaught/Function.cs @@ -0,0 +1,42 @@ +// 7-6: WaitForCallback failure caught and recovered +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackFailureCaught; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + try + { + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input); + + return result; + } + catch (CallbackFailedException) + { + return "recovered"; + } + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailureCaught/WaitForCallbackFailureCaught.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailureCaught/WaitForCallbackFailureCaught.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackFailureCaught/WaitForCallbackFailureCaught.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatAlive/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatAlive/Function.cs new file mode 100644 index 000000000..cd1fff319 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatAlive/Function.cs @@ -0,0 +1,37 @@ +// 7-13: WaitForCallback heartbeat keeps callback alive +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackHeartbeatAlive; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + // 10-second heartbeat timeout; external sends heartbeat then success. + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input, + config: new WaitForCallbackConfig { HeartbeatTimeout = TimeSpan.FromSeconds(10) }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatAlive/WaitForCallbackHeartbeatAlive.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatAlive/WaitForCallbackHeartbeatAlive.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatAlive/WaitForCallbackHeartbeatAlive.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatTimeout/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatTimeout/Function.cs new file mode 100644 index 000000000..8ad6894cc --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatTimeout/Function.cs @@ -0,0 +1,37 @@ +// 7-12: WaitForCallback heartbeat timeout (uncaught) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackHeartbeatTimeout; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + // 5-second heartbeat timeout; no heartbeat sent. Do NOT catch. + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input, + config: new WaitForCallbackConfig { HeartbeatTimeout = TimeSpan.FromSeconds(5) }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatTimeout/WaitForCallbackHeartbeatTimeout.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatTimeout/WaitForCallbackHeartbeatTimeout.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackHeartbeatTimeout/WaitForCallbackHeartbeatTimeout.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackInChild/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackInChild/Function.cs new file mode 100644 index 000000000..79f046a69 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackInChild/Function.cs @@ -0,0 +1,40 @@ +// 7-8: WaitForCallback inside a child context +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackInChild; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.RunInChildContextAsync(async (childContext, _ct) => + { + var callbackResult = await childContext.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input); + + return callbackResult; + }, name: "wrapper", config: new ChildContextConfig { SubType = "RunInChildContext" }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackInChild/WaitForCallbackInChild.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackInChild/WaitForCallbackInChild.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackInChild/WaitForCallbackInChild.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNoName/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNoName/Function.cs new file mode 100644 index 000000000..22af864a3 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNoName/Function.cs @@ -0,0 +1,34 @@ +// 7-3: WaitForCallback with no name (anonymous) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackNoName; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNoName/WaitForCallbackNoName.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNoName/WaitForCallbackNoName.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNoName/WaitForCallbackNoName.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNullResult/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNullResult/Function.cs new file mode 100644 index 000000000..d163a5b6f --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNullResult/Function.cs @@ -0,0 +1,36 @@ +// 7-15: WaitForCallback success with null payload +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackNullResult; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + // External completes with no payload/null. + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNullResult/WaitForCallbackNullResult.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNullResult/WaitForCallbackNullResult.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackNullResult/WaitForCallbackNullResult.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSequential/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSequential/Function.cs new file mode 100644 index 000000000..e5b78dd85 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSequential/Function.cs @@ -0,0 +1,42 @@ +// 7-9: WaitForCallback sequential (two callbacks in sequence) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackSequential; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var firstResult = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: "first"); + + var secondResult = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: "second"); + + return secondResult; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSequential/WaitForCallbackSequential.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSequential/WaitForCallbackSequential.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSequential/WaitForCallbackSequential.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSubmitterRetry/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSubmitterRetry/Function.cs new file mode 100644 index 000000000..0af6e812c --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSubmitterRetry/Function.cs @@ -0,0 +1,42 @@ +// 7-7: WaitForCallback submitter retry exhaustion +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackSubmitterRetry; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + // Submitter always throws. Retry exhaustion propagates; do NOT catch. + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + throw new InvalidOperationException("submitter always fails"); + }, + name: input, + config: new WaitForCallbackConfig + { + RetryStrategy = RetryStrategy.Exponential( + maxAttempts: 2, + initialDelay: TimeSpan.FromSeconds(1)) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSubmitterRetry/WaitForCallbackSubmitterRetry.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSubmitterRetry/WaitForCallbackSubmitterRetry.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackSubmitterRetry/WaitForCallbackSubmitterRetry.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeout/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeout/Function.cs new file mode 100644 index 000000000..76db3efce --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeout/Function.cs @@ -0,0 +1,37 @@ +// 7-5: WaitForCallback timeout (uncaught) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackTimeout; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + // 3-second timeout; no external completion arrives. Do NOT catch. + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input, + config: new WaitForCallbackConfig { Timeout = TimeSpan.FromSeconds(3) }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeout/WaitForCallbackTimeout.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeout/WaitForCallbackTimeout.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeout/WaitForCallbackTimeout.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeoutCaught/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeoutCaught/Function.cs new file mode 100644 index 000000000..559c140ff --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeoutCaught/Function.cs @@ -0,0 +1,43 @@ +// 7-14: WaitForCallback timeout caught and handled +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackTimeoutCaught; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + try + { + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: input, + config: new WaitForCallbackConfig { Timeout = TimeSpan.FromSeconds(3) }); + + return result; + } + catch (CallbackTimeoutException) + { + return "timed-out-handled"; + } + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeoutCaught/WaitForCallbackTimeoutCaught.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeoutCaught/WaitForCallbackTimeoutCaught.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackTimeoutCaught/WaitForCallbackTimeoutCaught.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackWithName/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackWithName/Function.cs new file mode 100644 index 000000000..959fa55fb --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackWithName/Function.cs @@ -0,0 +1,35 @@ +// 7-2: WaitForCallback with explicit name +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForCallbackWithName; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.WaitForCallbackAsync( + async (callbackId, callbackContext, ct) => + { + await Task.CompletedTask; + }, + name: "approval"); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackWithName/WaitForCallbackWithName.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackWithName/WaitForCallbackWithName.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_callback/WaitForCallbackWithName/WaitForCallbackWithName.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionBasic/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionBasic/Function.cs new file mode 100644 index 000000000..1281382b4 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionBasic/Function.cs @@ -0,0 +1,42 @@ +// 6-1: Wait-for-condition basic (polls until threshold met) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionBasic; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(int input, IDurableContext context) + { + var threshold = input; + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state + 1; + }, + new WaitForConditionConfig + { + InitialState = 0, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + state >= threshold ? WaitDecision.Stop() : WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionBasic/WaitForConditionBasic.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionBasic/WaitForConditionBasic.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionBasic/WaitForConditionBasic.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrows/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrows/Function.cs new file mode 100644 index 000000000..db796e0ca --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrows/Function.cs @@ -0,0 +1,41 @@ +// 6-7: Wait-for-condition check function throws (uncaught failure) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionCheckThrows; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + throw new InvalidOperationException("check function error"); + }, + new WaitForConditionConfig + { + InitialState = 0, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrows/WaitForConditionCheckThrows.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrows/WaitForConditionCheckThrows.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrows/WaitForConditionCheckThrows.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrowsCaught/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrowsCaught/Function.cs new file mode 100644 index 000000000..1ee45343c --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrowsCaught/Function.cs @@ -0,0 +1,48 @@ +// 6-8: Wait-for-condition check throws, caught by handler (recovers) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionCheckThrowsCaught; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + try + { + await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + throw new InvalidOperationException("check function error"); + }, + new WaitForConditionConfig + { + InitialState = 0, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + } + catch (Exception) + { + return "recovered"; + } + + return "unreachable"; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrowsCaught/WaitForConditionCheckThrowsCaught.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrowsCaught/WaitForConditionCheckThrowsCaught.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCheckThrowsCaught/WaitForConditionCheckThrowsCaught.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionComplexObject/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionComplexObject/Function.cs new file mode 100644 index 000000000..32e747293 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionComplexObject/Function.cs @@ -0,0 +1,56 @@ +// 6-9: Wait-for-condition with complex object state +using System.Text.Json.Serialization; +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionComplexObject; + +public class PollState +{ + [JsonPropertyName("status")] + public string Status { get; set; } = ""; + + [JsonPropertyName("attempts")] + public int Attempts { get; set; } +} + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + var newAttempts = state.Attempts + 1; + return new PollState + { + Status = newAttempts >= 2 ? "DONE" : "PENDING", + Attempts = newAttempts + }; + }, + new WaitForConditionConfig + { + InitialState = new PollState { Status = "PENDING", Attempts = 0 }, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + state.Status == "DONE" ? WaitDecision.Stop() : WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionComplexObject/WaitForConditionComplexObject.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionComplexObject/WaitForConditionComplexObject.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionComplexObject/WaitForConditionComplexObject.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomInitialState/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomInitialState/Function.cs new file mode 100644 index 000000000..946d334fb --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomInitialState/Function.cs @@ -0,0 +1,42 @@ +// 6-4: Wait-for-condition with custom initial state +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionCustomInitialState; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(int input, IDurableContext context) + { + var threshold = input; + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state + 1; + }, + new WaitForConditionConfig + { + InitialState = 5, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + state >= threshold ? WaitDecision.Stop() : WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomInitialState/WaitForConditionCustomInitialState.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomInitialState/WaitForConditionCustomInitialState.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomInitialState/WaitForConditionCustomInitialState.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomSerdes/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomSerdes/Function.cs new file mode 100644 index 000000000..e73a091fb --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomSerdes/Function.cs @@ -0,0 +1,41 @@ +// 6-11: Wait-for-condition custom serdes (state survives serialization) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionCustomSerdes; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(string input, IDurableContext context) + { + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state + "x"; + }, + new WaitForConditionConfig + { + InitialState = "", + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + state.Length >= 2 ? WaitDecision.Stop() : WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomSerdes/WaitForConditionCustomSerdes.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomSerdes/WaitForConditionCustomSerdes.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionCustomSerdes/WaitForConditionCustomSerdes.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionFixedDelay/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionFixedDelay/Function.cs new file mode 100644 index 000000000..3224d7130 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionFixedDelay/Function.cs @@ -0,0 +1,44 @@ +// 6-5: Wait-for-condition with fixed delay strategy +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionFixedDelay; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(int input, IDurableContext context) + { + var threshold = input; + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state + 1; + }, + new WaitForConditionConfig + { + InitialState = 0, + WaitStrategy = WaitStrategy.Fixed( + delay: TimeSpan.FromSeconds(2), + maxAttempts: 60, + isDone: state => state >= threshold) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionFixedDelay/WaitForConditionFixedDelay.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionFixedDelay/WaitForConditionFixedDelay.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionFixedDelay/WaitForConditionFixedDelay.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionImmediate/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionImmediate/Function.cs new file mode 100644 index 000000000..303e5faaa --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionImmediate/Function.cs @@ -0,0 +1,41 @@ +// 6-2: Wait-for-condition immediate stop (condition already met on first check) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionImmediate; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(int input, IDurableContext context) + { + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state; + }, + new WaitForConditionConfig + { + InitialState = input, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + state >= 5 ? WaitDecision.Stop() : WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionImmediate/WaitForConditionImmediate.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionImmediate/WaitForConditionImmediate.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionImmediate/WaitForConditionImmediate.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMaxAttempts/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMaxAttempts/Function.cs new file mode 100644 index 000000000..7b4ad49b8 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMaxAttempts/Function.cs @@ -0,0 +1,43 @@ +// 6-6: Wait-for-condition max attempts exceeded (failure) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionMaxAttempts; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(int input, IDurableContext context) + { + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state + 1; + }, + new WaitForConditionConfig + { + InitialState = 0, + WaitStrategy = WaitStrategy.Fixed( + delay: TimeSpan.FromSeconds(1), + maxAttempts: 3, + isDone: _ => false) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMaxAttempts/WaitForConditionMaxAttempts.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMaxAttempts/WaitForConditionMaxAttempts.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMaxAttempts/WaitForConditionMaxAttempts.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMultipleSequential/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMultipleSequential/Function.cs new file mode 100644 index 000000000..42579bf8e --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMultipleSequential/Function.cs @@ -0,0 +1,54 @@ +// 6-13: Multiple sequential wait_for_condition operations +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionMultipleSequential; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var firstResult = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state + 1; + }, + new WaitForConditionConfig + { + InitialState = 0, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + state >= 2 ? WaitDecision.Stop() : WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + + var secondResult = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state + 1; + }, + new WaitForConditionConfig + { + InitialState = firstResult, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + state >= 4 ? WaitDecision.Stop() : WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + + return secondResult; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMultipleSequential/WaitForConditionMultipleSequential.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMultipleSequential/WaitForConditionMultipleSequential.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionMultipleSequential/WaitForConditionMultipleSequential.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionNullResult/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionNullResult/Function.cs new file mode 100644 index 000000000..3167aa99d --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionNullResult/Function.cs @@ -0,0 +1,41 @@ +// 6-10: Wait-for-condition with null result +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionNullResult; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(object? input, IDurableContext context) + { + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return null; + }, + new WaitForConditionConfig + { + InitialState = null, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + WaitDecision.Stop()) + }); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionNullResult/WaitForConditionNullResult.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionNullResult/WaitForConditionNullResult.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionNullResult/WaitForConditionNullResult.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionThenStep/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionThenStep/Function.cs new file mode 100644 index 000000000..db8119c4f --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionThenStep/Function.cs @@ -0,0 +1,49 @@ +// 6-12: Wait-for-condition followed by a step (result passed onward) +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionThenStep; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(int input, IDurableContext context) + { + var threshold = input; + var pollResult = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state + 1; + }, + new WaitForConditionConfig + { + InitialState = 0, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + state >= threshold ? WaitDecision.Stop() : WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }); + + var stepResult = await context.StepAsync( + async (_, ct) => + { + await Task.CompletedTask; + return pollResult * 10; + }); + + return stepResult; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionThenStep/WaitForConditionThenStep.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionThenStep/WaitForConditionThenStep.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionThenStep/WaitForConditionThenStep.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionWithName/Function.cs b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionWithName/Function.cs new file mode 100644 index 000000000..b4eac9de8 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionWithName/Function.cs @@ -0,0 +1,43 @@ +// 6-3: Wait-for-condition with explicit name +using Amazon.Lambda.Core; +using Amazon.Lambda.DurableExecution; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +namespace WaitForConditionWithName; + +public class Function +{ + public static async Task Main(string[] args) + { + var handler = new Function(); + var serializer = new DefaultLambdaJsonSerializer(); + using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler.Handler, serializer); + using var bootstrap = new LambdaBootstrap(handlerWrapper); + await bootstrap.RunAsync(); + } + + public Task Handler( + DurableExecutionInvocationInput input, ILambdaContext context) + => DurableFunction.WrapAsync(Workflow, input, context); + + private async Task Workflow(int input, IDurableContext context) + { + var threshold = input; + var result = await context.WaitForConditionAsync( + async (state, checkCtx, ct) => + { + await Task.CompletedTask; + return state + 1; + }, + new WaitForConditionConfig + { + InitialState = 0, + WaitStrategy = WaitStrategy.FromDelegate((state, attempt) => + state >= threshold ? WaitDecision.Stop() : WaitDecision.ContinueAfter(TimeSpan.FromSeconds(1))) + }, + name: "poll-status"); + + return result; + } +} diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionWithName/WaitForConditionWithName.csproj b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionWithName/WaitForConditionWithName.csproj new file mode 100644 index 000000000..4b354e178 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/wait_for_condition/WaitForConditionWithName/WaitForConditionWithName.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Exe + true + bootstrap + enable + enable + + + + + + + + + + From cc3aa3f477fdf64083f20f89abb26e701f11942b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 11:48:17 -0400 Subject: [PATCH 04/22] chore: Daily ASP.NET Core version update in Dockerfiles (#2529) Co-authored-by: aws-sdk-dotnet-automation --- LambdaRuntimeDockerfiles/Images/net10/amd64/Dockerfile | 4 ++-- LambdaRuntimeDockerfiles/Images/net10/arm64/Dockerfile | 4 ++-- LambdaRuntimeDockerfiles/Images/net11/amd64/Dockerfile | 4 ++-- LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile | 4 ++-- LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile | 4 ++-- LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile | 4 ++-- LambdaRuntimeDockerfiles/Images/net9/amd64/Dockerfile | 4 ++-- LambdaRuntimeDockerfiles/Images/net9/arm64/Dockerfile | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/LambdaRuntimeDockerfiles/Images/net10/amd64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net10/amd64/Dockerfile index a3f689b5d..3bf00508b 100644 --- a/LambdaRuntimeDockerfiles/Images/net10/amd64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net10/amd64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=10.0.9 -ARG ASPNET_SHA512=a3d1fc542adfc2532e9a8ac4444fb0539efcec33cf733de60cd5f6f8fe3e9a5a3236b98694b26cf73d9cd75d2e1bca0c691e83d41107debc4cf87a5ab299a82d +ARG ASPNET_VERSION=10.0.11 +ARG ASPNET_SHA512=4c6be0623330074e699dab8084be15a1baebb7a518c0dd8ce99f93cf79777cd46f3a38ef9d25edc152ed606f084b63736bd9e4082eb32d188fc357bf6ac4d1d6 ARG LAMBDA_RUNTIME_NAME=dotnet10 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net10/arm64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net10/arm64/Dockerfile index a758076fd..e17bd42ca 100644 --- a/LambdaRuntimeDockerfiles/Images/net10/arm64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net10/arm64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=10.0.9 -ARG ASPNET_SHA512=13377eca63d52a85b7e7126f0ad6b017ffbd520b6d95db0ba05eb6c152645ef4238f8a3744d2b04ac418d9856ee7db732be927aabde84871402c3548e1ffcee2 +ARG ASPNET_VERSION=10.0.11 +ARG ASPNET_SHA512=9549f7a59d5d6f7dd3e965bf88631698b23974aff4e34d589037d6ae9a3f4433902881b4d23f7e18602ab954823f5be35015054a6eccb57041b0f20d92873ed7 ARG LAMBDA_RUNTIME_NAME=dotnet10 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net11/amd64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net11/amd64/Dockerfile index 15b5c9eb6..46ea5fb8d 100644 --- a/LambdaRuntimeDockerfiles/Images/net11/amd64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net11/amd64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=11.0.0-preview.5.26302.115 -ARG ASPNET_SHA512=71a0b84f8edb761ea8135871e5f64768576623497aeb7164932dc068a6e9b1afa9bcb5c763621fdbf07d9e9f6b8202332160c25d2b97b4bfff3c7e33cc848dc2 +ARG ASPNET_VERSION=11.0.0-preview.7.26381.103 +ARG ASPNET_SHA512=a5d8b79f3f9f9ad915ca4a3b5099823ca03511d1c900a8117c1ed6b8ccc1fe843e430d4f58d5c05b971ddacb7e88faf6ed258ed704ec9f414a81f6c05322c65b ARG LAMBDA_RUNTIME_NAME=dotnet11 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile index 8caf46681..f2764aeef 100644 --- a/LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=11.0.0-preview.5.26302.115 -ARG ASPNET_SHA512=2b2b5505bf3b53b3280170f0ccc05a5eb732c27857c350b6af57715abf5442ee77b06bf319d6d04e5922f150a8be5253af5af4ef7928303b865a2b7a310c334d +ARG ASPNET_VERSION=11.0.0-preview.7.26381.103 +ARG ASPNET_SHA512=cadec63c9223d5789c17fff57c4b26348b40b772b0b461972cf62b2743a517819b7dd8f1e52ba5da780a6d3109aa3ca0718e8a980bdfab3c73e33e8899e54e07 ARG LAMBDA_RUNTIME_NAME=dotnet11 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile index 2968faf8d..127b38512 100644 --- a/LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=8.0.28 -ARG ASPNET_SHA512=3052101724895dbc18beca8fa939c29185678fef7fb0798a2bad948761c61c172044500395315f9f2bd7c7343a14a9e8a438e5b841df3a7dc3b77faa5c263bd6 +ARG ASPNET_VERSION=8.0.30 +ARG ASPNET_SHA512=415f79420e9fc465467ccab237f18b609710a715ebe43cd3c05c69af975d474fddcbe37eb831164472de60739874216c451ebbfb5f1fb040b41e90e41dc77206 ARG LAMBDA_RUNTIME_NAME=dotnet8 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile index dcca0803f..b72ca9767 100644 --- a/LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=8.0.28 -ARG ASPNET_SHA512=28cdefc13f4169b6348cb3203bbb9d7b1723c513ee44ea52fcb6aac5c1e7986099141815b52451320f4c703edb8b11d675213e6c013138baae6f80ace9d93477 +ARG ASPNET_VERSION=8.0.30 +ARG ASPNET_SHA512=279d8ab84b6102c29fc96b5980805e80c679a234abdbe8047aa9f5954b260ddb70b4ae0793e2c379335d20dfaa6c86bdf7c8bd7d442c8ebe3c02d75b10bc8a4f ARG LAMBDA_RUNTIME_NAME=dotnet8 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net9/amd64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net9/amd64/Dockerfile index 1f42b4451..a4fad9b20 100644 --- a/LambdaRuntimeDockerfiles/Images/net9/amd64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net9/amd64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=9.0.17 -ARG ASPNET_SHA512=a800b47a7db2bebd418a77553b232f9a30a7d1e3da0e7b07572878ac3a75cfa8317f7c969ab78b79acc2e8cbacea5467566d4108def115b3f2659377660aab8a +ARG ASPNET_VERSION=9.0.19 +ARG ASPNET_SHA512=579f37c2af8dbe8f7e3ef294c02fcf6ce2649fc34aba8f8ead087a9bd794403a8852079fc0f1c5cbe5baba337e6b999478667fed0f05fbe0fb8fba70661f7608 ARG LAMBDA_RUNTIME_NAME=dotnet9 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net9/arm64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net9/arm64/Dockerfile index d9f700364..327e5694f 100644 --- a/LambdaRuntimeDockerfiles/Images/net9/arm64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net9/arm64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=9.0.17 -ARG ASPNET_SHA512=6ca04293c246b1485e7db51e743c53336d87b8e92c6ab8992d6d0972de84fed0adb3c743fafca58508199cad01f0cea875317d7b0cd977a82cab7d37c67c190b +ARG ASPNET_VERSION=9.0.19 +ARG ASPNET_SHA512=3c716a748de08c44b475d8a7e2ef8973d8d330fea31d076688263c209c649318c396f8e4779b5bc1ae2176f7b623985a0da177bb50265da722c73b6aebf7faeb ARG LAMBDA_RUNTIME_NAME=dotnet9 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 From 1fee8a64dbfab4a5aab7d2c3880b4f898998743b Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Mon, 17 Aug 2026 13:33:01 -0400 Subject: [PATCH 05/22] Improve Lambda Test Tool v2 getting-started docs and add samples (#2494) Rewrite the LambdaTestTool-v2 README to be usage-first and fix several copy-paste-breaking inaccuracies: - Add a top-of-page Quick Start (install -> run -> invoke -> result) and a Prerequisites section (.NET 8+, PATH for global tools, verify step). - Fix the API Gateway route Endpoint example to use the base URL (http://localhost:5050) instead of appending the function name. - Make emulator-mode examples consistently HttpV2 to match the sample handler (avoids HTTP 502) and normalize Get/HttpV2 casing. - Correct and complete the command-line options table; document the info command, SQS and DynamoDB Streams event sources, the web UI workflow, built-in sample events, saved requests, and theming. - Add Troubleshooting and Known Limitations sections; remove the duplicate H1 and stale version note; reframe the Aspire pointer. Add runnable sample projects under samples/ (AddFunctionTopLevel, AddFunctionClassLibrary, SQSProcessor, ToUpperFunction), each with its own README and committed launch profile. Add a .gitignore exception so the samples' launchSettings.json files are tracked. Document CLI launch path for class-library functions Verified end-to-end that a class-library Lambda function can run from the command line (not just an IDE). The Executable launch profile relies on the IDE expanding $(Configuration) and resolving workingDirectory, which plain 'dotnet run --launch-profile' does not do. - Add CopyLocalLockFileAssemblies to the AddFunctionClassLibrary sample so its NuGet dependencies are copied to the build output, making the function self-contained for a command-line 'dotnet exec' launch. - Restructure README Option 2 to lead with the command-line 'dotnet exec' steps and keep the IDE launch profile as a secondary option, noting the dotnet-run-vs-IDE caveat. - Rewrite the sample README with both command-line and IDE launch paths. Address PR feedback: bump samples to net10, fix API GW mode and config-storage-path docs --- .gitignore | 2 + Tools/LambdaTestTool-v2/README.md | 565 +++++++++++++----- .../AddFunctionClassLibrary.csproj | 22 + .../AddFunctionClassLibrary/Function.cs | 20 + .../Properties/launchSettings.json | 13 + .../samples/AddFunctionClassLibrary/README.md | 78 +++ .../AddFunctionTopLevel.csproj | 19 + .../samples/AddFunctionTopLevel/Program.cs | 17 + .../Properties/launchSettings.json | 10 + .../samples/AddFunctionTopLevel/README.md | 38 ++ Tools/LambdaTestTool-v2/samples/README.md | 17 + .../samples/SQSProcessor/Program.cs | 21 + .../Properties/launchSettings.json | 10 + .../samples/SQSProcessor/README.md | 39 ++ .../samples/SQSProcessor/SQSProcessor.csproj | 19 + .../samples/ToUpperFunction/Program.cs | 21 + .../Properties/launchSettings.json | 10 + .../samples/ToUpperFunction/README.md | 27 + .../ToUpperFunction/ToUpperFunction.csproj | 18 + 19 files changed, 805 insertions(+), 161 deletions(-) create mode 100644 Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/AddFunctionClassLibrary.csproj create mode 100644 Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/Function.cs create mode 100644 Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/Properties/launchSettings.json create mode 100644 Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/README.md create mode 100644 Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/AddFunctionTopLevel.csproj create mode 100644 Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/Program.cs create mode 100644 Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/Properties/launchSettings.json create mode 100644 Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/README.md create mode 100644 Tools/LambdaTestTool-v2/samples/README.md create mode 100644 Tools/LambdaTestTool-v2/samples/SQSProcessor/Program.cs create mode 100644 Tools/LambdaTestTool-v2/samples/SQSProcessor/Properties/launchSettings.json create mode 100644 Tools/LambdaTestTool-v2/samples/SQSProcessor/README.md create mode 100644 Tools/LambdaTestTool-v2/samples/SQSProcessor/SQSProcessor.csproj create mode 100644 Tools/LambdaTestTool-v2/samples/ToUpperFunction/Program.cs create mode 100644 Tools/LambdaTestTool-v2/samples/ToUpperFunction/Properties/launchSettings.json create mode 100644 Tools/LambdaTestTool-v2/samples/ToUpperFunction/README.md create mode 100644 Tools/LambdaTestTool-v2/samples/ToUpperFunction/ToUpperFunction.csproj diff --git a/.gitignore b/.gitignore index f86678d7a..fa7937bf4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ **/Deployment/ **/packages **/launchSettings.json +# Keep the launch profiles for the Lambda Test Tool v2 sample projects (they are part of the docs). +!Tools/LambdaTestTool-v2/samples/**/launchSettings.json **/Debug/ **/build/ diff --git a/Tools/LambdaTestTool-v2/README.md b/Tools/LambdaTestTool-v2/README.md index ba025aefa..dc45e5ceb 100644 --- a/Tools/LambdaTestTool-v2/README.md +++ b/Tools/LambdaTestTool-v2/README.md @@ -1,209 +1,404 @@ # AWS Lambda Test Tool -## Overview -The AWS Lambda Test Tool provides local testing capabilities for .NET Lambda functions with support for both Lambda emulation and API Gateway emulation. This tool allows developers to test their Lambda functions locally in three different modes: +Test and debug your .NET AWS Lambda functions locally. The tool runs a local Lambda Runtime API emulator (so your function connects to it exactly as it would in the cloud), an optional API Gateway emulator, and optional SQS / DynamoDB Streams event sources — all driven from a web UI. -1. Lambda Emulator Mode -2. API Gateway Emulator Mode -3. Combined Mode (both emulators) +![The Lambda Test Tool web UI showing the function input editor and invoke controls.](Resources/img.png) -![img.png](Resources/img.png) +> **Preview:** This tool is in preview. See [Known Limitations](#known-limitations). For questions and problems, please [open a GitHub issue](https://github.com/aws/aws-lambda-dotnet/issues). -## Comparison with Previous Test Tool +## Table of Contents -The AWS Lambda Test Tool is an evolution of the previous [AWS .NET Mock Lambda Test Tool](https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool), with several key improvements: - -### New Features -- **API Gateway Emulation**: Direct support for testing API Gateway integrations locally -- Updated to use a new flow for loading Lambda functions that mimics closer to the Lambda service. This solves many of the issues with the older tool when it came to loading dependencies. -- Ability to have multiple Lambda functions use the same instance of the test tool. -- UI refresh -- [Support for integration with .NET Aspire](https://github.com/aws/integrations-on-dotnet-aspire-for-aws/issues/17) - -# AWS Lambda Test Tool - -- [Overview](#overview) -- [Comparison with Previous Test Tool](#comparison-with-previous-test-tool) - - [New Features](#new-features) -- [Getting help](#getting-help) -- [.NET Aspire integration](#net-aspire-integration) +- [Quick Start](#quick-start) +- [Prerequisites](#prerequisites) +- [Features](#features) - [Installing](#installing) - [Running the Test Tool](#running-the-test-tool) - - [Lambda Emulator Mode](#lambda-emulator-mode) - - [API Gateway Emulator Mode](#api-gateway-emulator-mode) - - [Required Configuration](#required-configuration) - - [Combined Mode](#combined-mode) + - [Lambda Emulator Mode](#lambda-emulator-mode) + - [API Gateway Emulator Mode](#api-gateway-emulator-mode) + - [Combined Mode](#combined-mode) - [Command Line Options](#command-line-options) +- [Using the Web UI](#using-the-web-ui) + - [Built-in Sample Events](#built-in-sample-events) + - [Saving Requests](#saving-requests) + - [Light / Dark Theme](#light--dark-theme) - [API Gateway Configuration](#api-gateway-configuration) - - [Single Route Configuration](#single-route-configuration) - - [Multiple Routes Configuration](#multiple-routes-configuration) + - [Single Route](#single-route) + - [Multiple Routes](#multiple-routes) + - [Wildcard Paths](#wildcard-paths) +- [Event Sources](#event-sources) + - [SQS Event Source](#sqs-event-source) + - [DynamoDB Streams Event Source](#dynamodb-streams-event-source) - [Example Lambda Function Setup](#example-lambda-function-setup) - - [1. Lambda Function Code](#1-lambda-function-code) - - [Option 1: Using Top-Level Statements](#option-1-using-top-level-statements) - - [Option 2: Using Class Library](#option-2-using-class-library) - - [2. AWS_LAMBDA_RUNTIME_API](#2-aws_lambda_runtime_api) - - [3. API Gateway Configuration](#3-api-gateway-configuration) - - [4. Testing the Function](#4-testing-the-function) + - [Option 1: Top-Level Statements](#option-1-top-level-statements) + - [Option 2: Class Library](#option-2-class-library) + - [The AWS_LAMBDA_RUNTIME_API Environment Variable](#the-aws_lambda_runtime_api-environment-variable) +- [Sample Projects](#sample-projects) +- [.NET Aspire Integration](#net-aspire-integration) +- [Troubleshooting](#troubleshooting) +- [Known Limitations](#known-limitations) +- [What's New Compared to the Previous Test Tool](#whats-new-compared-to-the-previous-test-tool) +- [Getting Help](#getting-help) + +## Quick Start + +This gets you from install to a working local invocation in about five minutes, testing a Lambda function through the API Gateway emulator. -## Getting help +**1. Install the tool** (see [Prerequisites](#prerequisites) if `dotnet` commands aren't found): -This tool is currently in preview and there are some known limitations. For questions and problems please open a GitHub issue in this repository. +``` +dotnet tool install -g amazon.lambda.testtool +``` -## .NET Aspire integration -The easiest way to get started using the features of the new test tool is with .NET Aspire. The integration takes care of installing the tool and provides .NET Aspire extension methods for configuring your Lambda functions and API Gateway emulator in the .NET Aspire AppHost. It avoids all of the steps list below for installing the tooling and setting up environment variables. +**2. Create a Lambda function** that adds two numbers. This uses the AWS Lambda project templates (install once with `dotnet new install Amazon.Lambda.Templates`), or skip ahead and clone [`samples/AddFunctionTopLevel`](samples/AddFunctionTopLevel) instead: -Check out the following tracker issue for information on the .NET Aspire integration and steps for getting started. https://github.com/aws/integrations-on-dotnet-aspire-for-aws/issues/17 +``` +dotnet new lambda.EmptyFunction --name AddLambdaFunction +cd AddLambdaFunction/src/AddLambdaFunction +``` -## Installing +> The current `lambda.EmptyFunction` template targets `net10.0`, so building the generated project needs a .NET 10 SDK. If you only have .NET 8 or 9 installed, edit the `` in the generated `.csproj` to `net8.0` (or `net9.0`) before continuing. + +Replace the contents of `Function.cs` with a top-level-statements handler: + +```csharp +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +var handler = (APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) => +{ + var x = int.Parse(request.PathParameters["x"]); + var y = int.Parse(request.PathParameters["y"]); + return (x + y).ToString(); +}; + +await LambdaBootstrapBuilder.Create(handler, new CamelCaseLambdaJsonSerializer()) + .Build() + .RunAsync(); +``` -The tool is distributed as .NET Global Tool. To install the tool execute the following command: +Add the packages the handler uses: ``` -dotnet tool install -g amazon.lambda.testtool +dotnet add package Amazon.Lambda.RuntimeSupport +dotnet add package Amazon.Lambda.APIGatewayEvents ``` -To update the tool run the following command: +**3. Tell the function where the local runtime API is.** Add a launch profile to `Properties/launchSettings.json`: +```json +{ + "profiles": { + "AddLambdaFunction": { + "commandName": "Project", + "environmentVariables": { + "AWS_LAMBDA_RUNTIME_API": "localhost:5050/AddLambdaFunction" + } + } + } +} ``` -dotnet tool update -g amazon.lambda.testtool + +**4. Configure the API Gateway route** the emulator will expose. Set this environment variable in the terminal you'll start the test tool from: + +```bash +# Linux/macOS +export APIGATEWAY_EMULATOR_ROUTE_CONFIG='{"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"http://localhost:5050"}' ``` -## Running the Test Tool +```powershell +# Windows (PowerShell) +$env:APIGATEWAY_EMULATOR_ROUTE_CONFIG='{"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"http://localhost:5050"}' +``` -### Lambda Emulator Mode -Use this mode when you want to test Lambda functions directly without API Gateway integration. +**5. Start the test tool** with both the Lambda and API Gateway emulators (in the terminal from step 4): ``` -# Start Lambda emulator on port 5050 -dotnet lambda-test-tool start --lambda-emulator-port 5050 +dotnet lambda-test-tool start --lambda-emulator-port 5050 --api-gateway-emulator-port 5051 --api-gateway-emulator-mode HttpV2 ``` -### API Gateway Emulator Mode -Use this mode when you want to test Lambda functions through API Gateway endpoints. **Note: Running this mode by itself will not work, you will still need have the lambda runtime client running elsewhere and reference it in the `Endpoint` parameter in the `APIGATEWAY_EMULATOR_ROUTE_CONFIG` env varible (see below [Required Configuration](#required-configuration))** Api gateway mode requires additional configuration through environment variables. +**6. Start your function** (in a separate terminal, from the function directory): ``` -# Start API Gateway emulator on port 5051 in REST mode -dotnet lambda-test-tool start \ - --api-gateway-emulator-port 5051 \ - --api-gateway-emulator-mode Rest +dotnet run --launch-profile AddLambdaFunction +``` + +**7. Invoke it** through the API Gateway emulator: ``` -#### Required Configuration -When running via command line, you must set the environment variable for API Gateway route configuration: +curl "http://localhost:5051/add/5/3" +``` -Linux/macOS: -```bash -export APIGATEWAY_EMULATOR_ROUTE_CONFIG='{"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"{LAMBDA_RUNTIME_API}"}' +Expected response: +``` +8 +``` + +> A ready-made version of this walkthrough lives in [`samples/AddFunctionTopLevel`](samples/AddFunctionTopLevel). See [Sample Projects](#sample-projects). + +## Prerequisites + +- **.NET SDK 8.0 or later.** The tool targets `net8.0` and `net10.0` and rolls forward, so a .NET 8, 9, or 10 SDK works. Verify with `dotnet --version`. +- **The .NET global tools directory must be on your `PATH`.** `dotnet tool install -g` installs to `~/.dotnet/tools` (Linux/macOS) or `%USERPROFILE%\.dotnet\tools` (Windows). If `dotnet lambda-test-tool` reports "command not found" after installing, add that directory to your `PATH` and open a new terminal. +- Verify the install with `dotnet lambda-test-tool info`, which prints the installed version and path. + +## Features + +- **Lambda Runtime API emulator** — run and debug any .NET Lambda function locally; it connects to the emulator exactly as it would to the real Lambda service. +- **API Gateway emulator** — test functions through REST, HTTP API v1, or HTTP API v2 request/response shapes. +- **SQS and DynamoDB Streams event sources** — poll a real queue or table stream and invoke your function with batched events. +- **Web UI** — pick or edit an event, invoke, inspect the response, and re-invoke from history. +- **58 built-in sample events** — S3, SQS, SNS, DynamoDB, API Gateway, CloudWatch, Kinesis, Alexa, Lex, and more, available directly in the UI. +- **Saved requests** — save and reuse request payloads across sessions. +- **Light / dark theme.** +- **[.NET Aspire integration](#net-aspire-integration)** — for a lower-configuration setup. + +## Installing + +The tool is distributed as a .NET Global Tool: + +``` +dotnet tool install -g amazon.lambda.testtool ``` -Windows (Command Prompt): +To update: ``` -set APIGATEWAY_EMULATOR_ROUTE_CONFIG={"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"{LAMBDA_RUNTIME_API}"} +dotnet tool update -g amazon.lambda.testtool +``` + +To confirm the installed version and location (useful for the class-library setup below): ``` +dotnet lambda-test-tool info +``` + +## Running the Test Tool -Windows (PowerShell): +The test tool can run the Lambda emulator, the API Gateway emulator, or both. + +### Lambda Emulator Mode + +Use this mode to invoke Lambda functions directly (via the web UI or the SDK), without API Gateway. This is the simplest way to test event-driven functions (S3, SQS, custom events). ``` -$env:APIGATEWAY_EMULATOR_ROUTE_CONFIG='{"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"{LAMBDA_RUNTIME_API}"}' +# Start the Lambda emulator on port 5050 +dotnet lambda-test-tool start --lambda-emulator-port 5050 +``` + +The web UI opens automatically. Point your function at the emulator with the [`AWS_LAMBDA_RUNTIME_API`](#the-aws_lambda_runtime_api-environment-variable) environment variable, then invoke it from the UI. +### API Gateway Emulator Mode + +Use this mode to test functions through API Gateway endpoints. + +> **The API Gateway emulator does not run your function.** It forwards requests to a Lambda Runtime API endpoint that must already be running — either the Lambda emulator (Combined Mode below) or one you point at with the route config's `Endpoint`. Running this mode by itself will not invoke anything. + +``` +dotnet lambda-test-tool start --api-gateway-emulator-port 5051 --api-gateway-emulator-mode HttpV2 ``` -Replace `{LAMBDA_RUNTIME_API}` with your Lambda runtime API endpoint (e.g., "http://localhost:5050/AddLambdaFunction" or the endpoint specified in your `AWS_LAMBDA_RUNTIME_API` environment variable). +You must also set the `APIGATEWAY_EMULATOR_ROUTE_CONFIG` environment variable to map routes to functions — see [API Gateway Configuration](#api-gateway-configuration). +> **Choose the mode to match your handler.** `Rest`, `HttpV1`, and `HttpV2` differ in the request event shape and how the response is interpreted. A handler taking `APIGatewayHttpApiV2ProxyRequest` (like the Quick Start) must use `HttpV2`; `APIGatewayProxyRequest` is used by `Rest` and `HttpV1`. A mismatch typically produces an HTTP 502. `--api-gateway-emulator-mode` is required whenever you start the API Gateway emulator (`--api-gateway-emulator-port` / `--api-gateway-emulator-https-port`) — the `start` command errors if it's omitted. ### Combined Mode -Use this mode when you want to run both Lambda and API Gateway emulators simultaneously. + +Run both emulators together — the API Gateway emulator forwards to the Lambda emulator automatically. This is the most common setup. ``` -# Start both emulators dotnet lambda-test-tool start \ --lambda-emulator-port 5050 \ --api-gateway-emulator-port 5051 \ - --api-gateway-emulator-mode Rest + --api-gateway-emulator-mode HttpV2 ``` ## Command Line Options -| Option | Description | Required For | -|--------|-------------|--------------| -| `--lambda-emulator-port` | Port for Lambda emulator | Lambda Mode | -| `--lambda-emulator-host` | Host for Lambda emulator | Lambda Mode | -| `--api-gateway-emulator-port` | Port for API Gateway | API Gateway Mode | -| `--api-gateway-emulator-mode` | API Gateway mode (Rest/HttpV1/HttpV2) | API Gateway Mode | -| `--no-launch-window` | Disable auto-launching web interface | Optional | -| `--config-storage-path` | Path for saving settings and requests | Optional | +All options belong to the `start` command (`dotnet lambda-test-tool start ...`). -## API Gateway Configuration -When using API Gateway mode, you need to configure the route mapping using the APIGATEWAY_EMULATOR_ROUTE_CONFIG environment variable. This can be a single route or an array of routes: +| Option | Description | Default | +|--------|-------------|---------| +| `-p`, `--lambda-emulator-port ` | Port for the Lambda emulator / web UI. If set, the Lambda emulator starts. | — | +| `--lambda-emulator-host ` | Host for the web UI. Any value other than an IP or `localhost` (e.g. `*`, `+`) binds to all public addresses. | `localhost` | +| `--lambda-emulator-https-port ` | HTTPS port for the web UI. Requires certs configured for the host. | — | +| `--api-gateway-emulator-port ` | Port for the API Gateway emulator. If set, the emulator starts (requires `--api-gateway-emulator-mode`). | — | +| `--api-gateway-emulator-mode ` | API Gateway mode: `Rest`, `HttpV1`, or `HttpV2`. Required when the API Gateway emulator is started. | — | +| `--api-gateway-emulator-https-port ` | HTTPS port for the API Gateway emulator. Requires certs configured for the host. | — | +| `--sqs-eventsource-config ` | Configure an [SQS event source](#sqs-event-source). | — | +| `--dynamodbstreams-eventsource-config ` | Configure a [DynamoDB Streams event source](#dynamodb-streams-event-source). | — | +| `--no-launch-window` | Do not auto-launch the web UI in a browser. | off | +| `--config-storage-path ` | Path for [saving settings and requests](#saving-requests). A relative path is resolved against the current directory. | — | + +The tool also has an `info` command: + +``` +dotnet lambda-test-tool info [--format Text|Json] +``` -### Single Route Configuration +It prints the installed `Version` and `InstallPath`. `--format` defaults to `Text`. + +## Using the Web UI + +When the Lambda emulator starts, the web UI opens automatically (disable with `--no-launch-window`). It's the primary way to invoke and debug functions — no `curl` required. + +The typical flow: + +1. **Select the function** to invoke (the "Switch function" control lists every function connected to the emulator). +2. **Provide the event** in the Function Input editor. Type/paste JSON, or pick a [built-in sample event](#built-in-sample-events) or a [saved request](#saving-requests) from the dropdown. +3. **Invoke** the function. The request moves through the **Active Event**, **Queued**, and **History** tabs. +4. **Inspect the response** in the request/response dialog — the response body or the error and stack trace if the function threw. +5. **Re-Invoke** any past request from History, or **Clear** the queue/history. + +### Built-in Sample Events + +The tool ships **58 sample event payloads** (S3, SQS, SNS, DynamoDB, API Gateway, CloudWatch Logs, Kinesis, Alexa, Lex, CloudFront, and more), available from the "Example Requests" dropdown above the Function Input editor. Pick one to populate the editor with a realistic event instead of hand-writing JSON — the fastest way to test an event-driven handler. + +### Saving Requests + +You can save request payloads for quick reuse. Saved requests appear in a dropdown above the Function Input editor, and the UI provides: + +- A **Save** dialog to name and store the current input. +- A **Manage Saved Requests** dialog to delete saved requests. +- Toggles to show/hide the sample events, saved requests, and the requests list. + +Saving is only enabled when you provide a storage path at startup, because requests are persisted to disk there: + +``` +dotnet lambda-test-tool start --lambda-emulator-port 5050 --config-storage-path ``` + +### Light / Dark Theme + +The UI has a light/dark theme switcher in the top navigation. Your choice persists across sessions (when a `--config-storage-path` is configured). + +## API Gateway Configuration + +When using the API Gateway emulator, map routes to functions with the `APIGATEWAY_EMULATOR_ROUTE_CONFIG` environment variable. Each route has: + +- `LambdaResourceName` — the function name (matches the name in `AWS_LAMBDA_RUNTIME_API`). +- `HttpMethod` — e.g. `Get`, `Post` (matched case-insensitively). +- `Path` — the route template, e.g. `/add/{x}/{y}`. +- `Endpoint` — the **base URL** of the Lambda Runtime API, e.g. `http://localhost:5050`. Do **not** append the function name here; that comes from `LambdaResourceName`. In Combined Mode this is the Lambda emulator's address. + +The value can be a single route object or an array of routes. + +### Single Route + +```json { "LambdaResourceName": "AddLambdaFunction", "HttpMethod": "Get", - "Path": "/add/{x}/{y}" + "Path": "/add/{x}/{y}", + "Endpoint": "http://localhost:5050" } ``` -### Multiple Routes Configuration +### Multiple Routes -``` +```json [ { "LambdaResourceName": "AddLambdaFunction", "HttpMethod": "Get", - "Path": "/add/{x}/{y}" + "Path": "/add/{x}/{y}", + "Endpoint": "http://localhost:5050" }, { "LambdaResourceName": "SubtractLambdaFunction", "HttpMethod": "Get", - "Path": "/minus/{x}/{y}" + "Path": "/minus/{x}/{y}", + "Endpoint": "http://localhost:5050" } ] - ``` +> **Setting the variable on Windows Command Prompt:** quote the whole assignment so special characters are preserved: +> ``` +> set "APIGATEWAY_EMULATOR_ROUTE_CONFIG={"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"http://localhost:5050"}" +> ``` -#### Wildcard Paths -The API Gateway emulator supports the use of wildcard path. To define a wildcard path, you can use the `{proxy+}` syntax in the route pattern. See [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html) for a more detailed explanation on how proxies work. +### Wildcard Paths -Here's an example of how to set up an API Gateway emulator with a wildcard path: +Use the `{proxy+}` syntax to proxy any additional path segments to a function. See the [API Gateway proxy integration docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html) for details. -``` +```json [ { "LambdaResourceName": "RootFunction", "HttpMethod": "Get", - "Path": "/root" + "Path": "/root", + "Endpoint": "http://localhost:5050" }, { "LambdaResourceName": "MyOtherLambdaFunction", "HttpMethod": "Get", - "Path": "/root/{proxy+}" + "Path": "/root/{proxy+}", + "Endpoint": "http://localhost:5050" } ] +``` + +This maps `/root` to `RootFunction` and any deeper path (e.g. `/root/a/b`) to `MyOtherLambdaFunction`. + +## Event Sources + +The tool can poll a real AWS SQS queue or DynamoDB table stream and invoke your function with the batched events, mirroring how Lambda event source mappings work. These use real AWS credentials (via the `Profile`/`Region` keys or your default credential chain). + +### SQS Event Source +Long-polls a queue, batches messages into an `SQSEvent`, invokes the function, and deletes messages on success (honoring partial-batch failures via `SQSBatchResponse`). + +``` +dotnet lambda-test-tool start \ + --lambda-emulator-port 5050 \ + --sqs-eventsource-config "QueueUrl=,FunctionName=,VisibilityTimeout=100" ``` -This JSON configuration sets up two API Gateway resources: +The config is a comma-delimited list of key pairs. Supported keys: -1. `/root` that is mapped to the `RootFunction` Lambda function. -2. `/root/{proxy+}` that is a proxy resource mapped to the `MyOtherLambdaFunction` Lambda function. +| Key | Description | +|-----|-------------| +| `QueueUrl` | The queue to poll. | +| `FunctionName` | Function to invoke. Defaults to the current emulator's function. | +| `BatchSize` | Max messages per batch. | +| `VisibilityTimeout` | Visibility timeout (seconds) for received messages. | +| `DisableMessageDelete` | If `true`, don't delete messages after a successful invoke. | +| `LambdaRuntimeApi` | Runtime API endpoint if the function runs outside this instance. | +| `Region` | AWS region of the queue. | +| `Profile` | AWS profile for credentials. | -The `{proxy+}` syntax in the second path allows the API Gateway to proxy any additional path segments to the integrated Lambda function. +### DynamoDB Streams Event Source -## Example Lambda Function Setup +Resolves the table's latest stream, discovers shards, and polls for records to invoke your function. -Here's a simple Lambda function that adds two numbers together. +``` +dotnet lambda-test-tool start \ + --lambda-emulator-port 5050 \ + --dynamodbstreams-eventsource-config "TableName=,FunctionName=,BatchSize=100" +``` -### 1. Lambda Function Code -This can be implemented in two ways: +The config accepts comma-delimited key pairs, a JSON object, a JSON array, or a path to a JSON file. Supported keys: + +| Key | Description | +|-----|-------------| +| `TableName` | The table whose stream to poll. | +| `FunctionName` | Function to invoke. Defaults to the current emulator's function. | +| `BatchSize` | Max records per batch. | +| `PollingIntervalMs` | Delay between polls, in milliseconds. | +| `LambdaRuntimeApi` | Runtime API endpoint if the function runs outside this instance. | +| `Region` | AWS region of the table. | +| `Profile` | AWS profile for credentials. | + +## Example Lambda Function Setup -#### Option 1: Using Top-Level Statements +A function can be wired to the test tool in two ways. Option 1 is the lowest-friction path and is recommended for getting started. +### Option 1: Top-Level Statements + +The handler is the entry point, launched with a simple `Project` launch profile. ```csharp using Amazon.Lambda.APIGatewayEvents; @@ -211,27 +406,24 @@ using Amazon.Lambda.Core; using Amazon.Lambda.RuntimeSupport; using Amazon.Lambda.Serialization.SystemTextJson; -var Add = (APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) => +var handler = (APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) => { - // Parse x and y from the path parameters var x = int.Parse(request.PathParameters["x"]); var y = int.Parse(request.PathParameters["y"]); return (x + y).ToString(); }; -await LambdaBootstrapBuilder.Create(Add, new CamelCaseLambdaJsonSerializer()) +await LambdaBootstrapBuilder.Create(handler, new CamelCaseLambdaJsonSerializer()) .Build() .RunAsync(); - ``` -Configure the Lambda function to use the test tool: - **Properties/launchSettings.json** -``` + +```json { "profiles": { - "AspireTestFunction": { + "AddLambdaFunction": { "commandName": "Project", "environmentVariables": { "AWS_LAMBDA_RUNTIME_API": "localhost:5050/AddLambdaFunction" @@ -241,8 +433,13 @@ Configure the Lambda function to use the test tool: } ``` -#### Option 2: Using Class Library -``` +Run it with `dotnet run --launch-profile AddLambdaFunction`. A complete project is in [`samples/AddFunctionTopLevel`](samples/AddFunctionTopLevel). + +### Option 2: Class Library + +For a class-library function (a handler method rather than top-level statements), you run the function assembly under the test tool's copy of the Lambda runtime support library. This works the same whether you launch it from the command line or an IDE. + +```csharp using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Core; @@ -259,17 +456,59 @@ public class Function } ``` -Configure the Lambda function to use the test tool: +**1. Make sure dependencies land in the build output.** A class library doesn't copy its NuGet dependencies (e.g. `Amazon.Lambda.Core.dll`) next to the output DLL by default, so add this to the `.csproj`: -**Properties/launchSettings.json** +```xml + + true + ``` + +**2. Build the function** so the `.deps.json` / `.runtimeconfig.json` exist and dependencies are copied: + +``` +dotnet build +``` + +#### Run it from the command line + +Set [`AWS_LAMBDA_RUNTIME_API`](#the-aws_lambda_runtime_api-environment-variable), then launch the function assembly under the test tool's runtime support shim. From the build output directory (e.g. `bin/Debug/net10.0`): + +```bash +# Linux/macOS +export AWS_LAMBDA_RUNTIME_API="localhost:5050/AddLambdaFunction" + +dotnet exec \ + --depsfile ./MyLambdaFunction.deps.json \ + --runtimeconfig ./MyLambdaFunction.runtimeconfig.json \ + "$HOME/.dotnet/tools/.store/amazon.lambda.testtool/{TEST_TOOL_VERSION}/amazon.lambda.testtool/{TEST_TOOL_VERSION}/content/Amazon.Lambda.RuntimeSupport/{TARGET_FRAMEWORK}/Amazon.Lambda.RuntimeSupport.TestTool.dll" \ + "{FUNCTION_HANDLER}" +``` + +On Windows (PowerShell), the shim path is under `$env:USERPROFILE\.dotnet\tools\.store\...`. + +Replace the three placeholders: + +1. **`{TEST_TOOL_VERSION}`** — your installed test tool version (appears **twice** in the `.store` path). Find it with `dotnet lambda-test-tool info` or `dotnet tool list -g`. +2. **`{TARGET_FRAMEWORK}`** — your Lambda project's target framework, e.g. `net10.0`. +3. **`{FUNCTION_HANDLER}`** — your handler in the form `::.::`, e.g. `MyLambdaFunction::MyLambdaFunction.Function::Add`. + +> The runtime support assembly is named `Amazon.Lambda.RuntimeSupport.TestTool.dll` (not `Amazon.Lambda.RuntimeSupport.dll`) to avoid conflicting with the version your function already references. + +#### Run it from an IDE (Visual Studio / Rider) + +If you'd rather press F5, add a launch profile. This wraps the same `dotnet exec` command; the IDE expands `$(Configuration)` and resolves `workingDirectory` for you (plain `dotnet run --launch-profile` does not, so use the command-line form above outside an IDE). + +**Properties/launchSettings.json** + +```json { "profiles": { - "LambdaRuntimeClient_FunctionHandler": { - "workingDirectory": ".\\bin\\$(Configuration)\\net8.0", + "LambdaTestTool": { "commandName": "Executable", - "commandLineArgs": "exec --depsfile ./MyLambdaFunction.deps.json --runtimeconfig ./MyLambdaFunction.runtimeconfig.json %USERPROFILE%/.dotnet/tools/.store/amazon.lambda.testtool/{TEST_TOOL_VERSION}/amazon.lambda.testtool/{TEST_TOOL_VERSION}/content/Amazon.Lambda.RuntimeSupport/{TARGET_FRAMEWORK}/Amazon.Lambda.RuntimeSupport.TestTool.dll MyLambdaFunction::MyLambdaFunction.Function::Add", "executablePath": "dotnet", + "workingDirectory": ".\\bin\\$(Configuration)\\{TARGET_FRAMEWORK}", + "commandLineArgs": "exec --depsfile ./MyLambdaFunction.deps.json --runtimeconfig ./MyLambdaFunction.runtimeconfig.json %USERPROFILE%/.dotnet/tools/.store/amazon.lambda.testtool/{TEST_TOOL_VERSION}/amazon.lambda.testtool/{TEST_TOOL_VERSION}/content/Amazon.Lambda.RuntimeSupport/{TARGET_FRAMEWORK}/Amazon.Lambda.RuntimeSupport.TestTool.dll {FUNCTION_HANDLER}", "environmentVariables": { "AWS_LAMBDA_RUNTIME_API": "localhost:5050/AddLambdaFunction" } @@ -278,68 +517,72 @@ Configure the Lambda function to use the test tool: } ``` -There are three variables you may need to replace: +The same three placeholders apply. On **Linux/macOS**, replace `%USERPROFILE%` with `$HOME`/`~` and use forward slashes throughout the `workingDirectory`. -There are three variables you need to update in the launch settings: +A complete, build-verified example is in [`samples/AddFunctionClassLibrary`](samples/AddFunctionClassLibrary). -1. `{TEST_TOOL_VERSION}` - Replace with the current Amazon.Lambda.TestTool version (e.g., `0.0.3` in the example above) - - This appears in the path: `.store/amazon.lambda.testtool/{TEST_TOOL_VERSION}/amazon.lambda.testtool/{TEST_TOOL_VERSION}/content/` +### The AWS_LAMBDA_RUNTIME_API Environment Variable -2. `{TARGET_FRAMEWORK}` - Replace with your Lambda project's target framework version (e.g., `net8.0` in the example above) - - This appears in two places: - - The working directory: `.\\bin\\$(Configuration)\\{TARGET_FRAMEWORK}` - - The runtime support DLL path: `Amazon.Lambda.RuntimeSupport/{TARGET_FRAMEWORK}/Amazon.Lambda.RuntimeSupport.TestTool.dll` - - **Note: For the test tool the Amazon.Lambda.RuntimeSupport.dll assembly was renamed to Amazon.Lambda.RuntimeSupport.TestTool.dll to avoid conflicts with versions of Amazon.Lambda.RuntimeSupport used by the Lambda function itself.** +This variable tells your function where the Lambda Runtime API emulator is. Its format is: -3. `{FUNCTION_HANDLER}` - Replace with your function's handler using the format: `::.::` - - Example: `MyLambdaFunction::MyLambdaFunction.Function::Add` +``` +host:port/functionName +``` +The host and port must match the Lambda emulator (`--lambda-emulator-port`). In the examples above the emulator runs on `localhost:5050` and the function name is `AddLambdaFunction`, so the value is `localhost:5050/AddLambdaFunction`. +> **Do not add an `http://` prefix** to this value — the function will fail to connect if you do. (This differs from the API Gateway route config's `Endpoint`, which *does* include `http://`.) -### 2. AWS_LAMBDA_RUNTIME_API +## Sample Projects -The `AWS_LAMBDA_RUNTIME_API` environment variable tells the Lambda function where to find the Lambda runtime API endpoint. It has the following format: +Runnable starter projects live under [`samples/`](samples). Each has its own README with exact run steps. -`host:port/functionName` +| Sample | What it shows | +|--------|---------------| +| [`AddFunctionTopLevel`](samples/AddFunctionTopLevel) | The Quick Start: top-level-statements function behind the API Gateway emulator. | +| [`AddFunctionClassLibrary`](samples/AddFunctionClassLibrary) | A class-library function with a pre-filled `Executable` launch profile. | +| [`SQSProcessor`](samples/SQSProcessor) | An `SQSEvent` handler for the SQS event source (and the built-in `sqs.json` sample event). | +| [`ToUpperFunction`](samples/ToUpperFunction) | A minimal, zero-dependency function for exploring the web UI and sample events. | +## .NET Aspire Integration -The host and port should match the port that the lambda emulator is running on. -In this example we will be running the lambda runtime api emulator on `localhost` on port `5050` and our function name will be `AddLambdaFunction`. **Warning**: You should *not* add `http://` prefix to the host (if you do the lambda will fail to connect). +If you use [.NET Aspire](https://learn.microsoft.com/dotnet/aspire/), the AWS integration installs the test tool for you and provides extension methods to configure your Lambda functions and API Gateway emulator in the Aspire AppHost — avoiding the manual install and environment-variable setup described above. -### 3. API Gateway Configuration -To expose this Lambda function through API Gateway, set the APIGATEWAY_EMULATOR_ROUTE_CONFIG: +See the [.NET Aspire integration tracker](https://github.com/aws/integrations-on-dotnet-aspire-for-aws/issues/17) for status and setup steps. -``` -{ - "LambdaResourceName": "AddLambdaFunction", - "HttpMethod": "GET", - "Path": "/add/{x}/{y}" -} -``` +## Troubleshooting -### 4. Testing the Function -1. Start the test tool with both Lambda and API Gateway emulators: -``` -dotnet lambda-test-tool start \ - --lambda-emulator-port 5050 \ - --api-gateway-emulator-port 5051 \ - --api-gateway-emulator-mode HTTPV2 +| Symptom | Cause / Fix | +|---------|-------------| +| `dotnet lambda-test-tool: command not found` | The .NET global tools directory isn't on your `PATH`. See [Prerequisites](#prerequisites), then open a new terminal. | +| Function won't connect to the runtime API | Don't put `http://` in `AWS_LAMBDA_RUNTIME_API`; use `host:port/functionName`. | +| API Gateway requests do nothing / time out | The API Gateway emulator doesn't run your function. Use [Combined Mode](#combined-mode) or point `Endpoint` at a running Lambda Runtime API. | +| HTTP 502 from the API Gateway emulator | The emulator mode doesn't match your handler's request/response type. Match `Rest`/`HttpV1`/`HttpV2` to your handler — see [API Gateway Emulator Mode](#api-gateway-emulator-mode). | +| Invoke fails with a wrong function name | The route config `Endpoint` should be the base URL (`http://localhost:5050`), not include the function name. The function name comes from `LambdaResourceName` / `AWS_LAMBDA_RUNTIME_API`. | +| Class-library profile: "file not found" for the runtime support DLL | The `{TEST_TOOL_VERSION}` or `{TARGET_FRAMEWORK}` in `launchSettings.json` is wrong. Confirm the version with `dotnet lambda-test-tool info`. | +| Port already in use | Choose different `--lambda-emulator-port` / `--api-gateway-emulator-port` values. | -``` -2. Send a test request: -``` -curl -X GET "http://localhost:5051/add/5/3" -H "Content-Type: application/json" -d '"hello world"' -``` +## Known Limitations -Expected response: -``` -8 -``` +This tool is in preview. Notable current limitations: + +- The API Gateway emulator does not run your function — a Lambda Runtime API endpoint must be running separately (or use Combined Mode). +- The class-library launch profile (Option 2) requires manual path configuration and is Windows-oriented in the example. +- Event sources (SQS, DynamoDB Streams) require real AWS resources and credentials. + +Please [open a GitHub issue](https://github.com/aws/aws-lambda-dotnet/issues) if you hit a limitation not listed here. + +## What's New Compared to the Previous Test Tool -## Saving Lambda Requests +This tool is the evolution of the [AWS .NET Mock Lambda Test Tool](https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool), with several improvements: -The Test Tool provides users with the ability to save Lambda requests for quick access. Saved requests will be listed in a drop down above the request input area. +- **API Gateway emulation** — test API Gateway integrations locally. +- **A new function-loading flow** that mirrors the Lambda service more closely, resolving many dependency-loading issues in the older tool. +- **Multiple functions** can share one instance of the test tool. +- **SQS and DynamoDB Streams event sources.** +- **Refreshed web UI** with sample events, saved requests, and theming. +- **[.NET Aspire integration](#net-aspire-integration).** -In order to enable saving requests, you will need to provide a storage path during the test tool startup. +## Getting Help -You can use the command line argument `--config-storage-path ` to specify the storage path. +For questions and problems, please [open a GitHub issue](https://github.com/aws/aws-lambda-dotnet/issues) in this repository. diff --git a/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/AddFunctionClassLibrary.csproj b/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/AddFunctionClassLibrary.csproj new file mode 100644 index 000000000..bd450d1ea --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/AddFunctionClassLibrary.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + Lambda + AddFunctionClassLibrary + + true + + true + + + + + + + + + diff --git a/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/Function.cs b/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/Function.cs new file mode 100644 index 000000000..e2d052489 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/Function.cs @@ -0,0 +1,20 @@ +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.Core; + +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.CamelCaseLambdaJsonSerializer))] + +namespace AddFunctionClassLibrary; + +public class Function +{ + /// + /// Adds the two path parameters {x} and {y} and returns the sum. + /// Handler string: AddFunctionClassLibrary::AddFunctionClassLibrary.Function::Add + /// + public int Add(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) + { + var x = int.Parse(request.PathParameters["x"]); + var y = int.Parse(request.PathParameters["y"]); + return x + y; + } +} diff --git a/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/Properties/launchSettings.json b/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/Properties/launchSettings.json new file mode 100644 index 000000000..74b7f1427 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "LambdaTestTool": { + "commandName": "Executable", + "executablePath": "dotnet", + "workingDirectory": ".\\bin\\$(Configuration)\\net10.0", + "commandLineArgs": "exec --depsfile ./AddFunctionClassLibrary.deps.json --runtimeconfig ./AddFunctionClassLibrary.runtimeconfig.json %USERPROFILE%/.dotnet/tools/.store/amazon.lambda.testtool/{TEST_TOOL_VERSION}/amazon.lambda.testtool/{TEST_TOOL_VERSION}/content/Amazon.Lambda.RuntimeSupport/net10.0/Amazon.Lambda.RuntimeSupport.TestTool.dll AddFunctionClassLibrary::AddFunctionClassLibrary.Function::Add", + "environmentVariables": { + "AWS_LAMBDA_RUNTIME_API": "localhost:5050/AddLambdaFunction" + } + } + } +} diff --git a/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/README.md b/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/README.md new file mode 100644 index 000000000..bc0ae0875 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/AddFunctionClassLibrary/README.md @@ -0,0 +1,78 @@ +# AddFunctionClassLibrary + +The same "add two numbers" function as [`AddFunctionTopLevel`](../AddFunctionTopLevel), but as a **class library** (a handler method rather than top-level statements). A class-library function is launched by running its assembly under the test tool's copy of the Lambda runtime support library. You can do this from the command line or from an IDE. + +Handler: `AddFunctionClassLibrary::AddFunctionClassLibrary.Function::Add` + +## Setup (once) + +The `.csproj` sets `true` so the function's NuGet dependencies (e.g. `Amazon.Lambda.Core.dll`) are copied next to the output DLL — required for the command-line launch below. + +Find your installed test tool version, which you'll substitute for `{TEST_TOOL_VERSION}`: + +``` +dotnet lambda-test-tool info +``` + +(or `dotnet tool list -g`). For example, `0.15.0`. + +> The runtime support assembly is `Amazon.Lambda.RuntimeSupport.TestTool.dll` — renamed from `Amazon.Lambda.RuntimeSupport.dll` to avoid conflicting with the version your function references. + +## Run it (command line) + +**1. Build the function:** + +``` +dotnet build +``` + +**2. Set the API Gateway route** (this directory): + +```bash +# Linux/macOS +export APIGATEWAY_EMULATOR_ROUTE_CONFIG='{"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"http://localhost:5050"}' +``` + +```powershell +# Windows (PowerShell) +$env:APIGATEWAY_EMULATOR_ROUTE_CONFIG='{"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"http://localhost:5050"}' +``` + +**3. Start the test tool** (same terminal): + +``` +dotnet lambda-test-tool start --lambda-emulator-port 5050 --api-gateway-emulator-port 5051 --api-gateway-emulator-mode HttpV2 +``` + +**4. Start the function** from its build output directory (separate terminal), replacing `{TEST_TOOL_VERSION}` with your installed version: + +```bash +# Linux/macOS +cd bin/Debug/net10.0 +export AWS_LAMBDA_RUNTIME_API="localhost:5050/AddLambdaFunction" + +dotnet exec \ + --depsfile ./AddFunctionClassLibrary.deps.json \ + --runtimeconfig ./AddFunctionClassLibrary.runtimeconfig.json \ + "$HOME/.dotnet/tools/.store/amazon.lambda.testtool/{TEST_TOOL_VERSION}/amazon.lambda.testtool/{TEST_TOOL_VERSION}/content/Amazon.Lambda.RuntimeSupport/net10.0/Amazon.Lambda.RuntimeSupport.TestTool.dll" \ + "AddFunctionClassLibrary::AddFunctionClassLibrary.Function::Add" +``` + +**5. Invoke it:** + +``` +curl "http://localhost:5051/add/5/3" +# => 8 +``` + +## Run it (IDE: Visual Studio / Rider) + +The committed [`launchSettings.json`](Properties/launchSettings.json) wraps the same command as an `Executable` profile you can launch with F5. Before using it, replace `{TEST_TOOL_VERSION}` (it appears **twice** in the `commandLineArgs` `.store` path) with your installed version. + +The other values are already set for this project: target framework `net10.0`, deps/runtimeconfig file names, and the handler string. + +> This profile relies on the IDE expanding `$(Configuration)` and resolving `workingDirectory`. Plain `dotnet run --launch-profile` does **not** do this — use the command-line steps above outside an IDE. + +> The profile is written for **Windows** (`%USERPROFILE%`, backslashes). On **Linux/macOS**, replace `%USERPROFILE%` with `$HOME` and use forward slashes in `workingDirectory` (`./bin/$(Configuration)/net10.0`). + +Then set `APIGATEWAY_EMULATOR_ROUTE_CONFIG`, start the test tool (steps 2–3 above), press F5 on the `LambdaTestTool` profile, and invoke with the step 5 curl. diff --git a/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/AddFunctionTopLevel.csproj b/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/AddFunctionTopLevel.csproj new file mode 100644 index 000000000..196b22cb2 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/AddFunctionTopLevel.csproj @@ -0,0 +1,19 @@ + + + + Exe + net10.0 + enable + enable + Lambda + AddFunctionTopLevel + + + + + + + + + + diff --git a/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/Program.cs b/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/Program.cs new file mode 100644 index 000000000..950153aa7 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/Program.cs @@ -0,0 +1,17 @@ +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +// Adds the two path parameters {x} and {y} and returns the sum. +// Uses the HTTP API v2 request shape, so run the API Gateway emulator in HttpV2 mode. +var handler = (APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) => +{ + var x = int.Parse(request.PathParameters["x"]); + var y = int.Parse(request.PathParameters["y"]); + return (x + y).ToString(); +}; + +await LambdaBootstrapBuilder.Create(handler, new CamelCaseLambdaJsonSerializer()) + .Build() + .RunAsync(); diff --git a/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/Properties/launchSettings.json b/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/Properties/launchSettings.json new file mode 100644 index 000000000..3751fdf1d --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "AddLambdaFunction": { + "commandName": "Project", + "environmentVariables": { + "AWS_LAMBDA_RUNTIME_API": "localhost:5050/AddLambdaFunction" + } + } + } +} diff --git a/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/README.md b/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/README.md new file mode 100644 index 000000000..15bff3210 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/AddFunctionTopLevel/README.md @@ -0,0 +1,38 @@ +# AddFunctionTopLevel + +The [Quick Start](../../README.md#quick-start) function: a top-level-statements Lambda that adds two numbers, invoked through the API Gateway emulator. Uses the HTTP API v2 request shape, so the API Gateway emulator runs in `HttpV2` mode. + +## Run it + +**1. In this directory, set the API Gateway route** the emulator should expose: + +```bash +# Linux/macOS +export APIGATEWAY_EMULATOR_ROUTE_CONFIG='{"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"http://localhost:5050"}' +``` + +```powershell +# Windows (PowerShell) +$env:APIGATEWAY_EMULATOR_ROUTE_CONFIG='{"LambdaResourceName":"AddLambdaFunction","HttpMethod":"Get","Path":"/add/{x}/{y}","Endpoint":"http://localhost:5050"}' +``` + +**2. Start the test tool** (same terminal): + +``` +dotnet lambda-test-tool start --lambda-emulator-port 5050 --api-gateway-emulator-port 5051 --api-gateway-emulator-mode HttpV2 +``` + +**3. Start the function** (separate terminal, this directory): + +``` +dotnet run --launch-profile AddLambdaFunction +``` + +**4. Invoke it:** + +``` +curl "http://localhost:5051/add/5/3" +# => 8 +``` + +You can also invoke the function directly (without API Gateway) from the web UI that opens at `http://localhost:5050`. diff --git a/Tools/LambdaTestTool-v2/samples/README.md b/Tools/LambdaTestTool-v2/samples/README.md new file mode 100644 index 000000000..0bb3e7018 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/README.md @@ -0,0 +1,17 @@ +# Lambda Test Tool v2 — Sample Projects + +Runnable starter functions for the [AWS Lambda Test Tool](../README.md). Each is a minimal, self-contained project with its own README and a ready-to-use launch profile. + +| Sample | What it shows | Emulator setup | +|--------|---------------|----------------| +| [`AddFunctionTopLevel`](AddFunctionTopLevel) | Top-level-statements function behind the API Gateway emulator (the [Quick Start](../README.md#quick-start)). | Lambda + API Gateway (`HttpV2`) | +| [`AddFunctionClassLibrary`](AddFunctionClassLibrary) | A class-library function with a pre-filled `Executable` launch profile. | Lambda + API Gateway (`HttpV2`) | +| [`SQSProcessor`](SQSProcessor) | An `SQSEvent` handler, testable via the SQS event source or the built-in `sqs.json` sample event. | Lambda (+ optional SQS event source) | +| [`ToUpperFunction`](ToUpperFunction) | A minimal, zero-dependency function for exploring the web UI and sample events. | Lambda only | + +## Prerequisites + +- .NET 10 SDK (the samples target `net10.0`). To build them with an older SDK, change `` to `net8.0` or `net9.0`. +- The test tool installed: `dotnet tool install -g amazon.lambda.testtool` (see the [main README](../README.md#prerequisites)). + +Start with [`AddFunctionTopLevel`](AddFunctionTopLevel) if you're new — it's the lowest-friction path from install to a working invocation. diff --git a/Tools/LambdaTestTool-v2/samples/SQSProcessor/Program.cs b/Tools/LambdaTestTool-v2/samples/SQSProcessor/Program.cs new file mode 100644 index 000000000..aa770b177 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/SQSProcessor/Program.cs @@ -0,0 +1,21 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; +using Amazon.Lambda.SQSEvents; + +// Processes a batch of SQS messages, logging each message body. +// Test it either with the built-in "sqs.json" sample event from the web UI, +// or by wiring a real queue with --sqs-eventsource-config (see this sample's README). +var handler = (SQSEvent evnt, ILambdaContext context) => +{ + foreach (var message in evnt.Records) + { + context.Logger.LogLine($"Processing message {message.MessageId}: {message.Body}"); + } + + context.Logger.LogLine($"Processed {evnt.Records.Count} message(s)."); +}; + +await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) + .Build() + .RunAsync(); diff --git a/Tools/LambdaTestTool-v2/samples/SQSProcessor/Properties/launchSettings.json b/Tools/LambdaTestTool-v2/samples/SQSProcessor/Properties/launchSettings.json new file mode 100644 index 000000000..16b3a27fd --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/SQSProcessor/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "SQSProcessor": { + "commandName": "Project", + "environmentVariables": { + "AWS_LAMBDA_RUNTIME_API": "localhost:5050/SQSProcessor" + } + } + } +} diff --git a/Tools/LambdaTestTool-v2/samples/SQSProcessor/README.md b/Tools/LambdaTestTool-v2/samples/SQSProcessor/README.md new file mode 100644 index 000000000..ebce2591b --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/SQSProcessor/README.md @@ -0,0 +1,39 @@ +# SQSProcessor + +An `SQSEvent` handler that logs each message body. Demonstrates the shape of an SQS-triggered Lambda and the [SQS event source](../../README.md#sqs-event-source). You can test it two ways. + +## Option A: With the built-in sample event (no AWS needed) + +**1. Start the Lambda emulator:** + +``` +dotnet lambda-test-tool start --lambda-emulator-port 5050 +``` + +**2. Start the function** (separate terminal, this directory): + +``` +dotnet run --launch-profile SQSProcessor +``` + +**3. In the web UI** (`http://localhost:5050`), select `SQSProcessor`, choose the built-in **`sqs.json`** sample from the Example Requests dropdown, and click **Invoke**. The message body is logged in the function's console. + +## Option B: With a real SQS queue (event source polling) + +This polls an actual queue using your AWS credentials. + +**1. Start the function** (separate terminal, this directory): + +``` +dotnet run --launch-profile SQSProcessor +``` + +**2. Start the test tool with the SQS event source** pointed at your queue: + +``` +dotnet lambda-test-tool start \ + --lambda-emulator-port 5050 \ + --sqs-eventsource-config "QueueUrl=https://sqs..amazonaws.com//,FunctionName=SQSProcessor,Region=" +``` + +Send a message to the queue; the tool batches it into an `SQSEvent`, invokes `SQSProcessor`, and (on success) deletes the message. See the [main README](../../README.md#sqs-event-source) for all supported config keys. diff --git a/Tools/LambdaTestTool-v2/samples/SQSProcessor/SQSProcessor.csproj b/Tools/LambdaTestTool-v2/samples/SQSProcessor/SQSProcessor.csproj new file mode 100644 index 000000000..3b377c119 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/SQSProcessor/SQSProcessor.csproj @@ -0,0 +1,19 @@ + + + + Exe + net10.0 + enable + enable + Lambda + SQSProcessor + + + + + + + + + + diff --git a/Tools/LambdaTestTool-v2/samples/ToUpperFunction/Program.cs b/Tools/LambdaTestTool-v2/samples/ToUpperFunction/Program.cs new file mode 100644 index 000000000..5b99b40ba --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/ToUpperFunction/Program.cs @@ -0,0 +1,21 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +// A minimal function: uppercases the input string. +// Send the string "error" to see how the tool renders a thrown exception. +var handler = (string input, ILambdaContext context) => +{ + context.Logger.LogLine($"Executing function with input: {input}"); + + if (string.Equals("error", input, StringComparison.OrdinalIgnoreCase)) + { + throw new Exception("Forced error to demonstrate error rendering."); + } + + return input?.ToUpper(); +}; + +await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) + .Build() + .RunAsync(); diff --git a/Tools/LambdaTestTool-v2/samples/ToUpperFunction/Properties/launchSettings.json b/Tools/LambdaTestTool-v2/samples/ToUpperFunction/Properties/launchSettings.json new file mode 100644 index 000000000..eaed33dda --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/ToUpperFunction/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "ToUpperFunction": { + "commandName": "Project", + "environmentVariables": { + "AWS_LAMBDA_RUNTIME_API": "localhost:5050/ToUpperFunction" + } + } + } +} diff --git a/Tools/LambdaTestTool-v2/samples/ToUpperFunction/README.md b/Tools/LambdaTestTool-v2/samples/ToUpperFunction/README.md new file mode 100644 index 000000000..55b7f1bdf --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/ToUpperFunction/README.md @@ -0,0 +1,27 @@ +# ToUpperFunction + +The gentlest sample: a zero-dependency function that uppercases its input string. Good for a first look at the [web UI](../../README.md#using-the-web-ui) — no API Gateway or AWS resources needed. + +## Run it + +**1. Start the Lambda emulator:** + +``` +dotnet lambda-test-tool start --lambda-emulator-port 5050 +``` + +The web UI opens at `http://localhost:5050`. + +**2. Start the function** (separate terminal, this directory): + +``` +dotnet run --launch-profile ToUpperFunction +``` + +**3. Invoke it from the web UI:** + +1. Select `ToUpperFunction` as the function. +2. In the Function Input editor, enter a JSON string, e.g. `"hello world"`. +3. Click **Invoke**. The response is `"HELLO WORLD"`. + +Try `"error"` as the input to see how the tool renders a thrown exception and stack trace. diff --git a/Tools/LambdaTestTool-v2/samples/ToUpperFunction/ToUpperFunction.csproj b/Tools/LambdaTestTool-v2/samples/ToUpperFunction/ToUpperFunction.csproj new file mode 100644 index 000000000..37ec8afb8 --- /dev/null +++ b/Tools/LambdaTestTool-v2/samples/ToUpperFunction/ToUpperFunction.csproj @@ -0,0 +1,18 @@ + + + + Exe + net10.0 + enable + enable + Lambda + ToUpperFunction + + + + + + + + + From 98a1ebd78a2b8ad6574faf380e9f9e6d42562a0e Mon Sep 17 00:00:00 2001 From: Daniel Pinheiro Date: Mon, 17 Aug 2026 11:31:26 -0700 Subject: [PATCH 06/22] Fix NativeAOT IL3050/IL3053 warnings from net10.0 assemblies (#2531) The trim/AOT PropertyGroup (IsTrimmable, EnableTrimAnalyzer) was gated on '$(TargetFramework)' == 'net8.0'. Once net10.0 was added to DefaultPackageTargets, the net10.0 assembly shipped unmarked, so a net10 NativeAOT consumer resolving that asset gets the assembly-level IL3053 rollup from Amazon.Lambda.Serialization.SystemTextJson. Drop the condition so all target frameworks are marked trimmable. Applied the same fix to APIGatewayEvents, SQSEvents, SNSEvents and SimpleEmailEvents, which had the identical net8.0-only gating. --- .../038901ab-8453-4e79-a5b0-82b2ffa55d3a.json | 39 +++++++++++++++++++ .../Amazon.Lambda.APIGatewayEvents.csproj | 2 +- .../Amazon.Lambda.SNSEvents.csproj | 2 +- .../Amazon.Lambda.SQSEvents.csproj | 2 +- ...Lambda.Serialization.SystemTextJson.csproj | 2 +- .../Amazon.Lambda.SimpleEmailEvents.csproj | 2 +- 6 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 .autover/changes/038901ab-8453-4e79-a5b0-82b2ffa55d3a.json diff --git a/.autover/changes/038901ab-8453-4e79-a5b0-82b2ffa55d3a.json b/.autover/changes/038901ab-8453-4e79-a5b0-82b2ffa55d3a.json new file mode 100644 index 000000000..3ff19c466 --- /dev/null +++ b/.autover/changes/038901ab-8453-4e79-a5b0-82b2ffa55d3a.json @@ -0,0 +1,39 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.Serialization.SystemTextJson", + "Type": "Patch", + "ChangelogMessages": [ + "Fixed NativeAOT IL3050/IL3053 analysis warnings by marking all target frameworks as trimmable. Previously only the net8.0 target was marked, so the net10.0 assembly was not trimmable." + ] + }, + { + "Name": "Amazon.Lambda.APIGatewayEvents", + "Type": "Patch", + "ChangelogMessages": [ + "Marked all target frameworks as trimmable. Previously only the net8.0 target was marked, so the net10.0 assembly was not trimmable." + ] + }, + { + "Name": "Amazon.Lambda.SQSEvents", + "Type": "Patch", + "ChangelogMessages": [ + "Marked all target frameworks as trimmable. Previously only the net8.0 target was marked, so the net10.0 assembly was not trimmable." + ] + }, + { + "Name": "Amazon.Lambda.SNSEvents", + "Type": "Patch", + "ChangelogMessages": [ + "Marked all target frameworks as trimmable. Previously only the net8.0 target was marked, so the net10.0 assembly was not trimmable." + ] + }, + { + "Name": "Amazon.Lambda.SimpleEmailEvents", + "Type": "Patch", + "ChangelogMessages": [ + "Marked all target frameworks as trimmable. Previously only the net8.0 target was marked, so the net10.0 assembly was not trimmable." + ] + } + ] +} diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj b/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj index 3aabb9811..d9368937f 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj @@ -28,7 +28,7 @@ - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj b/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj index f93f0830c..2e6f7d33e 100644 --- a/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj +++ b/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj @@ -12,7 +12,7 @@ AWS;Amazon;Lambda - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj b/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj index a5b84398b..0bba176a2 100644 --- a/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj +++ b/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj @@ -12,7 +12,7 @@ AWS;Amazon;Lambda - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj index 679bc30ac..bc899ee33 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj +++ b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj @@ -26,7 +26,7 @@ - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Amazon.Lambda.SimpleEmailEvents.csproj b/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Amazon.Lambda.SimpleEmailEvents.csproj index 806326c29..ee46cef45 100644 --- a/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Amazon.Lambda.SimpleEmailEvents.csproj +++ b/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Amazon.Lambda.SimpleEmailEvents.csproj @@ -12,7 +12,7 @@ AWS;Amazon;Lambda - + IL2026,IL2067,IL2075 true true From ac90481f16b410180488a4f7943254d337664a42 Mon Sep 17 00:00:00 2001 From: aws-sdk-dotnet-automation Date: Mon, 17 Aug 2026 20:04:53 +0000 Subject: [PATCH 07/22] release_2026-08-17 --- .../Amazon.Lambda.APIGatewayEvents.csproj | 2 +- .../Amazon.Lambda.AspNetCoreServer.Hosting.csproj | 2 +- .../Amazon.Lambda.AspNetCoreServer.csproj | 2 +- .../src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj | 2 +- .../src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj | 2 +- .../Amazon.Lambda.Serialization.SystemTextJson.csproj | 2 +- .../Amazon.Lambda.SimpleEmailEvents.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj b/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj index d9368937f..b1a539a66 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj @@ -6,7 +6,7 @@ $(DefaultPackageTargets) Amazon Lambda .NET Core support - API Gateway package. Amazon.Lambda.APIGatewayEvents - 3.0.0 + 3.0.1 Amazon.Lambda.APIGatewayEvents Amazon.Lambda.APIGatewayEvents AWS;Amazon;Lambda diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj index b9e23b7c6..60fca02ed 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj @@ -7,7 +7,7 @@ $(DefaultPackageTargets) enable enable - 2.2.0 + 2.2.1 README.md Amazon.Lambda.AspNetCoreServer.Hosting Amazon.Lambda.AspNetCoreServer.Hosting diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj index 58affd887..8caffa7f0 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj @@ -6,7 +6,7 @@ Amazon.Lambda.AspNetCoreServer makes it easy to run ASP.NET Core Web API applications as AWS Lambda functions. $(DefaultPackageTargets) Amazon.Lambda.AspNetCoreServer - 10.2.0 + 10.2.1 Amazon.Lambda.AspNetCoreServer Amazon.Lambda.AspNetCoreServer AWS;Amazon;Lambda;aspnetcore diff --git a/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj b/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj index 2e6f7d33e..7d64c06fb 100644 --- a/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj +++ b/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj @@ -6,7 +6,7 @@ Amazon Lambda .NET Core support - SNSEvents package. $(DefaultPackageTargets) Amazon.Lambda.SNSEvents - 3.0.0 + 3.0.1 Amazon.Lambda.SNSEvents Amazon.Lambda.SNSEvents AWS;Amazon;Lambda diff --git a/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj b/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj index 0bba176a2..0019cba3e 100644 --- a/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj +++ b/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj @@ -6,7 +6,7 @@ Amazon Lambda .NET Core support - SQSEvents package. $(DefaultPackageTargets) Amazon.Lambda.SQSEvents - 3.0.0 + 3.0.1 Amazon.Lambda.SQSEvents Amazon.Lambda.SQSEvents AWS;Amazon;Lambda diff --git a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj index bc899ee33..05df21d38 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj +++ b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj @@ -9,7 +9,7 @@ Amazon.Lambda.Serialization.SystemTextJson Amazon.Lambda.Serialization.SystemTextJson AWS;Amazon;Lambda - 3.0.0 + 3.0.1 README.md