Skip to content
This repository was archived by the owner on Jun 10, 2026. It is now read-only.

[ENH] Pre-publish CI: run aws-sam-cli durable integration tests against candidate emulator image #225

Description

@yaythomas

Background

The emulator image public.ecr.aws/durable-functions/aws-durable-execution-emulator:latest is consumed automatically by aws-sam-cli: on every sam local invoke of a durable function, sam-cli pulls :latest and refreshes the local cache (see durable_functions_emulator_container.py; customers can override per-invoke with DURABLE_EXECUTIONS_EMULATOR_IMAGE_TAG but the default is :latest). This means any image we publish ships immediately to every durable-functions customer running sam-cli, with no version pin in between.

PR #216 in this repo recently demonstrated the blast radius: ~26 sam-cli durable integration tests went red across local-invoke, local-start-lambda, tier1-finch, and tier1-windows-other jobs (e.g. aws-sam-cli Integration Tests #496, run #8779 / local-start-lambda) the moment v1.2.0 went to :latest. Customer-visible symptom: a fresh samdev local invoke against any durable function 500s on first checkpoint or 404s on first local execution get|history|stop|callback. Mitigations are in flight on the sam-cli side (aws/aws-sam-cli#9038 merged, #9040 open) but they do not address the class problem: this repo's release pipeline has no signal from sam-cli before publishing :latest.

Why our existing tests didn't catch this

tests/web/e2e/routes_arn_encoding_int_test.py (added in #222) drives a real boto client against this repo's WebServer and would have caught the emulator-side routing bug. It does not — and cannot — exercise sam-cli's LocalLambdaHttpService, which is a separate Flask service that customers' boto clients actually hit when using samdev local invoke. Anything we change in the ARN, callback ID, or function-qualifier shape can break sam-cli's service without touching ours.

Proposal

Add a pre-publish CI step that builds the candidate emulator image and runs sam-cli's durable integration suite against it. Concrete shape:

  1. Build the emulator image from this repo (we already do this in ecr-release.yml).
  2. Tag it locally with a candidate tag, e.g. aws-durable-execution-emulator:pr-${SHA}.
  3. Check out aws/aws-sam-cli at develop, install in SAM_CLI_DEV=1 mode.
  4. Run, with DURABLE_EXECUTIONS_EMULATOR_IMAGE_TAG=pr-${SHA}:
    pytest -vv \
      tests/integration/local/invoke/test_invoke_durable.py \
      tests/integration/local/start_lambda/test_start_lambda_durable.py \
      tests/integration/local/execution/test_execution.py \
      tests/integration/local/callback/test_callback.py
    That's the durable subset — ~50 tests, runs in ~3–5 min in CI based on the local-invoke and local-start-lambda timings above.
  5. Publish to ECR only if step 4 is green.

Gate this on PRs that touch src/** so we get the signal pre-merge as well as pre-publish.

Acceptance criteria

  • A workflow (e.g. .github/workflows/sam-cli-compat.yml) that runs the four sam-cli durable test files against the locally-built emulator image and is required for PRs that change src/.
  • The publish job (ecr-release.yml) gated on the same workflow's success.
  • A README / CONTRIBUTING note explaining that any change affecting the emulator's HTTP contract — ARN shape, callback-token shape, route layout, response codes — must keep this job green.

Out of scope

  • Pinning sam-cli to a specific emulator tag. That just inverts the dependency: customers stop picking up emulator fixes until sam-cli ships a new release. Roll-forward + this CI gate is the durable answer.
  • Running the full sam-cli integration suite. The four files above cover every code path that talks to the emulator.

References

Activity

  1. moved this from Backlog to Ready in aws-durable-executionon May 22, 2026
  2. yaythomas commented on May 23, 2026

    @yaythomas
    ContributorAuthor

    Concrete proposal for the workflow

    Sketching out the YAML so this is easy to pick up. Uses an explicit file list (sam-cli's build.yml only lists three of the five, but two more — tests/integration/local/callback/test_callback.py and tests/integration/local/execution/test_execution.py — also inherit from DurableIntegBase and need the emulator).

    .github/workflows/sam-cli-compat.yml

    name: sam-cli-compat
    
    on:
      pull_request:
        branches: [main]
        paths:
          - 'src/**'
          - 'Dockerfile'
          - '.github/workflows/sam-cli-compat.yml'
    
    jobs:
      sam-cli-durable-integ:
        runs-on: ubuntu-latest
        timeout-minutes: 25
        steps:
          - uses: actions/checkout@v4
    
          - name: Build emulator image from this PR
            run: |
              TAG="pr-${{ github.event.pull_request.head.sha }}"
              docker build -t "public.ecr.aws/durable-functions/aws-durable-execution-emulator:${TAG}" .
              echo "EMULATOR_TAG=${TAG}" >> "$GITHUB_ENV"
    
          - uses: actions/setup-python@v5
            with:
              python-version: '3.11'
    
          - uses: actions/setup-python@v5
            with:
              python-version: '3.13'   # for the durable test-app Lambda runtime
    
          - name: Checkout aws-sam-cli
            uses: actions/checkout@v4
            with:
              repository: aws/aws-sam-cli
              ref: ${{ vars.SAM_CLI_REF || 'develop' }}   # override per-PR if a sam-cli fix is needed
              path: sam-cli
    
          - name: Install samdev
            working-directory: sam-cli
            env:
              SAM_CLI_DEV: '1'
            run: pip install -e '.[dev]'
    
          - name: Run sam-cli durable integration tests
            working-directory: sam-cli
            env:
              SAM_CLI_DEV: '1'
              SAM_CLI_TELEMETRY: '0'
              DURABLE_EXECUTIONS_EMULATOR_IMAGE_TAG: ${{ env.EMULATOR_TAG }}
            run: |
              pytest -vv \
                tests/integration/local/invoke/test_invoke_durable.py \
                tests/integration/local/start_api/test_start_api_durable.py \
                tests/integration/local/start_lambda/test_start_lambda_durable.py \
                tests/integration/local/callback/test_callback.py \
                tests/integration/local/execution/test_execution.py

    Then in Settings → Branches → main → Branch protection rules, mark sam-cli-durable-integ as a required check.

    Notes

    • File list is hand-curated. All five files inherit from tests/integration/durable_integ_base.DurableIntegBase at sam-cli develop HEAD. There's no formal contract enforcing that — if sam-cli adds a new emulator-dependent test that doesn't extend DurableIntegBase, this list will go stale silently. A follow-up to ask sam-cli for @pytest.mark.durable so we can do pytest -m durable instead is worth filing separately.
    • Chicken-and-egg escape hatch. ref: ${{ vars.SAM_CLI_REF || 'develop' }} lets a testing-lib PR that requires a corresponding sam-cli fix point the workflow at a specific sam-cli SHA via repo variable. Without that, this gate would have blocked PR [fix]: input payload too big to fit in initial execution state #216 from merging until fix(local-lambda): accept documented Lambda DurableExecutionArn shape aws-sam-cli#9040 existed — useful, but only if there's a way to coordinate.
    • Runtime. Locally these five files take ~5–7 min once images are warm, ~7–10 min cold. Expect 8–12 min on GitHub-hosted runners.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions