From 4d13ee5a177ac8389f8370a0cb52ef35a53867ce Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 6 Aug 2026 13:55:41 -0400 Subject: [PATCH 1/4] 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. --- .github/workflows/conformance-tests.yml | 137 ++++++ ...a.DurableExecution.IntegrationTests.csproj | 4 + .../Conformance/.gitignore | 15 + .../Conformance/README.md | 104 ++++ .../Conformance/scripts/build_examples.sh | 84 ++++ .../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_step.yaml | 452 ++++++++++++++++++ 48 files changed, 2388 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/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_step.yaml diff --git a/.github/workflows/conformance-tests.yml b/.github/workflows/conformance-tests.yml new file mode 100644 index 000000000..896d41ed1 --- /dev/null +++ b/.github/workflows/conformance-tests.yml @@ -0,0 +1,137 @@ +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@v4 + + - 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@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.0.x" + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Setup SAM CLI + uses: aws-actions/setup-sam@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-east-1' }} + + - 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 + 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-east-1' }} \ + --history-dir history-${{ matrix.suite }} \ + --report junit \ + --report-file report-${{ matrix.suite }} \ + --no-cleanup + + - name: Upload conformance report + if: always() + uses: actions/upload-artifact@v4 + 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..d5f4dedcd --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md @@ -0,0 +1,104 @@ +# 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 + +| Suite | Requirements | Handlers implemented | +|-------|-------------|----------------------| +| `step` | 1-1 .. 1-20 | ✅ all 20 | +| `wait`, `child`, `callback`, `invoke`, `parallel`, `map`, `wait_for_callback`, `wait_for_condition` | — | not yet scaffolded | + +Retry requirements (`1-11`, `1-13`, `1-14`, `1-15`, `1-18`) count attempts +across separate invocations, which the replay model cannot hold in memory, so +those handlers use the `AttemptsTable` DynamoDB table declared in the template. + +## 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: + +```bash +# 1. Publish the step handlers into publish// +./scripts/build_examples.sh step + +# 2. Deploy + invoke + validate the step 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 +``` + +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 secrets `TEST_ROLE_ARN` (SAM-capable deploy role) and +`TEST_LAMBDA_EXECUTION_ROLE_ARN`, plus the `AWS_REGION` variable. + +## 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. 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 100644 index 000000000..d83a81246 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/build_examples.sh @@ -0,0 +1,84 @@ +#!/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 + +echo "Build completed successfully!" >&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_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 From 8d7d366b823de1ebd186e7b5cf891d2442c29520 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 6 Aug 2026 15:04:47 -0400 Subject: [PATCH 2/4] =?UTF-8?q?Port=20remaining=208=20conformance=20suites?= =?UTF-8?q?=20(.NET)=20=E2=80=94=20all=209=20suites=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../Conformance/README.md | 49 +- .../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 | 24 + .../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_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 + 258 files changed, 10301 insertions(+), 11 deletions(-) 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 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_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/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md index d5f4dedcd..374ef0d98 100644 --- a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md @@ -42,14 +42,37 @@ Conformance/ ## Coverage -| Suite | Requirements | Handlers implemented | -|-------|-------------|----------------------| -| `step` | 1-1 .. 1-20 | ✅ all 20 | -| `wait`, `child`, `callback`, `invoke`, `parallel`, `map`, `wait_for_callback`, `wait_for_condition` | — | not yet scaffolded | - -Retry requirements (`1-11`, `1-13`, `1-14`, `1-15`, `1-18`) count attempts -across separate invocations, which the replay model cannot hold in memory, so -those handlers use the `AttemptsTable` DynamoDB table declared in the template. +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 @@ -64,13 +87,14 @@ those handlers use the `AttemptsTable` DynamoDB table declared in the template. ## Running locally -From this directory: +From this directory (swap `step` for any suite name): ```bash -# 1. Publish the step handlers into publish// +# 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 step suite +# 2. Deploy + invoke + validate the suite unset AWS_PROFILE python -m aws_durable_execution_conformance_tests.app \ --template template_step.yaml \ @@ -82,6 +106,9 @@ python -m aws_durable_execution_conformance_tests.app \ --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`. 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 index d83a81246..4ce59e368 100644 --- a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/build_examples.sh +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/scripts/build_examples.sh @@ -80,5 +80,29 @@ MKEOF echo "" >&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/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_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 545a9e5180a742c5e7f43a849daafe92adeaefee Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 6 Aug 2026 16:13:55 -0400 Subject: [PATCH 3/4] 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). --- .github/workflows/conformance-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/conformance-tests.yml b/.github/workflows/conformance-tests.yml index 896d41ed1..b17d3967c 100644 --- a/.github/workflows/conformance-tests.yml +++ b/.github/workflows/conformance-tests.yml @@ -90,7 +90,7 @@ jobs: # 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-east-1' }} + aws-region: ${{ vars.CONFORMANCE_AWS_REGION || 'us-west-2' }} - name: Inject Lambda execution role into template env: @@ -120,7 +120,7 @@ jobs: --language dotnet \ --suite ${{ matrix.suite }} \ --name conformance-dotnet-${SUITE_SLUG} \ - --region ${{ vars.CONFORMANCE_AWS_REGION || 'us-east-1' }} \ + --region ${{ vars.CONFORMANCE_AWS_REGION || 'us-west-2' }} \ --history-dir history-${{ matrix.suite }} \ --report junit \ --report-file report-${{ matrix.suite }} \ From 259395bf34230063ae5231a1283ebe0f14759de4 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 6 Aug 2026 18:33:36 -0400 Subject: [PATCH 4/4] Gate conformance CI on missing coverage (--fail-on failed+uncovered) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .github/workflows/conformance-tests.yml | 8 +++++ .../Conformance/README.md | 30 +++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.github/workflows/conformance-tests.yml b/.github/workflows/conformance-tests.yml index b17d3967c..b461705b5 100644 --- a/.github/workflows/conformance-tests.yml +++ b/.github/workflows/conformance-tests.yml @@ -114,6 +114,13 @@ jobs: 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 \ @@ -124,6 +131,7 @@ jobs: --history-dir history-${{ matrix.suite }} \ --report junit \ --report-file report-${{ matrix.suite }} \ + --fail-on failed+uncovered \ --no-cleanup - name: Upload conformance report diff --git a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md index 374ef0d98..d4ce87efd 100644 --- a/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md +++ b/Libraries/test/Amazon.Lambda.DurableExecution.IntegrationTests/Conformance/README.md @@ -118,8 +118,23 @@ The checked-in template is self-contained (it creates its own `.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 secrets `TEST_ROLE_ARN` (SAM-capable deploy role) and -`TEST_LAMBDA_EXECUTION_ROLE_ARN`, plus the `AWS_REGION` variable. +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 @@ -129,3 +144,14 @@ the repository secrets `TEST_ROLE_ARN` (SAM-capable deploy role) and `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.