-
Notifications
You must be signed in to change notification settings - Fork 20
feat: add Python conformance test package #556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| name: Conformance Tests | ||
|
|
||
| # Full-integration conformance run: discovers suites from template_<suite>.yaml, | ||
| # builds the Python handlers against the local monorepo SDK, and runs each suite | ||
| # as its own parallel matrix job with the pinned language-agnostic runner. | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: ["main"] | ||
| paths: | ||
| - "packages/aws-durable-execution-sdk-python-conformance-tests/**" | ||
| - ".github/workflows/conformance-tests.yml" | ||
| workflow_dispatch: | ||
| inputs: | ||
| region: | ||
| description: "AWS Region for deploying and running conformance tests" | ||
| required: false | ||
| default: us-west-2 | ||
| type: string | ||
|
|
||
| env: | ||
| AWS_REGION: ${{ github.event.inputs.region || 'us-west-2' }} | ||
|
|
||
| concurrency: | ||
| group: ${{ github.head_ref || github.ref_name || github.run_id }}-conformance | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| discover_suites: | ||
| name: discover conformance suites | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| suites: ${{ steps.discover.outputs.suites }} | ||
| steps: | ||
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
|
|
||
| - name: Discover suites from templates | ||
| id: discover | ||
| working-directory: packages/aws-durable-execution-sdk-python-conformance-tests | ||
| run: echo "suites=$(python3 scripts/discover_suites.py)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| conformance: | ||
| name: conformance (${{ matrix.suite }}) | ||
| needs: discover_suites | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| id-token: write # Required for AWS OIDC credentials | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| suite: ${{ fromJSON(needs.discover_suites.outputs.suites) }} | ||
| defaults: | ||
| run: | ||
| working-directory: packages/aws-durable-execution-sdk-python-conformance-tests | ||
| steps: | ||
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
|
|
||
| - name: Setup Python | ||
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | ||
| with: | ||
| python-version: "3.14" | ||
|
|
||
| - name: Configure AWS credentials | ||
| uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2 | ||
| with: | ||
| role-to-assume: "${{ secrets.TEST_ROLE_ARN }}" | ||
| role-session-name: pythonConformanceTest | ||
| aws-region: ${{ env.AWS_REGION }} | ||
|
|
||
| - name: Verify AWS test account | ||
| env: | ||
| TEST_ACCOUNT_ID: ${{ secrets.TEST_ACCOUNT_ID }} | ||
| run: | | ||
| ACTUAL_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) | ||
| if [ "$ACTUAL_ACCOUNT_ID" != "$TEST_ACCOUNT_ID" ]; then | ||
| echo "Expected AWS account $TEST_ACCOUNT_ID but assumed into $ACTUAL_ACCOUNT_ID" | ||
| exit 1 | ||
| fi | ||
| echo "Using AWS test account $ACTUAL_ACCOUNT_ID" | ||
|
|
||
| - name: Setup SAM CLI | ||
| uses: aws-actions/setup-sam@89ddb14d60e682855e3fea4be85b3c56485de310 # v3 | ||
|
|
||
| - name: Build conformance handlers | ||
| run: python3 scripts/build_examples.py | ||
|
|
||
| - name: Install conformance runner | ||
| run: pip install aws-durable-execution-conformance-tests==0.1.0 | ||
|
|
||
| - name: Inject Lambda execution role into template | ||
| env: | ||
| ROLE_ARN: ${{ secrets.TEST_LAMBDA_EXECUTION_ROLE_ARN }} | ||
| run: | | ||
| if [ -z "$ROLE_ARN" ]; then | ||
| echo "TEST_LAMBDA_EXECUTION_ROLE_ARN not set; template will create its own role." | ||
| exit 0 | ||
| fi | ||
| python3 scripts/inject_execution_role.py \ | ||
| --template template_${{ matrix.suite }}.yaml \ | ||
| --role-arn "$ROLE_ARN" | ||
|
|
||
| - name: Run conformance suite | ||
| run: | | ||
| python -m aws_durable_execution_conformance_tests.app \ | ||
| --template template_${{ matrix.suite }}.yaml \ | ||
| --language python \ | ||
| --suite ${{ matrix.suite }} \ | ||
| --name conf-py-${{ matrix.suite }}-${{ github.run_id }} \ | ||
| --region ${{ env.AWS_REGION }} \ | ||
| --history-dir history-${{ matrix.suite }} \ | ||
| --report junit \ | ||
| --report-file report-${{ matrix.suite }} | ||
|
|
||
| - name: Upload conformance report | ||
| if: always() | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| with: | ||
| name: conformance-report-${{ matrix.suite }} | ||
| path: | | ||
| packages/aws-durable-execution-sdk-python-conformance-tests/report-${{ matrix.suite }}.xml | ||
| packages/aws-durable-execution-sdk-python-conformance-tests/history-${{ matrix.suite }}/ | ||
| if-no-files-found: warn | ||
9 changes: 9 additions & 0 deletions
9
packages/aws-durable-execution-sdk-python-conformance-tests/.gitignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| build/ | ||
| lambda-build/ | ||
| dist/ | ||
| .aws-sam/ | ||
| history-*/ | ||
| report-*.xml | ||
| report-*.json | ||
| __pycache__/ | ||
| *.pyc |
121 changes: 121 additions & 0 deletions
121
packages/aws-durable-execution-sdk-python-conformance-tests/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| # Durable Execution Python SDK — Conformance Tests | ||
|
|
||
| Cross-SDK **conformance test handlers** for the Durable Execution Python SDK. | ||
| These handlers deploy as AWS Lambda functions and are exercised by the | ||
| language-agnostic conformance runner | ||
| [`aws-durable-execution-conformance-tests`](https://pypi.org/project/aws-durable-execution-conformance-tests/), | ||
| which invokes each function, pulls its execution history, and asserts it matches | ||
| the shared requirement specification. | ||
|
|
||
| The runner and requirement specifications are maintained in | ||
| [`aws/aws-durable-execution-conformance-tests`](https://github.com/aws/aws-durable-execution-conformance-tests). | ||
| This package owns the Python handlers and SAM templates that wire them to | ||
| requirement IDs. | ||
|
|
||
| ## Layout | ||
|
|
||
| ``` | ||
| handlers/ | ||
| <suite>/ # one .py handler per conformance scenario | ||
| template_<suite>.yaml # maps suite handlers to requirement IDs | ||
| scripts/ | ||
| discover_suites.py # derives supported suites from templates | ||
| build_examples.py # assembles lambda-build/ from the local monorepo SDK | ||
| inject_execution_role.py # CI: point functions at a pre-existing role | ||
| tests/ # unit tests for the scripts | ||
| ``` | ||
|
|
||
| Each `template_<suite>.yaml` is a self-contained deployment for one conformance | ||
| suite. Templates are the single source of truth: the local build and GitHub | ||
| Actions matrix discover suites from them automatically. | ||
|
|
||
| ## How a handler maps to a requirement | ||
|
|
||
| The link is the `TestingMetadata.TestDescription` field on each function in the | ||
| SAM template: | ||
|
|
||
| ```yaml | ||
| RequirementCase: | ||
| Type: AWS::Serverless::Function | ||
| TestingMetadata: | ||
| TestDescription: ["<requirement-id>"] | ||
| Properties: | ||
| CodeUri: lambda-build/ | ||
| Handler: <suite>.<handler_module>.handler | ||
| Role: !GetAtt DurableFunctionRole.Arn | ||
| DurableConfig: | ||
| RetentionPeriodInDays: 7 | ||
| ExecutionTimeout: 300 | ||
| ``` | ||
|
|
||
| The runner invokes the function once per requirement ID using the requirement's | ||
| `Input`, then compares the execution history with `ExpectedExecutionHistory`. | ||
|
|
||
| ## Building locally | ||
|
|
||
| `scripts/build_examples.py` assembles `lambda-build/` from the local monorepo | ||
| SDK source, not a PyPI release. `boto3` is provided by the Lambda runtime and is | ||
| not vendored. | ||
|
|
||
| ```bash | ||
| cd packages/aws-durable-execution-sdk-python-conformance-tests | ||
| python3 scripts/build_examples.py | ||
| ``` | ||
|
|
||
| The script discovers every `template_<suite>.yaml`, verifies a matching | ||
| `handlers/<suite>/` directory exists, and copies all discovered suites: | ||
|
|
||
| ``` | ||
| lambda-build/ | ||
| aws_durable_execution_sdk_python/ | ||
| <suite>/ | ||
| ``` | ||
|
|
||
| ## Running a suite | ||
|
|
||
| Prerequisites: Python ≥ 3.14, the AWS SAM CLI, and AWS credentials for an | ||
| account where Durable Execution is available. | ||
|
|
||
| ```bash | ||
| cd packages/aws-durable-execution-sdk-python-conformance-tests | ||
| SUITE=<suite> | ||
|
|
||
| python3 scripts/build_examples.py | ||
| pip install aws-durable-execution-conformance-tests==0.1.0 | ||
|
|
||
| python -m aws_durable_execution_conformance_tests.app \ | ||
| --template "template_${SUITE}.yaml" \ | ||
| --language python \ | ||
| --suite "$SUITE" \ | ||
| --name "conformance-python-${SUITE}-local" \ | ||
| --region us-west-2 \ | ||
| --history-dir "history-${SUITE}" \ | ||
| --report junit --report-file "report-${SUITE}" | ||
| ``` | ||
|
|
||
| The runner deploys the template, invokes each function, validates its result and | ||
| history, and cleans up the stack by default. | ||
|
|
||
| ## Authoring a new test case | ||
|
|
||
| 1. Find or add the requirement in the conformance repository under | ||
| `test-requirements/<suite>/<id>.yaml`. | ||
| 2. Add `handlers/<suite>/<descriptive_name>.py` exporting `handler`. Use the | ||
| SDK's real API; never hand-roll behavior to force an expected result. | ||
| 3. Register it in `template_<suite>.yaml` with | ||
| `Handler: <suite>.<name>.handler` and `TestDescription: ["<id>"]`. | ||
| 4. Rebuild and run the suite. | ||
|
|
||
| For a new suite, adding its template and handler directory is sufficient; the | ||
| build and CI matrix discover it automatically. | ||
|
|
||
| ## CI | ||
|
|
||
| `.github/workflows/conformance-tests.yml` first discovers all suites from | ||
| `template_<suite>.yaml`, then runs one parallel matrix job per suite. CI assumes | ||
| AWS credentials through the repository's existing OIDC secrets. | ||
|
|
||
| Before deployment, `scripts/inject_execution_role.py` points every function at | ||
| the pre-existing execution role (`TEST_LAMBDA_EXECUTION_ROLE_ARN`) and removes | ||
| the template's self-created `DurableFunctionRole`. The checked-in templates | ||
| remain self-contained for local runs. |
Empty file.
Empty file.
21 changes: 21 additions & 0 deletions
21
.../aws-durable-execution-sdk-python-conformance-tests/handlers/step/step_and_wait_replay.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| from typing import Any | ||
|
|
||
| from aws_durable_execution_sdk_python.config import Duration | ||
| from aws_durable_execution_sdk_python.context import ( | ||
| DurableContext, | ||
| StepContext, | ||
| durable_step, | ||
| ) | ||
| from aws_durable_execution_sdk_python.execution import durable_execution | ||
|
|
||
|
|
||
| @durable_step | ||
| def compute(_step_context: StepContext) -> str: | ||
| return "computed" | ||
|
|
||
|
|
||
| @durable_execution | ||
| def handler(_event: Any, context: DurableContext) -> str: | ||
| result: str = context.step(compute()) | ||
| context.wait(Duration.from_seconds(2)) | ||
| return result |
35 changes: 35 additions & 0 deletions
35
...urable-execution-sdk-python-conformance-tests/handlers/step/step_at_most_once_no_retry.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| """1-17: AtMostOnce interrupted (no retry) - Lambda crash, StepInterruptedError, fails permanently.""" | ||
|
|
||
| import os | ||
| import time | ||
| from typing import Any | ||
|
|
||
| from aws_durable_execution_sdk_python.config import StepConfig, StepSemantics | ||
| from aws_durable_execution_sdk_python.context import ( | ||
| DurableContext, | ||
| StepContext, | ||
| durable_step, | ||
| ) | ||
| from aws_durable_execution_sdk_python.execution import durable_execution | ||
| from aws_durable_execution_sdk_python.retries import RetryPresets | ||
|
|
||
|
|
||
| @durable_step | ||
| def at_most_once_flaky_step(_step_context: StepContext, *, input_1: str) -> str: | ||
| print(input_1, flush=True) | ||
| time.sleep(1) # Allow time for logs to flush to CloudWatch | ||
| os._exit(1) # Simulate Lambda crash | ||
| return "unreachable" | ||
|
|
||
|
|
||
| @durable_execution | ||
| def handler(event: Any, context: DurableContext) -> str: | ||
| result: str = context.step( | ||
| at_most_once_flaky_step(input_1=str(event)), | ||
| name="at_most_once_flaky_step", | ||
| config=StepConfig( | ||
| retry_strategy=RetryPresets.none(), | ||
| step_semantics=StepSemantics.AT_MOST_ONCE_PER_RETRY, | ||
| ), | ||
| ) | ||
| return result |
50 changes: 50 additions & 0 deletions
50
...able-execution-sdk-python-conformance-tests/handlers/step/step_at_most_once_with_retry.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| """1-18: AtMostOnce interrupted (with retry, uses the step context attempt number).""" | ||
|
|
||
| import os | ||
| import time | ||
| from typing import Any | ||
|
|
||
| from aws_durable_execution_sdk_python.config import Duration, StepConfig, StepSemantics | ||
| from aws_durable_execution_sdk_python.context import ( | ||
| DurableContext, | ||
| StepContext, | ||
| durable_step, | ||
| ) | ||
| from aws_durable_execution_sdk_python.execution import durable_execution | ||
| from aws_durable_execution_sdk_python.retries import ( | ||
| RetryStrategyConfig, | ||
| create_retry_strategy, | ||
| ) | ||
|
|
||
|
|
||
| @durable_step | ||
| def at_most_once_step(step_context: StepContext, *, input_1: str) -> str: | ||
| # Print input to stdout each time step executes | ||
| print(input_1, flush=True) | ||
| time.sleep(1) # Allow time for logs to flush to CloudWatch | ||
|
|
||
| # The attempt number is the SDK's built-in durable counter from the step | ||
| # context (1-based). Under AtMostOncePerRetry the interrupted first attempt | ||
| # is consumed, so the retry re-executes as attempt 2. | ||
| if step_context.attempt < 2: | ||
| # First attempt: simulate Lambda crash | ||
| os._exit(1) | ||
| # Second attempt (retry): succeed | ||
| return "succeeded on second attempt" | ||
|
|
||
|
|
||
| @durable_execution | ||
| def handler(event: Any, context: DurableContext) -> str: | ||
| retry_config = RetryStrategyConfig( | ||
| max_attempts=3, | ||
| initial_delay=Duration.from_seconds(1), | ||
| ) | ||
|
|
||
| result: str = context.step( | ||
| at_most_once_step(input_1=str(event)), | ||
| config=StepConfig( | ||
| retry_strategy=create_retry_strategy(retry_config), | ||
| step_semantics=StepSemantics.AT_MOST_ONCE_PER_RETRY, | ||
| ), | ||
| ) | ||
| return result |
19 changes: 19 additions & 0 deletions
19
packages/aws-durable-execution-sdk-python-conformance-tests/handlers/step/step_basic.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| from typing import Any | ||
|
|
||
| from aws_durable_execution_sdk_python.context import ( | ||
| DurableContext, | ||
| StepContext, | ||
| durable_step, | ||
| ) | ||
| from aws_durable_execution_sdk_python.execution import durable_execution | ||
|
|
||
|
|
||
| @durable_step | ||
| def greet(_step_context: StepContext, name: str) -> str: | ||
| return f"Hello, {name}!" | ||
|
|
||
|
|
||
| @durable_execution | ||
| def handler(event: Any, context: DurableContext) -> str: | ||
| result: str = context.step(greet(event)) | ||
| return result |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like Java workflow, sdk changes are ignored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add the trigger in a following PR that includes all the testing suites