From 2e6a4a4a30494c3e7a484c937a656b26cbebf514 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 17 Jun 2026 17:44:09 +0000 Subject: [PATCH 01/14] ci: fast CI --- .github/workflows/test-on-push-and-pr.yml | 71 +++++++++++++++++++---- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml index 5b80d23..50fdc99 100644 --- a/.github/workflows/test-on-push-and-pr.yml +++ b/.github/workflows/test-on-push-and-pr.yml @@ -17,32 +17,83 @@ jobs: alpine: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + distro_version: ["3.19", "3.20"] + runtime_version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - - name: Run alpine integration tests - run: DISTRO=alpine make test-integ + - name: Run integration test (alpine ${{ matrix.distro_version }} / python ${{ matrix.runtime_version }}) + run: | + docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent + CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ + tests/integration/codebuild/buildspec.os.alpine.yml \ + alpine "${{ matrix.distro_version }}" "${{ matrix.runtime_version }}" - amazonlinux: + debian: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + distro_version: ["bookworm", "bullseye"] + runtime_version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - - name: Run amazonlinux integration tests - run: DISTRO=amazonlinux make test-integ + - name: Run integration test (debian ${{ matrix.distro_version }} / python ${{ matrix.runtime_version }}) + run: | + docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent + CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ + tests/integration/codebuild/buildspec.os.debian.yml \ + debian "${{ matrix.distro_version }}" "${{ matrix.runtime_version }}" - debian: + amazonlinux2: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + runtime_version: ["3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v4 + - name: Run integration test (amazonlinux 2 / python ${{ matrix.runtime_version }}) + run: | + docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent + CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ + tests/integration/codebuild/buildspec.os.amazonlinux.2.yml \ + amazonlinux2 "2" "${{ matrix.runtime_version }}" + + amazonlinux2023: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + runtime_version: ["3.12", "3.13"] steps: - uses: actions/checkout@v4 - - name: Run debian integration tests - run: DISTRO=debian make test-integ + - name: Run integration test (amazonlinux 2023 / python ${{ matrix.runtime_version }}) + run: | + docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent + CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ + tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml \ + amazonlinux2023 "2023" "${{ matrix.runtime_version }}" ubuntu: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + distro_version: ["22.04", "24.04"] + runtime_version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - - name: Run ubuntu integration tests - run: DISTRO=ubuntu make test-integ \ No newline at end of file + - name: Run integration test (ubuntu ${{ matrix.distro_version }} / python ${{ matrix.runtime_version }}) + run: | + docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent + CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ + tests/integration/codebuild/buildspec.os.ubuntu.yml \ + ubuntu "${{ matrix.distro_version }}" "${{ matrix.runtime_version }}" From 0abc519efc24c403d34bd1700aa2cb421476f06d Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 17 Jun 2026 17:51:23 +0000 Subject: [PATCH 02/14] fix: remove 3.9 runtime as deprecated --- .github/workflows/test-on-push-and-pr.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml index 50fdc99..7717c81 100644 --- a/.github/workflows/test-on-push-and-pr.yml +++ b/.github/workflows/test-on-push-and-pr.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ '*' ] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest @@ -21,7 +24,7 @@ jobs: fail-fast: false matrix: distro_version: ["3.19", "3.20"] - runtime_version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + runtime_version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 @@ -38,7 +41,7 @@ jobs: fail-fast: false matrix: distro_version: ["bookworm", "bullseye"] - runtime_version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + runtime_version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 @@ -54,7 +57,7 @@ jobs: strategy: fail-fast: false matrix: - runtime_version: ["3.9", "3.10", "3.11"] + runtime_version: ["3.10", "3.11"] steps: - uses: actions/checkout@v4 @@ -87,7 +90,7 @@ jobs: fail-fast: false matrix: distro_version: ["22.04", "24.04"] - runtime_version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + runtime_version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 From 0ac22566d7e1d1d439d18ca22679d198eefed4a0 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 17 Jun 2026 18:49:26 +0000 Subject: [PATCH 03/14] fix: pr comments --- .github/workflows/test-on-push-and-pr.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml index 7717c81..3c8a3d6 100644 --- a/.github/workflows/test-on-push-and-pr.yml +++ b/.github/workflows/test-on-push-and-pr.yml @@ -24,7 +24,7 @@ jobs: fail-fast: false matrix: distro_version: ["3.19", "3.20"] - runtime_version: ["3.10", "3.11", "3.12", "3.13"] + runtime_version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 @@ -41,7 +41,7 @@ jobs: fail-fast: false matrix: distro_version: ["bookworm", "bullseye"] - runtime_version: ["3.10", "3.11", "3.12", "3.13"] + runtime_version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 @@ -73,7 +73,7 @@ jobs: strategy: fail-fast: false matrix: - runtime_version: ["3.12", "3.13"] + runtime_version: ["3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fail-fast: false matrix: distro_version: ["22.04", "24.04"] - runtime_version: ["3.10", "3.11", "3.12", "3.13"] + runtime_version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 From 15e6911a8aca9385de7a18ef06e40a73eb7a68cb Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 17 Jun 2026 19:39:41 +0000 Subject: [PATCH 04/14] fix: 3.14 --- .github/workflows/test-on-push-and-pr.yml | 10 ++++++++-- tests/test_concurrency.py | 9 ++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml index 3c8a3d6..5bf56d3 100644 --- a/.github/workflows/test-on-push-and-pr.yml +++ b/.github/workflows/test-on-push-and-pr.yml @@ -24,7 +24,10 @@ jobs: fail-fast: false matrix: distro_version: ["3.19", "3.20"] - runtime_version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + runtime_version: ["3.10", "3.11", "3.12", "3.13"] + include: + - distro_version: "3.21" + runtime_version: "3.14" steps: - uses: actions/checkout@v4 @@ -41,7 +44,10 @@ jobs: fail-fast: false matrix: distro_version: ["bookworm", "bullseye"] - runtime_version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + runtime_version: ["3.10", "3.11", "3.12", "3.13"] + include: + - distro_version: "bookworm" + runtime_version: "3.14" steps: - uses: actions/checkout@v4 diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 9e74793..38f57fb 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -18,8 +18,9 @@ def setUp(self): self.socket = "/tmp/sock" def test_success_and_failure_isolation(self): - success_counter = multiprocessing.Value("i", 0) - fail_counter = multiprocessing.Value("i", 0) + ctx = multiprocessing.get_context("fork") + success_counter = ctx.Value("i", 0) + fail_counter = ctx.Value("i", 0) def fake_bootstrap_run(handler, lambda_runtime_client): pid = multiprocessing.current_process().pid @@ -37,8 +38,10 @@ def fake_bootstrap_run(handler, lambda_runtime_client): ), patch( "awslambdaric.lambda_multi_concurrent_utils.bootstrap.run", side_effect=fake_bootstrap_run, + ), patch( + "awslambdaric.lambda_multi_concurrent_utils.multiprocessing.Process", + ctx.Process, ): - # spawn 4 multi-concurrent processes MultiConcurrentRunner.run_concurrent( self.handler, self.addr, self.use_thread, self.socket, max_concurrency=4 ) From d380c4bc6402afc0d2ef59adea96f154f43b1a85 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 17 Jun 2026 19:41:54 +0000 Subject: [PATCH 05/14] fix: put back comment --- tests/test_concurrency.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 38f57fb..4a6451a 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -42,6 +42,7 @@ def fake_bootstrap_run(handler, lambda_runtime_client): "awslambdaric.lambda_multi_concurrent_utils.multiprocessing.Process", ctx.Process, ): + # spawn 4 multi-concurrent processes MultiConcurrentRunner.run_concurrent( self.handler, self.addr, self.use_thread, self.socket, max_concurrency=4 ) From 816c2148ed06160a4c316d4d4e0cca8223486b38 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 17 Jun 2026 19:46:02 +0000 Subject: [PATCH 06/14] fix: backoff delay if rate is exceeded --- tests/integration/codebuild-local/test_one.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/integration/codebuild-local/test_one.sh b/tests/integration/codebuild-local/test_one.sh index 01dfa91..97d4063 100755 --- a/tests/integration/codebuild-local/test_one.sh +++ b/tests/integration/codebuild-local/test_one.sh @@ -17,6 +17,22 @@ function usage { >&2 echo " env Additional environment variables file." } +function pull_with_retry() { + local image="$1" + local max_retries=3 + local wait=10 + for attempt in $(seq 1 $max_retries); do + if docker pull "$image"; then + return 0 + fi + >&2 echo "Docker pull attempt $attempt/$max_retries failed. Retrying in ${wait}s..." + sleep $wait + wait=$((wait * 2)) + done + >&2 echo "Failed to pull $image after $max_retries attempts." + return 1 +} + main() { if (( $# != 3 && $# != 4)); then >&2 echo "Invalid number of parameters." @@ -49,6 +65,9 @@ main() { ARTIFACTS_DIR="$CODEBUILD_TEMP_DIR/artifacts" mkdir -p "$ARTIFACTS_DIR" + # Pre-pull the CodeBuild local agent image with retries to handle ECR rate limits. + pull_with_retry "public.ecr.aws/codebuild/local-builds:latest" + # Run CodeBuild local agent. "$(dirname "$0")"/codebuild_build.sh \ -i "$CODEBUILD_IMAGE_TAG" \ From 0785e06bbd9a730c6f7ba41baf658b391378f51e Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 17 Jun 2026 20:14:19 +0000 Subject: [PATCH 07/14] fix: try to remove codebuild --- .github/workflows/test-on-push-and-pr.yml | 291 +++++++++++++----- .gitignore | 2 + Makefile | 21 +- .../codebuild-local/Dockerfile.agent | 5 - .../codebuild-local/codebuild_build.sh | 201 ------------ tests/integration/codebuild-local/test_all.sh | 72 ----- tests/integration/codebuild-local/test_one.sh | 79 ----- .../codebuild/buildspec.os.alpine.yml | 111 ------- .../codebuild/buildspec.os.amazonlinux.2.yml | 106 ------- .../buildspec.os.amazonlinux.2023.yml | 105 ------- .../codebuild/buildspec.os.debian.yml | 111 ------- .../codebuild/buildspec.os.ubuntu.yml | 109 ------- tests/integration/docker-compose.template.yml | 30 -- tests/integration/run-local.sh | 90 ++++++ 14 files changed, 315 insertions(+), 1018 deletions(-) delete mode 100644 tests/integration/codebuild-local/Dockerfile.agent delete mode 100755 tests/integration/codebuild-local/codebuild_build.sh delete mode 100755 tests/integration/codebuild-local/test_all.sh delete mode 100755 tests/integration/codebuild-local/test_one.sh delete mode 100644 tests/integration/codebuild/buildspec.os.alpine.yml delete mode 100644 tests/integration/codebuild/buildspec.os.amazonlinux.2.yml delete mode 100644 tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml delete mode 100644 tests/integration/codebuild/buildspec.os.debian.yml delete mode 100644 tests/integration/codebuild/buildspec.os.ubuntu.yml delete mode 100644 tests/integration/docker-compose.template.yml create mode 100755 tests/integration/run-local.sh diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml index 5bf56d3..6f84385 100644 --- a/.github/workflows/test-on-push-and-pr.yml +++ b/.github/workflows/test-on-push-and-pr.yml @@ -18,91 +18,230 @@ jobs: - name: Run 'pr' target run: make pr - alpine: + integration-test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - distro_version: ["3.19", "3.20"] - runtime_version: ["3.10", "3.11", "3.12", "3.13"] include: - - distro_version: "3.21" + # Alpine + - distro: alpine + distro_version: "3.19" + runtime_version: "3.10" + python_location: /usr/local/bin/python + - distro: alpine + distro_version: "3.19" + runtime_version: "3.11" + python_location: /usr/local/bin/python + - distro: alpine + distro_version: "3.19" + runtime_version: "3.12" + python_location: /usr/local/bin/python + - distro: alpine + distro_version: "3.19" + runtime_version: "3.13" + python_location: /usr/local/bin/python + - distro: alpine + distro_version: "3.20" + runtime_version: "3.10" + python_location: /usr/local/bin/python + - distro: alpine + distro_version: "3.20" + runtime_version: "3.11" + python_location: /usr/local/bin/python + - distro: alpine + distro_version: "3.20" + runtime_version: "3.12" + python_location: /usr/local/bin/python + - distro: alpine + distro_version: "3.20" + runtime_version: "3.13" + python_location: /usr/local/bin/python + - distro: alpine + distro_version: "3.21" runtime_version: "3.14" - - steps: - - uses: actions/checkout@v4 - - name: Run integration test (alpine ${{ matrix.distro_version }} / python ${{ matrix.runtime_version }}) - run: | - docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent - CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ - tests/integration/codebuild/buildspec.os.alpine.yml \ - alpine "${{ matrix.distro_version }}" "${{ matrix.runtime_version }}" - - debian: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - distro_version: ["bookworm", "bullseye"] - runtime_version: ["3.10", "3.11", "3.12", "3.13"] - include: - - distro_version: "bookworm" + python_location: /usr/local/bin/python + # Debian + - distro: debian + distro_version: bookworm + runtime_version: "3.10" + python_location: /usr/local/bin/python + - distro: debian + distro_version: bookworm + runtime_version: "3.11" + python_location: /usr/local/bin/python + - distro: debian + distro_version: bookworm + runtime_version: "3.12" + python_location: /usr/local/bin/python + - distro: debian + distro_version: bookworm + runtime_version: "3.13" + python_location: /usr/local/bin/python + - distro: debian + distro_version: bookworm + runtime_version: "3.14" + python_location: /usr/local/bin/python + - distro: debian + distro_version: bullseye + runtime_version: "3.10" + python_location: /usr/local/bin/python + - distro: debian + distro_version: bullseye + runtime_version: "3.11" + python_location: /usr/local/bin/python + - distro: debian + distro_version: bullseye + runtime_version: "3.12" + python_location: /usr/local/bin/python + - distro: debian + distro_version: bullseye + runtime_version: "3.13" + python_location: /usr/local/bin/python + # Amazon Linux 2 + - distro: amazonlinux2 + distro_version: "2" + runtime_version: "3.10" + python_location: /usr/local/bin/python3 + - distro: amazonlinux2 + distro_version: "2" + runtime_version: "3.11" + python_location: /usr/local/bin/python3 + # Amazon Linux 2023 + - distro: amazonlinux2023 + distro_version: "2023" + runtime_version: "3.12" + python_location: /usr/local/bin/python3 + - distro: amazonlinux2023 + distro_version: "2023" + runtime_version: "3.13" + python_location: /usr/local/bin/python3 + - distro: amazonlinux2023 + distro_version: "2023" + runtime_version: "3.14" + python_location: /usr/local/bin/python3 + # Ubuntu + - distro: ubuntu + distro_version: "22.04" + runtime_version: "3.10" + python_location: /usr/bin/python3.10 + - distro: ubuntu + distro_version: "22.04" + runtime_version: "3.11" + python_location: /usr/bin/python3.11 + - distro: ubuntu + distro_version: "22.04" + runtime_version: "3.12" + python_location: /usr/bin/python3.12 + - distro: ubuntu + distro_version: "22.04" + runtime_version: "3.13" + python_location: /usr/bin/python3.13 + - distro: ubuntu + distro_version: "22.04" runtime_version: "3.14" + python_location: /usr/bin/python3.14 + - distro: ubuntu + distro_version: "24.04" + runtime_version: "3.10" + python_location: /usr/bin/python3.10 + - distro: ubuntu + distro_version: "24.04" + runtime_version: "3.11" + python_location: /usr/bin/python3.11 + - distro: ubuntu + distro_version: "24.04" + runtime_version: "3.12" + python_location: /usr/bin/python3.12 + - distro: ubuntu + distro_version: "24.04" + runtime_version: "3.13" + python_location: /usr/bin/python3.13 + - distro: ubuntu + distro_version: "24.04" + runtime_version: "3.14" + python_location: /usr/bin/python3.14 - steps: - - uses: actions/checkout@v4 - - name: Run integration test (debian ${{ matrix.distro_version }} / python ${{ matrix.runtime_version }}) - run: | - docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent - CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ - tests/integration/codebuild/buildspec.os.debian.yml \ - debian "${{ matrix.distro_version }}" "${{ matrix.runtime_version }}" - - amazonlinux2: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - runtime_version: ["3.10", "3.11"] + name: "${{ matrix.distro }} ${{ matrix.distro_version }} / python ${{ matrix.runtime_version }}" steps: - - uses: actions/checkout@v4 - - name: Run integration test (amazonlinux 2 / python ${{ matrix.runtime_version }}) - run: | - docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent - CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ - tests/integration/codebuild/buildspec.os.amazonlinux.2.yml \ - amazonlinux2 "2" "${{ matrix.runtime_version }}" - - amazonlinux2023: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - runtime_version: ["3.12", "3.13", "3.14"] + - uses: actions/checkout@v4 - steps: - - uses: actions/checkout@v4 - - name: Run integration test (amazonlinux 2023 / python ${{ matrix.runtime_version }}) - run: | - docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent - CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ - tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml \ - amazonlinux2023 "2023" "${{ matrix.runtime_version }}" - - ubuntu: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - distro_version: ["22.04", "24.04"] - runtime_version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + - name: Extract RIE + run: | + mkdir -p .scratch + ARCHITECTURE=$(arch) + if [[ "$ARCHITECTURE" == "x86_64" ]]; then + RIE="aws-lambda-rie" + elif [[ "$ARCHITECTURE" == "aarch64" ]]; then + RIE="aws-lambda-rie-arm64" + else + echo "Architecture $ARCHITECTURE is not currently supported." + exit 1 + fi + tar -xvf tests/integration/resources/${RIE}.tar.gz --directory .scratch + echo "RIE=${RIE}" >> "$GITHUB_ENV" - steps: - - uses: actions/checkout@v4 - - name: Run integration test (ubuntu ${{ matrix.distro_version }} / python ${{ matrix.runtime_version }}) - run: | - docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent - CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh \ - tests/integration/codebuild/buildspec.os.ubuntu.yml \ - ubuntu "${{ matrix.distro_version }}" "${{ matrix.runtime_version }}" + - name: Build Docker image + run: | + DOCKERFILE="tests/integration/docker/Dockerfile.echo.${{ matrix.distro }}" + TMPFILE=".scratch/Dockerfile.tmp" + cp "$DOCKERFILE" "$TMPFILE" + if [[ "${{ matrix.distro }}" == "alpine" ]]; then + echo "RUN apk add curl" >> "$TMPFILE" + fi + echo "COPY .scratch/${RIE} /usr/bin/${RIE}" >> "$TMPFILE" + docker build . \ + -f "$TMPFILE" \ + -t ric-test \ + --build-arg RUNTIME_VERSION=${{ matrix.runtime_version }} \ + --build-arg DISTRO_VERSION=${{ matrix.distro_version }} \ + --build-arg ARCHITECTURE=$(arch) + + - name: Run integration test + run: | + TEST_NAME="ric-integ-test" + docker network create "${TEST_NAME}-net" + + docker run \ + --detach \ + --name "${TEST_NAME}-app" \ + --network "${TEST_NAME}-net" \ + --entrypoint="" \ + ric-test \ + sh -c "/usr/bin/${RIE} ${{ matrix.python_location }} -m awslambdaric app.handler" + + sleep 2 + + docker run \ + --name "${TEST_NAME}-tester" \ + --env "TARGET=${TEST_NAME}-app" \ + --network "${TEST_NAME}-net" \ + --entrypoint="" \ + ric-test \ + sh -c 'curl -sS -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' + + ACTUAL="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)" + EXPECTED="success" + echo "Response: ${ACTUAL}" + if [ "$ACTUAL" != "$EXPECTED" ]; then + echo "FAIL: expected '${EXPECTED}', got '${ACTUAL}'" + exit 1 + fi + echo "PASS" + + - name: Dump container logs + if: always() + run: | + TEST_NAME="ric-integ-test" + echo "=== App container logs ===" + docker logs "${TEST_NAME}-app" 2>&1 || true + echo "=== Tester container logs ===" + docker logs "${TEST_NAME}-tester" 2>&1 || true + + - name: Cleanup + if: always() + run: | + TEST_NAME="ric-integ-test" + docker rm -f "${TEST_NAME}-app" "${TEST_NAME}-tester" 2>/dev/null || true + docker network rm "${TEST_NAME}-net" 2>/dev/null || true diff --git a/.gitignore b/.gitignore index 9d46e4c..0899fc1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ generated.docker-compose.*.yml tests/integration/resources/init +.scratch + .idea node_modules/ diff --git a/Makefile b/Makefile index 521b61c..1a6e673 100644 --- a/Makefile +++ b/Makefile @@ -11,17 +11,15 @@ init: test: check-format pytest --cov awslambdaric --cov-report term-missing --cov-fail-under 90 tests -.PHONY: setup-codebuild-agent -setup-codebuild-agent: - docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent - -.PHONY: test-smoke -test-smoke: setup-codebuild-agent - CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh tests/integration/codebuild/buildspec.os.alpine.yml alpine 3.15 3.9 - .PHONY: test-integ -test-integ: setup-codebuild-agent - CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild/. +test-integ: + @echo "Integration tests run via GitHub Actions (see .github/workflows/test-on-push-and-pr.yml)" + @echo "To run a single combo locally:" + @echo " make test-integ-local DISTRO=alpine DISTRO_VERSION=3.20 RUNTIME_VERSION=3.13" + +.PHONY: test-integ-local +test-integ-local: + tests/integration/run-local.sh $(DISTRO) $(DISTRO_VERSION) $(RUNTIME_VERSION) .PHONY: check-security check-security: @@ -43,9 +41,6 @@ dev: init test .PHONY: pr pr: init check-format check-security dev -codebuild: setup-codebuild-agent - CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild - .PHONY: clean clean: rm -rf dist diff --git a/tests/integration/codebuild-local/Dockerfile.agent b/tests/integration/codebuild-local/Dockerfile.agent deleted file mode 100644 index 1016c0c..0000000 --- a/tests/integration/codebuild-local/Dockerfile.agent +++ /dev/null @@ -1,5 +0,0 @@ -FROM public.ecr.aws/amazonlinux/amazonlinux:2 - -RUN amazon-linux-extras enable docker && \ - yum clean metadata && \ - yum install -y docker tar diff --git a/tests/integration/codebuild-local/codebuild_build.sh b/tests/integration/codebuild-local/codebuild_build.sh deleted file mode 100755 index 45329d2..0000000 --- a/tests/integration/codebuild-local/codebuild_build.sh +++ /dev/null @@ -1,201 +0,0 @@ -#!/bin/bash -# This file is copied from https://github.com/aws/aws-codebuild-docker-images/blob/f0912e4b16e427da35351fc102f0f56f4ceb938a/local_builds/codebuild_build.sh - -function allOSRealPath() { - if isOSWindows - then - path="" - case $1 in - .* ) path="$PWD/${1#./}" ;; - /* ) path="$1" ;; - * ) path="/$1" ;; - esac - - echo "/$path" | sed -e 's/\\/\//g' -e 's/://' -e 's/./\U&/3' - else - case $1 in - /* ) echo "$1"; exit;; - * ) echo "$PWD/${1#./}"; exit;; - esac - fi -} - -function isOSWindows() { - if [ $OSTYPE == "msys" ] - then - return 0 - else - return 1 - fi -} - -function usage { - echo "usage: codebuild_build.sh [-i image_name] [-a artifact_output_directory] [options]" - echo "Required:" - echo " -i Used to specify the customer build container image." - echo " -a Used to specify an artifact output directory." - echo "Options:" - echo " -l IMAGE Used to override the default local agent image." - echo " -r Used to specify a report output directory." - echo " -s Used to specify source information. Defaults to the current working directory for primary source." - echo " * First (-s) is for primary source" - echo " * Use additional (-s) in : format for secondary source" - echo " * For sourceIdentifier, use a value that is fewer than 128 characters and contains only alphanumeric characters and underscores" - echo " -c Use the AWS configuration and credentials from your local host. This includes ~/.aws and any AWS_* environment variables." - echo " -p Used to specify the AWS CLI Profile." - echo " -b FILE Used to specify a buildspec override file. Defaults to buildspec.yml in the source directory." - echo " -m Used to mount the source directory to the customer build container directly." - echo " -d Used to run the build container in docker privileged mode." - echo " -e FILE Used to specify a file containing environment variables." - echo " (-e) File format expectations:" - echo " * Each line is in VAR=VAL format" - echo " * Lines beginning with # are processed as comments and ignored" - echo " * Blank lines are ignored" - echo " * File can be of type .env or .txt" - echo " * There is no special handling of quotation marks, meaning they will be part of the VAL" - exit 1 -} - -image_flag=false -artifact_flag=false -awsconfig_flag=false -mount_src_dir_flag=false -docker_privileged_mode_flag=false - -while getopts "cmdi:a:r:s:b:e:l:p:h" opt; do - case $opt in - i ) image_flag=true; image_name=$OPTARG;; - a ) artifact_flag=true; artifact_dir=$OPTARG;; - r ) report_dir=$OPTARG;; - b ) buildspec=$OPTARG;; - c ) awsconfig_flag=true;; - m ) mount_src_dir_flag=true;; - d ) docker_privileged_mode_flag=true;; - s ) source_dirs+=("$OPTARG");; - e ) environment_variable_file=$OPTARG;; - l ) local_agent_image=$OPTARG;; - p ) aws_profile=$OPTARG;; - h ) usage; exit;; - \? ) echo "Unknown option: -$OPTARG" >&2; exit 1;; - : ) echo "Missing option argument for -$OPTARG" >&2; exit 1;; - * ) echo "Invalid option: -$OPTARG" >&2; exit 1;; - esac -done - -if ! $image_flag -then - echo "The image name flag (-i) must be included for a build to run" >&2 -fi - -if ! $artifact_flag -then - echo "The artifact directory (-a) must be included for a build to run" >&2 -fi - -if ! $image_flag || ! $artifact_flag -then - exit 1 -fi - -docker_command="docker run " -if isOSWindows -then - docker_command+="-v //var/run/docker.sock:/var/run/docker.sock -e " -else - docker_command+="-v /var/run/docker.sock:/var/run/docker.sock -e " -fi - -docker_command+="\"IMAGE_NAME=$image_name\" -e \ - \"ARTIFACTS=$(allOSRealPath "$artifact_dir")\"" - -if [ -n "$report_dir" ] -then - docker_command+=" -e \"REPORTS=$(allOSRealPath "$report_dir")\"" -fi - -if [ -z "$source_dirs" ] -then - docker_command+=" -e \"SOURCE=$(allOSRealPath "$PWD")\"" -else - for index in "${!source_dirs[@]}"; do - if [ $index -eq 0 ] - then - docker_command+=" -e \"SOURCE=$(allOSRealPath "${source_dirs[$index]}")\"" - else - identifier=${source_dirs[$index]%%:*} - src_dir=$(allOSRealPath "${source_dirs[$index]#*:}") - - docker_command+=" -e \"SECONDARY_SOURCE_$index=$identifier:$src_dir\"" - fi - done -fi - -if [ -n "$buildspec" ] -then - docker_command+=" -e \"BUILDSPEC=$(allOSRealPath "$buildspec")\"" -fi - -if [ -n "$environment_variable_file" ] -then - environment_variable_file_path=$(allOSRealPath "$environment_variable_file") - environment_variable_file_dir=$(dirname "$environment_variable_file_path") - environment_variable_file_basename=$(basename "$environment_variable_file") - docker_command+=" -v \"$environment_variable_file_dir:/LocalBuild/envFile/\" -e \"ENV_VAR_FILE=$environment_variable_file_basename\"" -fi - -if $awsconfig_flag -then - if [ -d "$HOME/.aws" ] - then - configuration_file_path=$(allOSRealPath "$HOME/.aws") - docker_command+=" -e \"AWS_CONFIGURATION=$configuration_file_path\"" - else - docker_command+=" -e \"AWS_CONFIGURATION=NONE\"" - fi - - if [ -n "$aws_profile" ] - then - docker_command+=" -e \"AWS_PROFILE=$aws_profile\"" - fi - - docker_command+="$(env | grep ^AWS_ | while read -r line; do echo " -e \"$line\""; done )" -fi - -if $mount_src_dir_flag -then - docker_command+=" -e \"MOUNT_SOURCE_DIRECTORY=TRUE\"" -fi - -if $docker_privileged_mode_flag -then - docker_command+=" -e \"DOCKER_PRIVILEGED_MODE=TRUE\"" -fi - -if isOSWindows -then - docker_command+=" -e \"INITIATOR=$USERNAME\"" -else - docker_command+=" -e \"INITIATOR=$USER\"" -fi - -if [ -n "$local_agent_image" ] -then - docker_command+=" $local_agent_image" -else - docker_command+=" public.ecr.aws/codebuild/local-builds:latest" -fi - -# Note we do not expose the AWS_SECRET_ACCESS_KEY or the AWS_SESSION_TOKEN -exposed_command=$docker_command -secure_variables=( "AWS_SECRET_ACCESS_KEY=" "AWS_SESSION_TOKEN=") -for variable in "${secure_variables[@]}" -do - exposed_command="$(echo $exposed_command | sed "s/\($variable\)[^ ]*/\1********\"/")" -done - -echo "Build Command:" -echo "" -echo $exposed_command -echo "" - -eval $docker_command \ No newline at end of file diff --git a/tests/integration/codebuild-local/test_all.sh b/tests/integration/codebuild-local/test_all.sh deleted file mode 100755 index 1a09241..0000000 --- a/tests/integration/codebuild-local/test_all.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash -# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -set -euo pipefail - -CODEBUILD_IMAGE_TAG="${CODEBUILD_IMAGE_TAG:-al2/x86_64/standard/3.0}" -DRYRUN="${DRYRUN-0}" -DISTRO="${DISTRO:=""}" - -function usage { - echo "usage: test_all.sh buildspec_yml_dir" - echo "Runs all buildspec build-matrix combinations via test_one.sh." - echo "Required:" - echo " buildspec_yml_dir Used to specify the CodeBuild buildspec template file." -} - -do_one_yaml() { - local -r YML="$1" - - OS_DISTRIBUTION=$(grep -oE 'OS_DISTRIBUTION:\s*(\S+)' "$YML" | cut -d' ' -f2) - DISTRO_VERSIONS=$(sed '1,/DISTRO_VERSION/d;/RUNTIME_VERSION/,$d' "$YML" | tr -d '\-" ') - RUNTIME_VERSIONS=$(sed '1,/RUNTIME_VERSION/d;/phases/,$d' "$YML" | sed '/#.*$/d' | tr -d '\-" ') - - for DISTRO_VERSION in $DISTRO_VERSIONS ; do - for RUNTIME_VERSION in $RUNTIME_VERSIONS ; do - if (( DRYRUN == 1 )) ; then - echo DRYRUN test_one_combination "$YML" "$OS_DISTRIBUTION" "$DISTRO_VERSION" "$RUNTIME_VERSION" - else - test_one_combination "$YML" "$OS_DISTRIBUTION" "$DISTRO_VERSION" "$RUNTIME_VERSION" - fi - done - done -} - -test_one_combination() { - local -r YML="$1" - local -r OS_DISTRIBUTION="$2" - local -r DISTRO_VERSION="$3" - local -r RUNTIME_VERSION="$4" - - echo Testing: - echo " BUILDSPEC" "$YML" - echo " with" "$OS_DISTRIBUTION"-"$DISTRO_VERSION" "$RUNTIME_VERSION" - - "$(dirname "$0")"/test_one.sh "$YML" "$OS_DISTRIBUTION" "$DISTRO_VERSION" "$RUNTIME_VERSION" \ - > >(sed "s/^/$OS_DISTRIBUTION$DISTRO_VERSION-$RUNTIME_VERSION: /") 2> >(sed "s/^/$OS_DISTRIBUTION-$DISTRO_VERSION:$RUNTIME_VERSION: /" >&2) -} - -main() { - if (( $# != 1 && $# != 2)); then - >&2 echo "Invalid number of parameters." - usage - exit 1 - fi - - BUILDSPEC_YML_DIR="$1" - echo $DISTRO $BUILDSPEC_YML_DIR - ls $BUILDSPEC_YML_DIR - HAS_YML=0 - for f in "$BUILDSPEC_YML_DIR"/*"$DISTRO"*.yml ; do - [ -f "$f" ] || continue; - do_one_yaml "$f" - HAS_YML=1 - done - - if (( HAS_YML == 0 )); then - >&2 echo At least one buildspec is required. - exit 2 - fi -} - -main "$@" diff --git a/tests/integration/codebuild-local/test_one.sh b/tests/integration/codebuild-local/test_one.sh deleted file mode 100755 index 97d4063..0000000 --- a/tests/integration/codebuild-local/test_one.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash -# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -set -euo pipefail - -CODEBUILD_IMAGE_TAG="${CODEBUILD_IMAGE_TAG:-al2/x86_64/standard/3.0}" - -function usage { - >&2 echo "usage: test_one.sh buildspec_yml os_distribution distro_version runtime_version [env]" - >&2 echo "Runs one buildspec version combination from a build-matrix buildspec." - >&2 echo "Required:" - >&2 echo " buildspec_yml Used to specify the CodeBuild buildspec template file." - >&2 echo " os_distribution Used to specify the OS distribution to build." - >&2 echo " distro_version Used to specify the distro version of ." - >&2 echo " runtime_version Used to specify the runtime version to test on the selected ." - >&2 echo "Optional:" - >&2 echo " env Additional environment variables file." -} - -function pull_with_retry() { - local image="$1" - local max_retries=3 - local wait=10 - for attempt in $(seq 1 $max_retries); do - if docker pull "$image"; then - return 0 - fi - >&2 echo "Docker pull attempt $attempt/$max_retries failed. Retrying in ${wait}s..." - sleep $wait - wait=$((wait * 2)) - done - >&2 echo "Failed to pull $image after $max_retries attempts." - return 1 -} - -main() { - if (( $# != 3 && $# != 4)); then - >&2 echo "Invalid number of parameters." - usage - exit 1 - fi - - set -x - BUILDSPEC_YML="$1" - OS_DISTRIBUTION="$2" - DISTRO_VERSION="$3" - RUNTIME_VERSION="$4" - EXTRA_ENV="${5-}" - - CODEBUILD_TEMP_DIR=$(mktemp -d codebuild."$OS_DISTRIBUTION"-"$DISTRO_VERSION"-"$RUNTIME_VERSION".XXXXXXXXXX) - trap 'rm -rf $CODEBUILD_TEMP_DIR' EXIT - - # Create an env file for codebuild_build. - ENVFILE="$CODEBUILD_TEMP_DIR/.env" - if [ -f "$EXTRA_ENV" ]; then - cat "$EXTRA_ENV" > "$ENVFILE" - fi - { - echo "" - echo "OS_DISTRIBUTION=$OS_DISTRIBUTION" - echo "DISTRO_VERSION=$DISTRO_VERSION" - echo "RUNTIME_VERSION=$RUNTIME_VERSION" - } >> "$ENVFILE" - - ARTIFACTS_DIR="$CODEBUILD_TEMP_DIR/artifacts" - mkdir -p "$ARTIFACTS_DIR" - - # Pre-pull the CodeBuild local agent image with retries to handle ECR rate limits. - pull_with_retry "public.ecr.aws/codebuild/local-builds:latest" - - # Run CodeBuild local agent. - "$(dirname "$0")"/codebuild_build.sh \ - -i "$CODEBUILD_IMAGE_TAG" \ - -a "$ARTIFACTS_DIR" \ - -e "$ENVFILE" \ - -b "$BUILDSPEC_YML" -} - -main "$@" diff --git a/tests/integration/codebuild/buildspec.os.alpine.yml b/tests/integration/codebuild/buildspec.os.alpine.yml deleted file mode 100644 index 8b290f5..0000000 --- a/tests/integration/codebuild/buildspec.os.alpine.yml +++ /dev/null @@ -1,111 +0,0 @@ -version: 0.2 - -env: - variables: - OS_DISTRIBUTION: alpine - PYTHON_LOCATION: "/usr/local/bin/python" - TEST_NAME: "aws-lambda-python-rtc-alpine-test" -batch: - build-matrix: - static: - ignore-failure: false - env: - privileged-mode: true - dynamic: - env: - variables: - DISTRO_VERSION: - - "3.19" - - "3.20" - RUNTIME_VERSION: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - - "3.13" -phases: - pre_build: - commands: - - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - - echo "Extracting and including the Runtime Interface Emulator" - - SCRATCH_DIR=".scratch" - - mkdir "${SCRATCH_DIR}" - - ARCHITECTURE=$(arch) - - > - if [[ "$ARCHITECTURE" == "x86_64" ]]; then - RIE="aws-lambda-rie" - elif [[ "$ARCHITECTURE" == "aarch64" ]]; then - RIE="aws-lambda-rie-arm64" - else - echo "Architecture $ARCHITECTURE is not currently supported." - exit 1 - fi - - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" - - > - cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo "RUN apk add curl" >> \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json - service docker restart - - echo "Building image ${IMAGE_TAG}" - - > - docker build . \ - -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ - -t "${IMAGE_TAG}" \ - --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ - --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ - --load - build: - commands: - - set -x - - echo "Running Image ${IMAGE_TAG}" - - docker network create "${TEST_NAME}-network" - - > - docker run \ - --detach \ - --name "${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" - - sleep 2 - - > - docker run \ - --name "${TEST_NAME}-tester" \ - --env "TARGET=${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' - - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)" - - expected='success' - - | - echo "Response: ${actual}" - if [[ "$actual" != "$expected" ]]; then - echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" - exit -1 - fi - finally: - - | - echo "---------Container Logs: ${TEST_NAME}-app----------" - echo - docker logs "${TEST_NAME}-app" || true - echo - echo "---------------------------------------------------" - echo "--------Container Logs: ${TEST_NAME}-tester--------" - echo - docker logs "${TEST_NAME}-tester" || true - echo - echo "---------------------------------------------------" - - echo "Cleaning up..." - - docker stop "${TEST_NAME}-app" || true - - docker rm --force "${TEST_NAME}-app" || true - - docker stop "${TEST_NAME}-tester" || true - - docker rm --force "${TEST_NAME}-tester" || true - - docker network rm "${TEST_NAME}-network" || true diff --git a/tests/integration/codebuild/buildspec.os.amazonlinux.2.yml b/tests/integration/codebuild/buildspec.os.amazonlinux.2.yml deleted file mode 100644 index 05722bb..0000000 --- a/tests/integration/codebuild/buildspec.os.amazonlinux.2.yml +++ /dev/null @@ -1,106 +0,0 @@ -version: 0.2 - -env: - variables: - OS_DISTRIBUTION: amazonlinux2 - PYTHON_LOCATION: "/usr/local/bin/python3" - TEST_NAME: "aws-lambda-python-rtc-amazonlinux-test" -batch: - build-matrix: - static: - ignore-failure: false - env: - privileged-mode: true - dynamic: - env: - variables: - DISTRO_VERSION: - - "2" - RUNTIME_VERSION: - - "3.9" - - "3.10" - - "3.11" -phases: - pre_build: - commands: - - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - - echo "Extracting and including the Runtime Interface Emulator" - - SCRATCH_DIR=".scratch" - - mkdir "${SCRATCH_DIR}" - - ARCHITECTURE=$(arch) - - > - if [[ "$ARCHITECTURE" == "x86_64" ]]; then - RIE="aws-lambda-rie" - elif [[ "$ARCHITECTURE" == "aarch64" ]]; then - RIE="aws-lambda-rie-arm64" - else - echo "Architecture $ARCHITECTURE is not currently supported." - exit 1 - fi - - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" - - > - cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json - service docker restart - - echo "Building image ${IMAGE_TAG}" - - > - docker build . \ - -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ - -t "${IMAGE_TAG}" \ - --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ - --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ - --build-arg ARCHITECTURE="${ARCHITECTURE}" \ - --load - build: - commands: - - set -x - - echo "Running Image ${IMAGE_TAG}" - - docker network create "${TEST_NAME}-network" - - > - docker run \ - --detach \ - --name "${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" - - sleep 2 - - > - docker run \ - --name "${TEST_NAME}-tester" \ - --env "TARGET=${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' - - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)" - - expected='success' - - | - echo "Response: ${actual}" - if [[ "$actual" != "$expected" ]]; then - echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" - exit -1 - fi - finally: - - | - echo "---------Container Logs: ${TEST_NAME}-app----------" - echo - docker logs "${TEST_NAME}-app" || true - echo - echo "---------------------------------------------------" - echo "--------Container Logs: ${TEST_NAME}-tester--------" - echo - docker logs "${TEST_NAME}-tester" || true - echo - echo "---------------------------------------------------" - - echo "Cleaning up..." - - docker stop "${TEST_NAME}-app" || true - - docker rm --force "${TEST_NAME}-app" || true - - docker stop "${TEST_NAME}-tester" || true - - docker rm --force "${TEST_NAME}-tester" || true - - docker network rm "${TEST_NAME}-network" || true diff --git a/tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml b/tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml deleted file mode 100644 index 9d6d20f..0000000 --- a/tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml +++ /dev/null @@ -1,105 +0,0 @@ -version: 0.2 - -env: - variables: - OS_DISTRIBUTION: amazonlinux2023 - PYTHON_LOCATION: "/usr/local/bin/python3" - TEST_NAME: "aws-lambda-python-rtc-amazonlinux-test" -batch: - build-matrix: - static: - ignore-failure: false - env: - privileged-mode: true - dynamic: - env: - variables: - DISTRO_VERSION: - - "2023" - RUNTIME_VERSION: - - "3.12" - - "3.13" -phases: - pre_build: - commands: - - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - - echo "Extracting and including the Runtime Interface Emulator" - - SCRATCH_DIR=".scratch" - - mkdir "${SCRATCH_DIR}" - - ARCHITECTURE=$(arch) - - > - if [[ "$ARCHITECTURE" == "x86_64" ]]; then - RIE="aws-lambda-rie" - elif [[ "$ARCHITECTURE" == "aarch64" ]]; then - RIE="aws-lambda-rie-arm64" - else - echo "Architecture $ARCHITECTURE is not currently supported." - exit 1 - fi - - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" - - > - cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json - service docker restart - - echo "Building image ${IMAGE_TAG}" - - > - docker build . \ - -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ - -t "${IMAGE_TAG}" \ - --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ - --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ - --build-arg ARCHITECTURE="${ARCHITECTURE}" \ - --load - build: - commands: - - set -x - - echo "Running Image ${IMAGE_TAG}" - - docker network create "${TEST_NAME}-network" - - > - docker run \ - --detach \ - --name "${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" - - sleep 2 - - > - docker run \ - --name "${TEST_NAME}-tester" \ - --env "TARGET=${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' - - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)" - - expected='success' - - | - echo "Response: ${actual}" - if [[ "$actual" != "$expected" ]]; then - echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" - exit -1 - fi - finally: - - | - echo "---------Container Logs: ${TEST_NAME}-app----------" - echo - docker logs "${TEST_NAME}-app" || true - echo - echo "---------------------------------------------------" - echo "--------Container Logs: ${TEST_NAME}-tester--------" - echo - docker logs "${TEST_NAME}-tester" || true - echo - echo "---------------------------------------------------" - - echo "Cleaning up..." - - docker stop "${TEST_NAME}-app" || true - - docker rm --force "${TEST_NAME}-app" || true - - docker stop "${TEST_NAME}-tester" || true - - docker rm --force "${TEST_NAME}-tester" || true - - docker network rm "${TEST_NAME}-network" || true diff --git a/tests/integration/codebuild/buildspec.os.debian.yml b/tests/integration/codebuild/buildspec.os.debian.yml deleted file mode 100644 index 44c061f..0000000 --- a/tests/integration/codebuild/buildspec.os.debian.yml +++ /dev/null @@ -1,111 +0,0 @@ -version: 0.2 - -env: - variables: - OS_DISTRIBUTION: debian - PYTHON_LOCATION: "/usr/local/bin/python" - TEST_NAME: "aws-lambda-python-rtc-debian-test" -batch: - build-matrix: - static: - ignore-failure: false - env: - privileged-mode: true - dynamic: - env: - variables: - DISTRO_VERSION: - - "bookworm" - - "bullseye" - RUNTIME_VERSION: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - - "3.13" -phases: - pre_build: - commands: - - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - - echo "Extracting and including the Runtime Interface Emulator" - - SCRATCH_DIR=".scratch" - - mkdir "${SCRATCH_DIR}" - - ARCHITECTURE=$(arch) - - > - if [[ "$ARCHITECTURE" == "x86_64" ]]; then - RIE="aws-lambda-rie" - elif [[ "$ARCHITECTURE" == "aarch64" ]]; then - RIE="aws-lambda-rie-arm64" - else - echo "Architecture $ARCHITECTURE is not currently supported." - exit 1 - fi - - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" - - > - cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo "RUN apt-get update && apt-get install -y curl" >> \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json - service docker restart - - echo "Building image ${IMAGE_TAG}" - - > - docker build . \ - -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ - -t "${IMAGE_TAG}" \ - --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ - --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ - --load - build: - commands: - - set -x - - echo "Running Image ${IMAGE_TAG}" - - docker network create "${TEST_NAME}-network" - - > - docker run \ - --detach \ - --name "${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" - - sleep 2 - - > - docker run \ - --name "${TEST_NAME}-tester" \ - --env "TARGET=${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' - - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)" - - expected='success' - - | - echo "Response: ${actual}" - if [[ "$actual" != "$expected" ]]; then - echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" - exit -1 - fi - finally: - - | - echo "---------Container Logs: ${TEST_NAME}-app----------" - echo - docker logs "${TEST_NAME}-app" || true - echo - echo "---------------------------------------------------" - echo "--------Container Logs: ${TEST_NAME}-tester--------" - echo - docker logs "${TEST_NAME}-tester" || true - echo - echo "---------------------------------------------------" - - echo "Cleaning up..." - - docker stop "${TEST_NAME}-app" || true - - docker rm --force "${TEST_NAME}-app" || true - - docker stop "${TEST_NAME}-tester" || true - - docker rm --force "${TEST_NAME}-tester" || true - - docker network rm "${TEST_NAME}-network" || true diff --git a/tests/integration/codebuild/buildspec.os.ubuntu.yml b/tests/integration/codebuild/buildspec.os.ubuntu.yml deleted file mode 100644 index a6e556d..0000000 --- a/tests/integration/codebuild/buildspec.os.ubuntu.yml +++ /dev/null @@ -1,109 +0,0 @@ -version: 0.2 - -env: - variables: - OS_DISTRIBUTION: ubuntu - PYTHON_LOCATION: "/usr/bin/python" - TEST_NAME: "aws-lambda-python-rtc-ubuntu-test" -batch: - build-matrix: - static: - ignore-failure: false - env: - privileged-mode: true - dynamic: - env: - variables: - DISTRO_VERSION: - - "22.04" - - "24.04" - RUNTIME_VERSION: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - - "3.13" -phases: - pre_build: - commands: - - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - - echo "Extracting and including the Runtime Interface Emulator" - - SCRATCH_DIR=".scratch" - - mkdir "${SCRATCH_DIR}" - - ARCHITECTURE=$(arch) - - > - if [[ "$ARCHITECTURE" == "x86_64" ]]; then - RIE="aws-lambda-rie" - elif [[ "$ARCHITECTURE" == "aarch64" ]]; then - RIE="aws-lambda-rie-arm64" - else - echo "Architecture $ARCHITECTURE is not currently supported." - exit 1 - fi - - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" - - > - cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ - "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" - - > - echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json - service docker restart - - echo "Building image ${IMAGE_TAG}" - - > - docker build . \ - -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ - -t "${IMAGE_TAG}" \ - --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ - --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ - --load - build: - commands: - - set -x - - echo "Running Image ${IMAGE_TAG}" - - docker network create "${TEST_NAME}-network" - - PYTHON_LOCATION=${PYTHON_LOCATION}${RUNTIME_VERSION} - - > - docker run \ - --detach \ - --name "${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" - - sleep 2 - - > - docker run \ - --name "${TEST_NAME}-tester" \ - --env "TARGET=${TEST_NAME}-app" \ - --network "${TEST_NAME}-network" \ - --entrypoint="" \ - "${IMAGE_TAG}" \ - sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' - - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)" - - expected='success' - - | - echo "Response: ${actual}" - if [[ "$actual" != "$expected" ]]; then - echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" - exit -1 - fi - finally: - - | - echo "---------Container Logs: ${TEST_NAME}-app----------" - echo - docker logs "${TEST_NAME}-app" || true - echo - echo "---------------------------------------------------" - echo "--------Container Logs: ${TEST_NAME}-tester--------" - echo - docker logs "${TEST_NAME}-tester" || true - echo - echo "---------------------------------------------------" - - echo "Cleaning up..." - - docker stop "${TEST_NAME}-app" || true - - docker rm --force "${TEST_NAME}-app" || true - - docker stop "${TEST_NAME}-tester" || true - - docker rm --force "${TEST_NAME}-tester" || true - - docker network rm "${TEST_NAME}-network" || true diff --git a/tests/integration/docker-compose.template.yml b/tests/integration/docker-compose.template.yml deleted file mode 100644 index 38db376..0000000 --- a/tests/integration/docker-compose.template.yml +++ /dev/null @@ -1,30 +0,0 @@ -version: '3.3' -services: - function: - build: - context: . - args: - RUNTIME_VERSION: "${runtime_version}" - DISTRO_VERSION: "${distro_version}" - dockerfile: ./docker/Dockerfile.echo.${DISTRO} - environment: - - AWS_LAMBDA_RUNTIME_API=runtime:9001 - - runtime: - build: - context: . - dockerfile: ../docker-helpers/Dockerfile.runtime - - invoker: - build: - context: . - dockerfile: ../docker-helpers/Dockerfile.aws-cli - entrypoint: [ - aws, lambda, invoke, - --endpoint, http://runtime:9001, - --no-sign-request, - --region, us-west-2, - --function-name, ignored, - --payload, '{ "name": "Lambda" }', - /dev/stdout - ] diff --git a/tests/integration/run-local.sh b/tests/integration/run-local.sh new file mode 100755 index 0000000..a603984 --- /dev/null +++ b/tests/integration/run-local.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Run a single integration test locally. +# Usage: run-local.sh + +set -euo pipefail + +if (( $# != 3 )); then + echo "usage: run-local.sh " + echo " e.g. run-local.sh alpine 3.20 3.13" + exit 1 +fi + +DISTRO="$1" +DISTRO_VERSION="$2" +RUNTIME_VERSION="$3" +TEST_NAME="ric-integ-test" +SCRATCH_DIR=".scratch" + +trap 'docker rm -f "${TEST_NAME}-app" "${TEST_NAME}-tester" 2>/dev/null || true; docker network rm "${TEST_NAME}-net" 2>/dev/null || true; rm -rf "$SCRATCH_DIR"' EXIT + +mkdir -p "$SCRATCH_DIR" + +ARCHITECTURE=$(arch) +if [[ "$ARCHITECTURE" == "x86_64" ]]; then + RIE="aws-lambda-rie" +elif [[ "$ARCHITECTURE" == "aarch64" ]]; then + RIE="aws-lambda-rie-arm64" +else + echo "Architecture $ARCHITECTURE is not currently supported." + exit 1 +fi + +tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "$SCRATCH_DIR" + +DOCKERFILE="tests/integration/docker/Dockerfile.echo.${DISTRO}" +TMPFILE="$SCRATCH_DIR/Dockerfile.tmp" +cp "$DOCKERFILE" "$TMPFILE" +if [[ "$DISTRO" == "alpine" ]]; then + echo "RUN apk add curl" >> "$TMPFILE" +fi +echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> "$TMPFILE" + +echo "Building image for ${DISTRO} ${DISTRO_VERSION} / python ${RUNTIME_VERSION}..." +docker build . \ + -f "$TMPFILE" \ + -t ric-test \ + --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ + --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ + --build-arg ARCHITECTURE="${ARCHITECTURE}" + +# Determine python location +case "$DISTRO" in + alpine|debian) PYTHON_LOCATION="/usr/local/bin/python" ;; + amazonlinux2|amazonlinux2023) PYTHON_LOCATION="/usr/local/bin/python3" ;; + ubuntu) PYTHON_LOCATION="/usr/bin/python${RUNTIME_VERSION}" ;; + *) echo "Unknown distro: $DISTRO"; exit 1 ;; +esac + +echo "Running integration test..." +docker network create "${TEST_NAME}-net" + +docker run \ + --detach \ + --name "${TEST_NAME}-app" \ + --network "${TEST_NAME}-net" \ + --entrypoint="" \ + ric-test \ + sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" + +sleep 2 + +docker run \ + --name "${TEST_NAME}-tester" \ + --env "TARGET=${TEST_NAME}-app" \ + --network "${TEST_NAME}-net" \ + --entrypoint="" \ + ric-test \ + sh -c 'curl -sS -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' + +ACTUAL="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)" +EXPECTED="success" +echo "Response: ${ACTUAL}" +if [ "$ACTUAL" != "$EXPECTED" ]; then + echo "FAIL: expected '${EXPECTED}', got '${ACTUAL}'" + docker logs "${TEST_NAME}-app" 2>&1 || true + exit 1 +fi +echo "PASS" From 2a9e4f23e55f757d00cd1841f272ee1bd722e263 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 17 Jun 2026 20:21:43 +0000 Subject: [PATCH 08/14] fix: branch --- .github/workflows/test-on-push-and-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml index 6f84385..3171a8f 100644 --- a/.github/workflows/test-on-push-and-pr.yml +++ b/.github/workflows/test-on-push-and-pr.yml @@ -4,7 +4,7 @@ on: push: branches: [ main ] pull_request: - branches: [ '*' ] + branches: [ '**' ] permissions: contents: read From f07f335062b227d172b25a5c6592f24a585cb225 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 17 Jun 2026 20:41:33 +0000 Subject: [PATCH 09/14] fix: install curl for debian --- tests/integration/docker/Dockerfile.echo.debian | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/docker/Dockerfile.echo.debian b/tests/integration/docker/Dockerfile.echo.debian index bf0f4fa..5c14eb9 100644 --- a/tests/integration/docker/Dockerfile.echo.debian +++ b/tests/integration/docker/Dockerfile.echo.debian @@ -44,6 +44,8 @@ RUN pip install \ # Grab a fresh slim copy of the Python image FROM public.ecr.aws/docker/library/python:${RUNTIME_VERSION}-slim-${DISTRO_VERSION} +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + # Include global arg in this stage of the build ARG FUNCTION_DIR="/home/app/" From 6a51f51b5535d745f506462fbe1b661c77df5dc4 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 18 Jun 2026 11:55:23 +0000 Subject: [PATCH 10/14] fix: pr comments --- tests/test_concurrency.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 4a6451a..2c55bf3 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -18,18 +18,19 @@ def setUp(self): self.socket = "/tmp/sock" def test_success_and_failure_isolation(self): - ctx = multiprocessing.get_context("fork") - success_counter = ctx.Value("i", 0) - fail_counter = ctx.Value("i", 0) + manager = multiprocessing.Manager() + success_counter = manager.Value("i", 0) + fail_counter = manager.Value("i", 0) + lock = manager.Lock() def fake_bootstrap_run(handler, lambda_runtime_client): pid = multiprocessing.current_process().pid if pid % 2 == 0: for _ in range(3): - with success_counter.get_lock(): + with lock: success_counter.value += 1 else: - with fail_counter.get_lock(): + with lock: fail_counter.value += 1 raise RuntimeError("Simulated failure") @@ -38,17 +39,14 @@ def fake_bootstrap_run(handler, lambda_runtime_client): ), patch( "awslambdaric.lambda_multi_concurrent_utils.bootstrap.run", side_effect=fake_bootstrap_run, - ), patch( - "awslambdaric.lambda_multi_concurrent_utils.multiprocessing.Process", - ctx.Process, ): - # spawn 4 multi-concurrent processes MultiConcurrentRunner.run_concurrent( self.handler, self.addr, self.use_thread, self.socket, max_concurrency=4 ) self.assertEqual(success_counter.value, 6) self.assertEqual(fail_counter.value, 2) + manager.shutdown() if __name__ == "__main__": From f649f7b353fdc658e5ca8c3b9c0f135c320be6e6 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 18 Jun 2026 12:00:45 +0000 Subject: [PATCH 11/14] fix: test --- tests/test_concurrency.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 2c55bf3..71271a5 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -21,11 +21,14 @@ def test_success_and_failure_isolation(self): manager = multiprocessing.Manager() success_counter = manager.Value("i", 0) fail_counter = manager.Value("i", 0) + process_index = manager.Value("i", 0) lock = manager.Lock() def fake_bootstrap_run(handler, lambda_runtime_client): - pid = multiprocessing.current_process().pid - if pid % 2 == 0: + with lock: + idx = process_index.value + process_index.value += 1 + if idx % 2 == 0: for _ in range(3): with lock: success_counter.value += 1 From 35a7381573495ed535068e61463502f6863ae0a0 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 18 Jun 2026 12:17:37 +0000 Subject: [PATCH 12/14] fix: tests --- tests/test_concurrency.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 71271a5..9221382 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -2,7 +2,7 @@ Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. """ -import multiprocessing +import threading import unittest from unittest.mock import patch, MagicMock @@ -18,23 +18,23 @@ def setUp(self): self.socket = "/tmp/sock" def test_success_and_failure_isolation(self): - manager = multiprocessing.Manager() - success_counter = manager.Value("i", 0) - fail_counter = manager.Value("i", 0) - process_index = manager.Value("i", 0) - lock = manager.Lock() + success_counter = 0 + fail_counter = 0 + process_index = 0 + lock = threading.Lock() def fake_bootstrap_run(handler, lambda_runtime_client): + nonlocal success_counter, fail_counter, process_index with lock: - idx = process_index.value - process_index.value += 1 + idx = process_index + process_index += 1 if idx % 2 == 0: for _ in range(3): with lock: - success_counter.value += 1 + success_counter += 1 else: with lock: - fail_counter.value += 1 + fail_counter += 1 raise RuntimeError("Simulated failure") with patch( @@ -42,14 +42,17 @@ def fake_bootstrap_run(handler, lambda_runtime_client): ), patch( "awslambdaric.lambda_multi_concurrent_utils.bootstrap.run", side_effect=fake_bootstrap_run, + ), patch( + "awslambdaric.lambda_multi_concurrent_utils.multiprocessing.Process", + threading.Thread, ): + # spawn 4 multi-concurrent processes MultiConcurrentRunner.run_concurrent( self.handler, self.addr, self.use_thread, self.socket, max_concurrency=4 ) - self.assertEqual(success_counter.value, 6) - self.assertEqual(fail_counter.value, 2) - manager.shutdown() + self.assertEqual(success_counter, 6) + self.assertEqual(fail_counter, 2) if __name__ == "__main__": From f19187b256cf7ec2103850e193a823ea14372ca0 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 18 Jun 2026 13:24:10 +0000 Subject: [PATCH 13/14] fix: remove mock --- tests/test_concurrency.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 9221382..17eda0a 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -42,14 +42,18 @@ def fake_bootstrap_run(handler, lambda_runtime_client): ), patch( "awslambdaric.lambda_multi_concurrent_utils.bootstrap.run", side_effect=fake_bootstrap_run, - ), patch( - "awslambdaric.lambda_multi_concurrent_utils.multiprocessing.Process", - threading.Thread, ): # spawn 4 multi-concurrent processes - MultiConcurrentRunner.run_concurrent( - self.handler, self.addr, self.use_thread, self.socket, max_concurrency=4 - ) + threads = [] + for _ in range(4): + t = threading.Thread( + target=MultiConcurrentRunner.run_single, + args=(self.handler, self.addr, self.use_thread, self.socket), + ) + t.start() + threads.append(t) + for t in threads: + t.join() self.assertEqual(success_counter, 6) self.assertEqual(fail_counter, 2) From dd73fd4f96796026e7b44d3824616589b1d1583a Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 18 Jun 2026 13:30:47 +0000 Subject: [PATCH 14/14] fix: tests --- tests/test_concurrency.py | 44 ++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 17eda0a..bc104fd 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -2,7 +2,7 @@ Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. """ -import threading +import multiprocessing import unittest from unittest.mock import patch, MagicMock @@ -18,23 +18,22 @@ def setUp(self): self.socket = "/tmp/sock" def test_success_and_failure_isolation(self): - success_counter = 0 - fail_counter = 0 - process_index = 0 - lock = threading.Lock() + multiprocessing.set_start_method("fork", force=True) + success_counter = multiprocessing.Value("i", 0) + fail_counter = multiprocessing.Value("i", 0) + process_index = multiprocessing.Value("i", 0) def fake_bootstrap_run(handler, lambda_runtime_client): - nonlocal success_counter, fail_counter, process_index - with lock: - idx = process_index - process_index += 1 + with process_index.get_lock(): + idx = process_index.value + process_index.value += 1 if idx % 2 == 0: for _ in range(3): - with lock: - success_counter += 1 + with success_counter.get_lock(): + success_counter.value += 1 else: - with lock: - fail_counter += 1 + with fail_counter.get_lock(): + fail_counter.value += 1 raise RuntimeError("Simulated failure") with patch( @@ -44,19 +43,12 @@ def fake_bootstrap_run(handler, lambda_runtime_client): side_effect=fake_bootstrap_run, ): # spawn 4 multi-concurrent processes - threads = [] - for _ in range(4): - t = threading.Thread( - target=MultiConcurrentRunner.run_single, - args=(self.handler, self.addr, self.use_thread, self.socket), - ) - t.start() - threads.append(t) - for t in threads: - t.join() - - self.assertEqual(success_counter, 6) - self.assertEqual(fail_counter, 2) + MultiConcurrentRunner.run_concurrent( + self.handler, self.addr, self.use_thread, self.socket, max_concurrency=4 + ) + + self.assertEqual(success_counter.value, 6) + self.assertEqual(fail_counter.value, 2) if __name__ == "__main__":