diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 00000000000..4b6cc4c4bc7 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,423 @@ +name: Integration Tests + +on: + schedule: + # Run at 7pm PST (3am UTC next day) Monday-Friday + # This translates to Tuesday-Saturday 3am UTC + - cron: '0 3 * * 2-6' + workflow_dispatch: # Allow manual triggering + # NOTE: This workflow can only be manually triggered from develop or main branches + # The other branch will not pass the OIDC permission check + branches: + - develop # Allow manual trigger on develop branch + - main # Allow manual trigger on main branch + +permissions: + id-token: write # Required for OIDC + contents: read + +env: + AWS_DEFAULT_REGION: us-east-1 + SAM_CLI_DEV: 1 + SAM_CLI_TELEMETRY: 0 + SAM_CLI_CONTAINER_CONNECTION_TIMEOUT: 60 + NODE_VERSION: "22.21.1" + AWS_S3: "AWS_S3_TESTING" + AWS_ECR: "AWS_ECR_TESTING" + CARGO_LAMBDA_VERSION: "v0.17.1" + NOSE_PARAMETERIZED_NO_WARN: 1 + BY_CANARY: true + UV_PYTHON: python3.9 + CREDENTIAL_DISTRIBUTION_LAMBDA_ARN: ${{ secrets.CREDENTIAL_DISTRIBUTION_LAMBDA_ARN }} + ACCOUNT_RESET_LAMBDA_ARN: ${{ secrets.ACCOUNT_RESET_LAMBDA_ARN }} + +jobs: + integration-tests: + # Only run scheduled jobs on the main aws/aws-sam-cli repository, not on forks + if: github.event_name != 'schedule' || github.repository == 'aws/aws-sam-cli' + name: ${{ matrix.test_suite }} (${{ matrix.container_runtime }}) + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + # container_runtime: [docker, finch, no-container] + container_runtime: [docker, finch , no-container] + test_suite: + - build-integ + - build-integ-java-python-provided + - build-integ-dotnet-node-ruby + - build-integ-arm64 + - build-integ-arm64-java + - terraform-build + - package-delete-deploy + - sync + - local-invoke + - local-start + - local-start2 + - other-and-e2e + exclude: + # no-container mode only applies to build-integ test suites + - container_runtime: no-container + test_suite: terraform-build + - container_runtime: no-container + test_suite: sync + - container_runtime: no-container + test_suite: package-delete-deploy + - container_runtime: no-container + test_suite: local-invoke + - container_runtime: no-container + test_suite: local-start + - container_runtime: no-container + test_suite: local-start2 + - container_runtime: no-container + test_suite: other-and-e2e + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + # For scheduled runs, always use develop + # For manual runs, use the branch from "Use workflow from" dropdown + ref: ${{ github.event_name == 'schedule' && 'develop' || github.ref }} + + - name: Free up disk space + run: | + echo "Disk space before cleanup:" + df -h + + # Remove .NET if not needed (not other-and-e2e and not no-container) + if [ "${{ matrix.test_suite }}" != "other-and-e2e" ] && [ "${{ matrix.test_suite }}" != "sync" ] && [ "${{ matrix.container_runtime }}" != "no-container" ]; then + echo "Removing .NET to free up disk space..." + nohup sudo rm -rf /usr/share/dotnet & + fi + + # Run cleanup in background to not block workflow + nohup bash -c ' + sudo rm -rf /usr/local/share/powershell /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL + sudo apt-get clean + ' > /dev/null 2>&1 & + + # Remove all existing Docker images to free up space + echo "Removing all existing Docker images..." + if [ "${{ matrix.container_runtime }}" = "finch" ]; then + # For finch, run as blocking command to ensure cleanup completes + docker system prune -af --volumes || true + else + # For docker and no-container, run in background + nohup docker system prune -af --volumes || true & + fi + + - name: Configure AWS credentials via OIDC + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.OIDC_ROLE_ARN }} + aws-region: us-east-1 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: | + 3.11 + 3.9 + 3.10 + 3.12 + 3.13 + + - name: Set up Node.js + if: contains(fromJSON('["build-integ", "build-integ-java-python-provided", "build-integ-dotnet-node-ruby", "build-integ-arm64"]'), matrix.test_suite) && matrix.container_runtime == 'no-container' + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Set up Java 21 + if: (contains(fromJSON('["build-integ", "build-integ-java-python-provided", "build-integ-arm64", "build-integ-arm64-java"]'), matrix.test_suite) && matrix.container_runtime == 'no-container') || matrix.test_suite == 'sync' + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '21' + + - name: Set up Go + if: contains(fromJSON('["build-integ", "build-integ-java-python-provided", "build-integ-arm64"]'), matrix.test_suite) && matrix.container_runtime == 'no-container' + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Install Maven and Gradle + if: (contains(fromJSON('["build-integ", "build-integ-java-python-provided", "build-integ-arm64", "build-integ-arm64-java"]'), matrix.test_suite) && matrix.container_runtime == 'no-container') || matrix.test_suite == 'sync' + run: | + # Remove system Maven if it exists + sudo apt-get remove -y maven || true + + # Install Maven 3.9.11 + wget https://dlcdn.apache.org/maven/maven-3/3.9.11/binaries/apache-maven-3.9.11-bin.zip -P /tmp + sudo unzip -d /opt/mvn /tmp/apache-maven-*.zip + + # Install Gradle 9.0.0 + wget https://services.gradle.org/distributions/gradle-9.0.0-bin.zip -P /tmp + sudo unzip -d /opt/gradle /tmp/gradle-*.zip + + # Create symlinks to ensure our Maven is used + sudo ln -sf /opt/mvn/apache-maven-3.9.11/bin/mvn /usr/local/bin/mvn + sudo ln -sf /opt/gradle/gradle-9.0.0/bin/gradle /usr/local/bin/gradle + + # Add to PATH (prepend to ensure our versions are used first) + echo "/opt/mvn/apache-maven-3.9.11/bin" >> $GITHUB_PATH + echo "/opt/gradle/gradle-9.0.0/bin" >> $GITHUB_PATH + + # Set MAVEN_HOME + echo "MAVEN_HOME=/opt/mvn/apache-maven-3.9.11" >> $GITHUB_ENV + + # Verify versions + export PATH="/opt/mvn/apache-maven-3.9.11/bin:/opt/gradle/gradle-9.0.0/bin:$PATH" + mvn --version + gradle --version + + - name: Install .NET 8 SDK + if: contains(fromJSON('["build-integ-java-python-provided", "build-integ-dotnet-node-ruby", "build-integ-arm64"]'), matrix.test_suite) && matrix.container_runtime == 'no-container' || matrix.test_suite == 'other-and-e2e' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Set up Ruby 3.3.7 + if: (contains(fromJSON('["build-integ","build-integ-dotnet-node-ruby", "build-integ-arm64"]'), matrix.test_suite) && matrix.container_runtime == 'no-container') || matrix.test_suite == 'other-and-e2e' + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3.7' + + - name: Set up Ruby 3.2.7 + if: (contains(fromJSON('["build-integ","build-integ-dotnet-node-ruby", "build-integ-arm64"]'), matrix.test_suite) && matrix.container_runtime == 'no-container') || matrix.test_suite == 'other-and-e2e' + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2.7' + + # sync test only need ruby 3.4 + - name: Set up Ruby 3.4.2 + if: (contains(fromJSON('["build-integ","build-integ-dotnet-node-ruby", "build-integ-arm64"]'), matrix.test_suite) && matrix.container_runtime == 'no-container') || contains(fromJSON('["sync", "other-and-e2e"]'), matrix.test_suite) + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.4.7' + + + - name: Install Rust toolchain + if: contains(fromJSON('["build-integ", "build-integ-java-python-provided", "build-integ-arm64"]'), matrix.test_suite) && matrix.container_runtime == 'no-container' + run: | + curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL https://sh.rustup.rs | sh -s -- --default-toolchain none -y + source $HOME/.cargo/env + rustup toolchain install stable --profile minimal --no-self-update + rustup default stable + rustup target add x86_64-unknown-linux-gnu --toolchain stable + rustup target add aarch64-unknown-linux-gnu --toolchain stable + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Install cargo-lambda + if: contains(fromJSON('["build-integ", "build-integ-java-python-provided", "build-integ-arm64"]'), matrix.test_suite) && matrix.container_runtime == 'no-container' + run: pip install cargo-lambda==${{ env.CARGO_LAMBDA_VERSION }} + + - name: Install Terraform + if: contains(fromJSON('["terraform-build", "local-invoke", "local-start2"]'), matrix.test_suite) + run: | + for i in {1..3}; do + TER_VER=$(curl -s https://api.github.com/repos/hashicorp/terraform/releases/latest | grep tag_name | cut -d: -f2 | tr -d \"\,\v | awk '{$1=$1};1') + if [ -n "$TER_VER" ]; then + if wget https://releases.hashicorp.com/terraform/${TER_VER}/terraform_${TER_VER}_linux_amd64.zip -P /tmp; then + sudo unzip -d /opt/terraform /tmp/terraform_${TER_VER}_linux_amd64.zip + sudo mv /opt/terraform/terraform /usr/local/bin/ + terraform -version + break + fi + fi + echo "Terraform installation attempt $i failed, retrying..." + sleep 5 + done + + - name: Setup Finch runtime + if: matrix.container_runtime == 'finch' + run: | + echo "=== Initializing Finch runtime ===" + sudo systemctl stop docker || true + sudo systemctl stop docker.socket || true + sudo systemctl disable docker || true + sudo systemctl disable docker.socket || true + + for i in {1..3}; do + if curl -fsSL https://artifact.runfinch.com/deb/GPG_KEY.pub | sudo gpg --dearmor -o /usr/share/keyrings/runfinch-finch-archive-keyring.gpg; then + break + fi + sleep 10 + done + + echo 'deb [signed-by=/usr/share/keyrings/runfinch-finch-archive-keyring.gpg arch=amd64] https://artifact.runfinch.com/deb noble main' | sudo tee /etc/apt/sources.list.d/runfinch-finch.list + sudo apt update + sudo apt install -y runfinch-finch + sudo systemctl enable --now finch + sudo systemctl enable --now finch-buildkit + sleep 3 + sudo chmod 666 /var/run/finch.sock + + for i in {1..12}; do + if sudo finch info >/dev/null 2>&1; then + break + fi + sleep 5 + done + + sudo mkdir -p /run/buildkit-finch /run/buildkit-default /run/buildkit + sudo ln -sf /var/lib/finch/buildkit/buildkitd.sock /run/buildkit-finch/buildkitd.sock + sudo ln -sf /var/lib/finch/buildkit/buildkitd.sock /run/buildkit-default/buildkitd.sock + sudo ln -sf /var/lib/finch/buildkit/buildkitd.sock /run/buildkit/buildkitd.sock + sudo chmod 666 /var/lib/finch/buildkit/buildkitd.sock + sudo chmod 666 /run/buildkit-*/buildkitd.sock + sudo finch run --privileged --rm tonistiigi/binfmt:master --install all + + sudo finch info + sudo finch version + + - name: Setup Docker runtime + if: matrix.container_runtime == 'docker' + run: | + echo "=== Initializing Docker runtime ===" + docker info + docker version + + - name: Setup QEMU for ARM64 emulation + if: matrix.container_runtime != 'no-container' + run: | + if [ "${{ matrix.container_runtime }}" = "finch" ]; then + sudo finch run --rm --privileged multiarch/qemu-user-static --reset -p yes + else + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + fi + + - name: Initialize project + run: | + export CONTAINER_RUNTIME=${{ matrix.container_runtime }} + make init + + - name: Get testing resources and credentials + run: | + # Try with skip_role_deletion parameter first + test_env_var=$(python3.9 tests/get_testing_resources.py skip_role_deletion 2>&1) + + if [ $? -ne 0 ]; then + echo "First attempt with skip_role_deletion failed, trying without parameter..." + test_env_var=$(python3.9 tests/get_testing_resources.py) + + if [ $? -ne 0 ]; then + echo "get_testing_resources failed. Failed to acquire credentials or test resources." + exit 1 + fi + fi + + # Save current credentials for account reset later + echo "CI_ACCESS_ROLE_AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID" >> $GITHUB_ENV + echo "CI_ACCESS_ROLE_AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY" >> $GITHUB_ENV + echo "CI_ACCESS_ROLE_AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN" >> $GITHUB_ENV + + # Set test credentials + echo "AWS_ACCESS_KEY_ID=$(echo "$test_env_var" | jq -j ".accessKeyID")" >> $GITHUB_ENV + echo "AWS_SECRET_ACCESS_KEY=$(echo "$test_env_var" | jq -j ".secretAccessKey")" >> $GITHUB_ENV + echo "AWS_SESSION_TOKEN=$(echo "$test_env_var" | jq -j ".sessionToken")" >> $GITHUB_ENV + echo "TASK_TOKEN=$(echo "$test_env_var" | jq -j ".taskToken")" >> $GITHUB_ENV + echo "AWS_S3_TESTING=$(echo "$test_env_var" | jq -j ".TestBucketName")" >> $GITHUB_ENV + echo "AWS_ECR_TESTING=$(echo "$test_env_var" | jq -j ".TestECRURI")" >> $GITHUB_ENV + echo "AWS_KMS_KEY=$(echo "$test_env_var" | jq -j ".TestKMSKeyArn")" >> $GITHUB_ENV + echo "AWS_SIGNING_PROFILE_NAME=$(echo "$test_env_var" | jq -j ".TestSigningProfileName")" >> $GITHUB_ENV + echo "AWS_SIGNING_PROFILE_VERSION_ARN=$(echo "$test_env_var" | jq -j ".TestSigningProfileARN")" >> $GITHUB_ENV + + # Display first 6 characters of credentials for verification + ACCESS_KEY=$(echo "$test_env_var" | jq -j ".accessKeyID") + SECRET_KEY=$(echo "$test_env_var" | jq -j ".secretAccessKey") + echo "AWS_ACCESS_KEY_ID (first 6 chars): ${ACCESS_KEY:0:6}..." + echo "AWS_SECRET_ACCESS_KEY (first 6 chars): ${SECRET_KEY:0:6}..." + + + - name: Login to Public ECR + if: matrix.container_runtime != 'no-container' && env.BY_CANARY == 'true' + run: | + if [ "${{ matrix.container_runtime }}" = "finch" ]; then + echo "Logging in Public ECR with Finch" + aws ecr-public get-login-password --region us-east-1 | sudo finch login --username AWS --password-stdin public.ecr.aws || { echo "FATAL: Finch Public ECR login failed"; exit 1; } + else + echo "Logging in Public ECR with Docker" + aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws || { echo "FATAL: Docker Public ECR login failed"; exit 1; } + fi + echo "Public ECR authentication completed successfully" + + - name: Run tests + run: | + # Set USING_FINCH_RUNTIME environment variable for finch tests + if [ "${{ matrix.container_runtime }}" = "finch" ]; then + export container_runtime="finch" + fi + + # Determine container keyword filter based on container_runtime + if [ "${{ matrix.container_runtime }}" = "no-container" ]; then + CONTAINER_FILTER="not container" + else + CONTAINER_FILTER=container + fi + + case "${{ matrix.test_suite }}" in + "build-integ") + pytest -vv -n 2 --reruns 3 tests/integration/buildcmd -m 'not java and not python and not provided and not dotnet and not nodejs and not ruby' --ignore=tests/integration/buildcmd/test_build_cmd_arm64.py --ignore=tests/integration/buildcmd/test_build_terraform_applications.py --ignore=tests/integration/buildcmd/test_build_terraform_applications_other_cases.py -k "${CONTAINER_FILTER}" --json-report --json-report-file=TEST_REPORT-integration-buildcmd-${{ matrix.container_runtime }}.json + ;; + "build-integ-java-python-provided") + pytest -vv -n 2 --reruns 3 tests/integration/buildcmd -m 'java or python or provided' --ignore=tests/integration/buildcmd/test_build_cmd_arm64.py --ignore=tests/integration/buildcmd/test_build_terraform_applications.py --ignore=tests/integration/buildcmd/test_build_terraform_applications_other_cases.py -k "${CONTAINER_FILTER}" --json-report --json-report-file=TEST_REPORT-integration-buildcmd-java-python-provided-${{ matrix.container_runtime }}.json + ;; + "build-integ-dotnet-node-ruby") + pytest -vv -n 2 --reruns 3 tests/integration/buildcmd -m 'dotnet or nodejs or ruby' --ignore=tests/integration/buildcmd/test_build_cmd_arm64.py --ignore=tests/integration/buildcmd/test_build_terraform_applications.py --ignore=tests/integration/buildcmd/test_build_terraform_applications_other_cases.py -k "${CONTAINER_FILTER}" --json-report --json-report-file=TEST_REPORT-build-integ-dotnet-node-ruby-${{ matrix.container_runtime }}.json + ;; + "build-integ-arm64") + pytest -vv -n 2 --reruns 3 tests/integration/buildcmd/test_build_cmd_arm64.py -m 'not java' -k "${CONTAINER_FILTER}" --json-report --json-report-file=TEST_REPORT-integration-buildcmd-arm64-${{ matrix.container_runtime }}.json + ;; + "build-integ-arm64-java") + pytest -vv -n 2 --reruns 3 tests/integration/buildcmd/test_build_cmd_arm64.py -m 'java' -k "${CONTAINER_FILTER}" --json-report --json-report-file=TEST_REPORT-integration-buildcmd-arm64-java-${{ matrix.container_runtime }}.json + ;; + "terraform-build") + pytest -vv -n 4 --reruns 4 tests/integration/buildcmd/test_build_terraform_applications.py tests/integration/buildcmd/test_build_terraform_applications_other_cases.py --json-report --json-report-file=TEST_REPORT-integration-terraform-${{ matrix.container_runtime }}.json + ;; + "package-delete-deploy") + pytest -vv tests/integration/package tests/integration/delete tests/integration/deploy --dist=loadgroup -n 6 --reruns 4 --json-report --json-report-file=TEST_REPORT-integration-package-delete-deploy-${{ matrix.container_runtime }}.json + ;; + "sync") + pytest -vv tests/integration/sync -n 6 --reruns 3 --dist loadscope --json-report --json-report-file=TEST_REPORT-integration-sync-${{ matrix.container_runtime }}.json + ;; + "local-invoke") + pytest -vv --reruns 3 tests/integration/local/invoke tests/integration/local/generate_event --json-report --json-report-file=TEST_REPORT-integration-local-invoke-${{ matrix.container_runtime }}.json + ;; + "local-start") + pytest -vv --reruns 3 tests/integration/local/start_api --ignore tests/integration/local/start_api/test_start_api_with_terraform_application.py --json-report --json-report-file=TEST_REPORT-integration-local-start-${{ matrix.container_runtime }}.json + ;; + "local-start2") + pytest -vv --reruns 3 tests/integration/local/start_lambda tests/integration/local/start_api/test_start_api_with_terraform_application.py --json-report --json-report-file=TEST_REPORT-integration-local-start2-${{ matrix.container_runtime }}.json + ;; + "other-and-e2e") + pytest -vv -n 4 --reruns 4 --dist loadgroup tests/integration tests/end_to_end --ignore=tests/integration/buildcmd --ignore=tests/integration/delete --ignore=tests/integration/deploy --ignore=tests/integration/package --ignore=tests/integration/sync --ignore=tests/integration/local --json-report --json-report-file=TEST_REPORT-integration-others-${{ matrix.container_runtime }}.json + pytest -vv --reruns 3 tests/regression --json-report --json-report-file=TEST_REPORT-regression-${{ matrix.container_runtime }}.json + ;; + esac + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ matrix.test_suite }}-${{ matrix.container_runtime }} + path: TEST_REPORT-*.json + + - name: Reset test account + if: always() + run: | + # Switch back to TAM caller role credentials + export AWS_ACCESS_KEY_ID=$CI_ACCESS_ROLE_AWS_ACCESS_KEY_ID + export AWS_SECRET_ACCESS_KEY=$CI_ACCESS_ROLE_AWS_SECRET_ACCESS_KEY + export AWS_SESSION_TOKEN=$CI_ACCESS_ROLE_AWS_SESSION_TOKEN + + # Invoke account reset Lambda with raw binary format + aws lambda invoke \ + --function-name "$ACCOUNT_RESET_LAMBDA_ARN" \ + --payload "{\"taskToken\": \"$TASK_TOKEN\", \"output\": \"{}\"}" \ + ./lambda-output.txt \ + --region us-west-2 \ + --cli-binary-format raw-in-base64-out + + cat ./lambda-output.txt diff --git a/samcli/commands/remote/invoke/cli.py b/samcli/commands/remote/invoke/cli.py index b9919fda71f..9bc1469e614 100644 --- a/samcli/commands/remote/invoke/cli.py +++ b/samcli/commands/remote/invoke/cli.py @@ -173,7 +173,7 @@ def do_cli( event = lambda_test_event["json"] LOG.debug("Remote event contents: %s", event) metadata = lambda_test_event["metadata"] - if "invocationType" in metadata: + if "invocationType" in metadata and metadata["invocationType"] is not None: parameter.setdefault("InvocationType", metadata["invocationType"]) elif test_event_name: LOG.info("Note: remote event is only supported for AWS Lambda Function resource.") diff --git a/samcli/lib/utils/name_utils.py b/samcli/lib/utils/name_utils.py index ce74816e268..64f2476f882 100644 --- a/samcli/lib/utils/name_utils.py +++ b/samcli/lib/utils/name_utils.py @@ -12,7 +12,7 @@ MIN_ARN_PARTS = 5 # Minimum parts for a valid ARN structure LAMBDA_FUNCTION_NAME_PATTERN = ( - r"^(arn:[^:]+:lambda:[^:]*:\d{12}:function:|\d{12}:function:)?[a-zA-Z0-9-_\.]+(:[\w$-]+)?$" + r"^(arn:[^:]+:lambda:[^:]*:\d{12}:function:|\d{12}:function:)?[a-zA-Z0-9-_\.\[\]]+(:[\w$-]+)?$" ) diff --git a/tests/get_testing_resources.py b/tests/get_testing_resources.py index c91a79fd77c..1df5d622ad4 100644 --- a/tests/get_testing_resources.py +++ b/tests/get_testing_resources.py @@ -5,6 +5,7 @@ import json import os +import sys import boto3 from boto3.session import Session @@ -16,7 +17,8 @@ def main(): - env_vars = get_testing_credentials() + skip_role_deletion = len(sys.argv) > 1 and sys.argv[1] == "skip_role_deletion" + env_vars = get_testing_credentials(skip_role_deletion) # Assume testing account credential in order to access managed test resource stack test_session = Session( aws_access_key_id=env_vars["accessKeyID"], @@ -37,7 +39,7 @@ def get_managed_test_resource_outputs(session: Session): return outputs_dict -def get_testing_credentials(): +def get_testing_credentials(skip_role_deletion=False): lambda_arn = os.environ["CREDENTIAL_DISTRIBUTION_LAMBDA_ARN"] # Max attempts to 0 so that boto3 will not invoke multiple times lambda_client = boto3.client( @@ -49,7 +51,14 @@ def get_testing_credentials(): ), region_name="us-west-2", ) - response = lambda_client.invoke(FunctionName=lambda_arn) + + # Prepare payload if skip_role_deletion is True + if skip_role_deletion: + payload_data = json.dumps({"skip_role_deletion": True}) + response = lambda_client.invoke(FunctionName=lambda_arn, Payload=payload_data) + else: + response = lambda_client.invoke(FunctionName=lambda_arn) + payload = json.loads(response["Payload"].read()) if response.get("FunctionError"): raise ValueError(f"Failed to get credential. {payload['errorType']}") diff --git a/tests/integration/buildcmd/build_integ_base.py b/tests/integration/buildcmd/build_integ_base.py index 35f8b1e0f33..5edca627b1c 100644 --- a/tests/integration/buildcmd/build_integ_base.py +++ b/tests/integration/buildcmd/build_integ_base.py @@ -7,6 +7,7 @@ import time import logging import json +from datetime import datetime, timezone from typing import Optional from unittest import TestCase @@ -36,6 +37,27 @@ LOG = logging.getLogger(__name__) +def show_container_in_test_name(testcase_func, param_num, param): + """ + Generates a custom name for parameterized test cases. + Adds '_in_container' suffix when any parameter contains 'container' in its string representation. + """ + # Get the base test name + base_name = f"{testcase_func.__name__}_{param_num}" + + # Check if any parameter contains "container" in its string representation + for arg in param.args: + if isinstance(arg, str) and "container" in arg.lower(): + base_name += "_in_container" + break + elif arg is True: # Also check for boolean True which might indicate use_container + # Check if this might be a use_container parameter by position + # This is a fallback for cases where True is used instead of "use_container" + continue + + return base_name + + class BuildIntegBase(TestCase): template: Optional[str] = "template.yaml" @@ -543,6 +565,12 @@ def _test_with_go(self, runtime, code_uri, mode, relative_path, architecture=Non newenv["GOPROXY"] = "direct" newenv["GOPATH"] = str(self.working_dir) + # Build with musl target to avoid glibc compatibility issues + # This ensures the binary works in the Lambda execution environment + newenv["GOOS"] = "linux" + newenv["GOARCH"] = "arm64" if architecture == ARM64 else "amd64" + newenv["CGO_ENABLED"] = "0" + run_command(cmdlist, cwd=self.working_dir, env=newenv) self._verify_built_artifact( @@ -638,7 +666,7 @@ def _test_with_building_java( osutils.convert_to_unix_line_ending(os.path.join(self.test_data_path, self.USING_GRADLEW_PATH, "gradlew")) # Use shorter timeout in GitHub Actions to fail faster; # Putting 1800 because Windows Canary Instances takes longer - timeout = 90 if os.environ.get("GITHUB_ACTIONS") else 1800 + timeout = 1800 if os.environ.get("BY_CANARY") else 180 run_command(cmdlist, cwd=self.working_dir, timeout=timeout) self._verify_built_artifact( diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py index b5e4f0e8f55..d8351d4eb5e 100644 --- a/tests/integration/buildcmd/test_build_cmd.py +++ b/tests/integration/buildcmd/test_build_cmd.py @@ -36,6 +36,7 @@ IntrinsicIntegBase, BuildIntegGoBase, BuildIntegPythonBase, + show_container_in_test_name, ) @@ -46,7 +47,7 @@ @skipIf(SKIP_DOCKER_TESTS, SKIP_DOCKER_MESSAGE) -class TestBuildingImageTypeLambdaDockerFileFailures(BuildIntegBase): +class TestBuildingImageTypeLambdaDockerFileFailuresContainer(BuildIntegBase): template = "template_image.yaml" def test_with_invalid_dockerfile_location(self): @@ -86,7 +87,7 @@ def test_with_invalid_dockerfile_definition(self): @skipIf(SKIP_DOCKER_TESTS, SKIP_DOCKER_MESSAGE) -class TestLoadingImagesFromArchive(BuildIntegBase): +class TestLoadingImagesFromArchiveContainer(BuildIntegBase): template = "template_loadable_image.yaml" FUNCTION_LOGICAL_ID = "ImageFunction" @@ -145,7 +146,7 @@ def test_load_success(self): ("template_cfn_local_prebuilt_image.yaml", "Code.ImageUri"), ], ) -class TestSkipBuildingFunctionsWithLocalImageUri(BuildIntegBase): +class TestSkipBuildingFunctionsWithLocalImageUriContainer(BuildIntegBase): EXPECTED_FILES_PROJECT_MANIFEST: Set[str] = set() FUNCTION_LOGICAL_ID_IMAGE = "ImageFunction" @@ -223,7 +224,7 @@ def test_with_default_requirements(self, runtime): ), ], ) -class TestSkipBuildingFlaggedFunctions(BuildIntegPythonBase): +class TestSkipBuildingFlaggedFunctionsContainer(BuildIntegPythonBase): template = "template_cfn_function_flagged_to_skip_build.yaml" SKIPPED_FUNCTION_LOGICAL_ID = "SkippedFunction" src_code_path = "PreBuiltPython" @@ -280,7 +281,7 @@ def _validate_skipped_built_function( @pytest.mark.ruby class TestBuildCommand_RubyFunctions(BuildIntegRubyBase): - @parameterized.expand([(False,), ("use_container",)]) + @parameterized.expand([(False,), ("use_container",)], name_func=show_container_in_test_name) def test_building_ruby_3_2(self, use_container): if use_container and SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: self.skipTest(SKIP_DOCKER_MESSAGE) @@ -290,14 +291,14 @@ def test_building_ruby_3_2(self, use_container): @parameterized.expand([("ruby3.3",), ("ruby3.4",)]) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) @pytest.mark.al2023 - def test_building_ruby_al2023(self, runtime): + def test_building_ruby_al2023_in_container(self, runtime): self._test_with_default_gemfile(runtime, "use_container", "Ruby", self.test_data_path) class TestBuildCommand_RubyFunctions_With_Architecture(BuildIntegRubyBase): template = "template_with_architecture.yaml" - @parameterized.expand([(False,), ("use_container",)]) + @parameterized.expand([(False,), ("use_container",)], name_func=show_container_in_test_name) def test_building_ruby_3_2(self, use_container): if use_container and SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: self.skipTest(SKIP_DOCKER_MESSAGE) @@ -306,10 +307,11 @@ def test_building_ruby_3_2(self, use_container): @parameterized.expand( [ ("ruby3.3", "Ruby33", False), - ("ruby3.3", "Ruby33", True), + ("ruby3.3", "Ruby33", "use_container"), # ("ruby3.4", "Ruby34", False), # TODO: Try to make this work in AppVeyor (windows-al2023) - ("ruby3.4", "Ruby34", True), - ] + ("ruby3.4", "Ruby34", "use_container"), + ], + name_func=show_container_in_test_name, ) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) @pytest.mark.al2023 @@ -357,7 +359,9 @@ def _prepare_application_environment(self, runtime): class TestBuildCommand_Go_Modules(BuildIntegGoBase): - @parameterized.expand([("go1.x", "Go", None, False), ("go1.x", "Go", "debug", True)]) + @parameterized.expand( + [("go1.x", "Go", None, False), ("go1.x", "Go", "debug", "use_container")], name_func=show_container_in_test_name + ) def test_building_go(self, runtime, code_uri, mode, use_container): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -413,9 +417,10 @@ def test_function_not_found(self): ("python3.11", "use_container", "FunctionOne"), ("python3.11", False, "FunctionTwo"), ("python3.11", "use_container", "FunctionTwo"), - ] + ], + name_func=show_container_in_test_name, ) - def test_build_single_function(self, runtime, use_container, function_identifier): + def test_build_single_function_invoke_in_container(self, runtime, use_container, function_identifier): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -518,7 +523,8 @@ class TestBuildCommand_LayerBuilds(BuildIntegBase): ("python3.12", "use_container", "LayerOne", "ContentUri"), ("python3.12", False, "LambdaLayerOne", "Content"), ("python3.12", "use_container", "LambdaLayerOne", "Content"), - ] + ], + name_func=show_container_in_test_name, ) def test_build_single_layer(self, runtime, use_container, layer_identifier, content_property): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -542,7 +548,8 @@ def test_build_single_layer(self, runtime, use_container, layer_identifier, cont ) @parameterized.expand( - [("makefile", False, "LayerWithMakefile"), ("makefile", "use_container", "LayerWithMakefile")] + [("makefile", False, "LayerWithMakefile"), ("makefile", "use_container", "LayerWithMakefile")], + name_func=show_container_in_test_name, ) def test_build_layer_with_makefile(self, build_method, use_container, layer_identifier): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -566,7 +573,7 @@ def test_build_layer_with_makefile(self, build_method, use_container, layer_iden ) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) - def test_build_layer_with_makefile_no_compatible_runtimes(self): + def test_build_layer_with_makefile_no_compatible_runtimes_in_container(self): build_method = "makefile" use_container = True layer_identifier = "LayerWithMakefileNoCompatibleRuntimes" @@ -589,7 +596,8 @@ def test_build_layer_with_makefile_no_compatible_runtimes(self): ) @parameterized.expand( - [("makefile", False), ("makefile", "use_container"), ("python3.9", False), ("python3.9", "use_container")] + [("makefile", False), ("makefile", "use_container"), ("python3.9", False), ("python3.9", "use_container")], + name_func=show_container_in_test_name, ) def test_build_layer_with_architecture_not_compatible(self, build_method, use_container): # The BuildArchitecture is not one of the listed CompatibleArchitectures @@ -617,7 +625,9 @@ def test_build_layer_with_architecture_not_compatible(self, build_method, use_co # Build should still succeed self.assertEqual(command_result.process.returncode, 0) - @parameterized.expand([("python3.11", False), ("python3.11", "use_container")]) + @parameterized.expand( + [("python3.11", False), ("python3.11", "use_container")], name_func=show_container_in_test_name + ) def test_build_arch_no_compatible_arch(self, runtime, use_container): # BuildArchitecture is present, but CompatibleArchitectures section is missing if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -643,7 +653,9 @@ def test_build_arch_no_compatible_arch(self, runtime, use_container): # Build should still succeed self.assertEqual(command_result.process.returncode, 0) - @parameterized.expand([("python3.11", False), ("python3.11", "use_container")]) + @parameterized.expand( + [("python3.11", False), ("python3.11", "use_container")], name_func=show_container_in_test_name + ) def test_compatible_arch_no_build_arch(self, runtime, use_container): # CompatibleArchitectures is present, but BuildArchitecture section is missing if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -694,7 +706,10 @@ def test_build_layer_with_makefile_with_fake_build_architecture(self): # Build should still succeed self.assertEqual(command_result.process.returncode, 0) - @parameterized.expand([("python3.12", False, "LayerTwo"), ("python3.12", "use_container", "LayerTwo")]) + @parameterized.expand( + [("python3.12", False, "LayerTwo"), ("python3.12", "use_container", "LayerTwo")], + name_func=show_container_in_test_name, + ) def test_build_fails_with_missing_metadata(self, runtime, use_container, layer_identifier): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -708,7 +723,7 @@ def test_build_fails_with_missing_metadata(self, runtime, use_container, layer_i self.assertEqual(command_result.process.returncode, 1) self.assertFalse(self.default_build_dir.joinpath(layer_identifier).exists()) - @parameterized.expand([False, "use_container"]) + @parameterized.expand([False, "use_container"], name_func=show_container_in_test_name) def test_function_build_succeeds_with_referenced_layer(self, use_container): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -722,8 +737,10 @@ def test_function_build_succeeds_with_referenced_layer(self, use_container): command_result = run_command(cmdlist, cwd=self.working_dir) self.assertEqual(command_result.process.returncode, 0) - @parameterized.expand([("python3.12", False), ("python3.12", "use_container")]) - def test_build_function_and_layer(self, runtime, use_container): + @parameterized.expand( + [("python3.12", False), ("python3.12", "use_container")], name_func=show_container_in_test_name + ) + def test_build_function_and_layer_invoke_in_conatiner(self, runtime, use_container): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -757,8 +774,10 @@ def test_build_function_and_layer(self, runtime, use_container): self.verify_docker_container_cleanedup(runtime) self.verify_pulled_image(runtime) - @parameterized.expand([("python3.12", False), ("python3.12", "use_container")]) - def test_build_function_with_dependent_layer(self, runtime, use_container): + @parameterized.expand( + [("python3.12", False), ("python3.12", "use_container")], name_func=show_container_in_test_name + ) + def test_build_function_with_dependent_layer_invoke_in_container(self, runtime, use_container): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -828,8 +847,13 @@ class TestBuildWithBuildMethod(BuildIntegBase): FUNCTION_LOGICAL_ID = "Function" - @parameterized.expand([(False, None, "makefile"), ("use_container", "Makefile-container", "makefile")]) - def test_with_makefile_builder_specified_python_runtime(self, use_container, manifest, build_method): + @parameterized.expand( + [(False, None, "makefile"), ("use_container", "Makefile-container", "makefile")], + name_func=show_container_in_test_name, + ) + def test_with_makefile_builder_specified_python_runtime_invoke_in_container( + self, use_container, manifest, build_method + ): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -864,8 +888,8 @@ def test_with_makefile_builder_specified_python_runtime(self, use_container, man self.verify_docker_container_cleanedup(runtime) self.verify_pulled_image(runtime) - @parameterized.expand([(False,), ("use_container")]) - def test_with_native_builder_specified_python_runtime(self, use_container): + @parameterized.expand([(False,), ("use_container")], name_func=show_container_in_test_name) + def test_with_native_builder_specified_python_runtime_invoke_in_container(self, use_container): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -973,16 +997,17 @@ class TestBuildWithDedupBuilds(DedupBuildIntegBase): (False, "Ruby", "app.lambda_handler", "app.second_lambda_handler", "ruby3.4"), # container ( - True, + "use_container", "Java/gradlew/8", "aws.example.Hello::myHandler", "aws.example.SecondFunction::myHandler", "java8.al2", ), - (True, "Node", "main.lambdaHandler", "main.secondLambdaHandler", "nodejs20.x"), - (True, "Python", "main.first_function_handler", "main.second_function_handler", "python3.9"), - (True, "Ruby", "app.lambda_handler", "app.second_lambda_handler", "ruby3.4"), - ] + ("use_container", "Node", "main.lambdaHandler", "main.secondLambdaHandler", "nodejs20.x"), + ("use_container", "Python", "main.first_function_handler", "main.second_function_handler", "python3.9"), + ("use_container", "Ruby", "app.lambda_handler", "app.second_lambda_handler", "ruby3.4"), + ], + name_func=show_container_in_test_name, ) def test_dedup_build(self, use_container, code_uri, function1_handler, function2_handler, runtime): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -1018,7 +1043,7 @@ def test_dedup_build(self, use_container, code_uri, function1_handler, function2 class TestBuildWithDedupImageBuilds(DedupBuildIntegBase): template = "dedup-functions-image-template.yaml" - def test_dedup_build(self): + def test_dedup_build_invoke_in_container(self): """ Build template above and verify that each function call returns as expected """ @@ -1104,16 +1129,17 @@ class TestBuildWithCacheBuilds(CachedBuildIntegBase): (False, "Ruby", "app.lambda_handler", "app.second_lambda_handler", "ruby3.4"), # container ( - True, + "use_container", "Java/gradlew/8", "aws.example.Hello::myHandler", "aws.example.SecondFunction::myHandler", "java8.al2", ), - (True, "Node", "main.lambdaHandler", "main.secondLambdaHandler", "nodejs20.x"), - (True, "Python", "main.first_function_handler", "main.second_function_handler", "python3.9"), - (True, "Ruby", "app.lambda_handler", "app.second_lambda_handler", "ruby3.4"), - ] + ("use_container", "Node", "main.lambdaHandler", "main.secondLambdaHandler", "nodejs20.x"), + ("use_container", "Python", "main.first_function_handler", "main.second_function_handler", "python3.9"), + ("use_container", "Ruby", "app.lambda_handler", "app.second_lambda_handler", "ruby3.4"), + ], + name_func=show_container_in_test_name, ) def test_cache_build(self, use_container, code_uri, function1_handler, function2_handler, runtime): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -1175,7 +1201,7 @@ def test_no_cached_override_build(self): ) @skipIf(SKIP_DOCKER_TESTS, SKIP_DOCKER_MESSAGE) - def test_cached_build_with_env_vars(self): + def test_cached_build_with_env_vars_in_container(self): """ Build 2 times to verify that second time hits the cached build """ @@ -1214,7 +1240,7 @@ class TestRepeatedBuildHitsCache(BuildIntegBase): # Use template containing both functions and layers template = "layers-functions-template.yaml" - @parameterized.expand([(True,), (False,)]) + @parameterized.expand([("use_container",), (False,)], name_func=show_container_in_test_name) def test_repeated_cached_build_hits_cache(self, use_container): """ Build 2 times to verify that second time hits the cached build @@ -1295,16 +1321,17 @@ class TestParallelBuilds(DedupBuildIntegBase): (False, "Ruby", "app.lambda_handler", "app.second_lambda_handler", "ruby3.4"), # container ( - True, + "use_container", "Java/gradlew/8", "aws.example.Hello::myHandler", "aws.example.SecondFunction::myHandler", "java8.al2", ), - (True, "Node", "main.lambdaHandler", "main.secondLambdaHandler", "nodejs20.x"), - (True, "Python", "main.first_function_handler", "main.second_function_handler", "python3.9"), - (True, "Ruby", "app.lambda_handler", "app.second_lambda_handler", "ruby3.4"), - ] + ("use_container", "Node", "main.lambdaHandler", "main.secondLambdaHandler", "nodejs20.x"), + ("use_container", "Python", "main.first_function_handler", "main.second_function_handler", "python3.9"), + ("use_container", "Ruby", "app.lambda_handler", "app.second_lambda_handler", "ruby3.4"), + ], + name_func=show_container_in_test_name, ) def test_dedup_build(self, use_container, code_uri, function1_handler, function2_handler, runtime): """ @@ -1342,7 +1369,7 @@ class TestParallelBuildsJavaWithLayers(DedupBuildIntegBase): template = "template-java-maven-with-layers.yaml" beta_features = False # parameterized - def test_dedup_build(self): + def test_dedup_build_invoke_in_container(self): """ Build template above and verify that each function call returns as expected """ @@ -1374,7 +1401,8 @@ class TestBuildWithInlineCode(BuildIntegBase): [ (False,), ("use_container",), - ] + ], + name_func=show_container_in_test_name, ) def test_inline_not_built(self, use_container): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -1421,7 +1449,8 @@ class TestBuildWithJsonContainerEnvVars(BuildIntegBase): [ ("use_container", "env_vars_function.json"), ("use_container", "env_vars_parameters.json"), - ] + ], + name_func=show_container_in_test_name, ) def test_json_env_vars_passed(self, use_container, env_vars_file): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -1471,7 +1500,8 @@ class TestBuildWithInlineContainerEnvVars(BuildIntegBase): [ ("use_container", "TEST_ENV_VAR=MyVar"), ("use_container", "CheckEnvVarsFunction.TEST_ENV_VAR=MyVar"), - ] + ], + name_func=show_container_in_test_name, ) def test_inline_env_vars_passed(self, use_container, inline_env_var): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -1525,9 +1555,10 @@ class TestBuildWithNestedStacks(NestedBuildIntegBase): True, True, ), - ] + ], + name_func=show_container_in_test_name, ) - def test_nested_build(self, use_container, cached, parallel): + def test_nested_build_invoke_in_container(self, use_container, cached, parallel): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -1601,7 +1632,7 @@ class TestBuildWithNestedStacks3Level(NestedBuildIntegBase): template = os.path.join("deep-nested", "template.yaml") - def test_nested_build(self): + def test_nested_build_in_container(self): if SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: self.skipTest(SKIP_DOCKER_MESSAGE) @@ -1663,7 +1694,7 @@ class TestBuildWithNestedStacks3LevelWithSymlink(NestedBuildIntegBase): template = os.path.join("deep-nested", "template-with-symlink.yaml") - def test_nested_build(self): + def test_nested_build_in_container(self): if SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: self.skipTest(SKIP_DOCKER_MESSAGE) @@ -1743,9 +1774,10 @@ class TestBuildWithNestedStacksImage(NestedBuildIntegBase): True, True, ), - ] + ], + name_func=show_container_in_test_name, ) - def test_nested_build(self, use_container, cached, parallel): + def test_nested_build_invoke_in_container(self, use_container, cached, parallel): """ Build template above and verify that each function call returns as expected """ @@ -1803,7 +1835,8 @@ class TestBuildWithCustomBuildImage(BuildIntegBase): [ ("use_container", None), ("use_container", "public.ecr.aws/sam/build-python3.11:latest-x86_64"), - ] + ], + name_func=show_container_in_test_name, ) def test_custom_build_image_succeeds(self, use_container, build_image): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): @@ -1860,7 +1893,7 @@ def _verify_build_succeeds(self, build_dir): ], ) class TestBuildPassingLayerAcrossStacks(IntrinsicIntegBase): - def test_nested_build(self): + def test_nested_build_in_container(self): if SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: self.skipTest(SKIP_DOCKER_MESSAGE) @@ -1898,7 +1931,7 @@ class TestBuildWithS3FunctionsOrLayers(NestedBuildIntegBase): "requirements.txt", } - def test_functions_layers_with_s3_codeuri(self): + def test_functions_layers_with_s3_codeuri_in_container(self): if SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: self.skipTest(SKIP_DOCKER_MESSAGE) @@ -1927,7 +1960,7 @@ def test_functions_layers_with_s3_codeuri(self): class TestBuildWithZipFunctionsOrLayers(NestedBuildIntegBase): template = "template-with-zip-code.yaml" - def test_functions_layers_with_s3_codeuri(self): + def test_functions_layers_with_s3_codeuri_in_container(self): if SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: self.skipTest(SKIP_DOCKER_MESSAGE) @@ -1974,7 +2007,8 @@ def tearDownClass(cls): (False, "us-east-2"), (False, "eu-west-1"), (False, None), - ] + ], + name_func=show_container_in_test_name, ) def test_sar_application_with_location_resolved_from_map(self, use_container, region): if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): diff --git a/tests/integration/buildcmd/test_build_cmd_arm64.py b/tests/integration/buildcmd/test_build_cmd_arm64.py index b9862404eed..cb0ff24f452 100644 --- a/tests/integration/buildcmd/test_build_cmd_arm64.py +++ b/tests/integration/buildcmd/test_build_cmd_arm64.py @@ -15,6 +15,7 @@ BuildIntegRubyBase, BuildIntegRustBase, rust_parameterized_class, + show_container_in_test_name, ) from tests.testing_utils import ( SKIP_DOCKER_TESTS, @@ -26,6 +27,7 @@ ) +# this test will use docker to invoke in the end, we need to mark all these test as in_container @pytest.mark.python class TestBuildCommand_PythonFunctions_With_Specified_Architecture_arm64(BuildIntegPythonBase): template = "template_with_architecture.yaml" @@ -47,14 +49,15 @@ class TestBuildCommand_PythonFunctions_With_Specified_Architecture_arm64(BuildIn ("python3.11", "Python", "use_container"), ] ) - def test_with_default_requirements(self, runtime, codeuri, use_container): + def test_with_default_requirements_invoke_in_container(self, runtime, codeuri, use_container): self._test_with_default_requirements(runtime, codeuri, use_container, self.test_data_path, architecture=ARM64) @parameterized.expand( [ ("python3.12", "Python", "use_container"), ("python3.13", "Python", "use_container"), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_with_default_requirements_al2023(self, runtime, codeuri, use_container): @@ -76,7 +79,8 @@ class TestBuildCommand_EsbuildFunctions_arm64(BuildIntegEsbuildBase): "app.lambdaHandler", "use_container", ), - ] + ], + name_func=show_container_in_test_name, ) def test_building_default_package_json(self, runtime, code_uri, expected_files, handler, use_container): self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, ARM64) @@ -117,7 +121,8 @@ class TestBuildCommand_EsbuildFunctions_With_External_Manifest_arm64(BuildIntegE "app.lambdaHandler", False, ), - ] + ], + name_func=show_container_in_test_name, ) def test_building_default_package_json(self, runtime, code_uri, expected_files, handler, use_container): self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, ARM64) @@ -131,7 +136,8 @@ class TestBuildCommand_NodeFunctions_With_Specified_Architecture_arm64(BuildInte [ ("nodejs20.x", False), ("nodejs22.x", False), - ] + ], + name_func=show_container_in_test_name, ) def test_building_default_package_json(self, runtime, use_container): self._test_with_default_package_json(runtime, use_container, self.test_data_path, ARM64) @@ -140,7 +146,8 @@ def test_building_default_package_json(self, runtime, use_container): [ ("nodejs20.x", "use_container"), ("nodejs22.x", "use_container"), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_building_default_package_json_al2023(self, runtime, use_container): @@ -515,7 +522,8 @@ def test_building_Makefile(self, runtime, use_container, manifest): "use_container", "Makefile-container", ), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_building_Makefile_al2023(self, runtime, use_container, manifest): @@ -535,7 +543,8 @@ class TestBuildCommand_Rust_arm64(BuildIntegRustBase): ("provided.al2", "debug", False), ("provided.al2023", None, False), ("provided.al2023", "debug", False), - ] + ], + name_func=show_container_in_test_name, ) def test_build(self, runtime, build_mode, use_container): self._test_with_rust_cargo_lambda( diff --git a/tests/integration/buildcmd/test_build_cmd_dotnet.py b/tests/integration/buildcmd/test_build_cmd_dotnet.py index 0316f8e4ed6..4179b098527 100644 --- a/tests/integration/buildcmd/test_build_cmd_dotnet.py +++ b/tests/integration/buildcmd/test_build_cmd_dotnet.py @@ -115,7 +115,7 @@ class TestBuildCommand_Dotnet_cli_package_interactive(BuildIntegDotnetBase): ("provided.al2", "Dotnet7", None), ] ) - def test_dotnet_al2(self, runtime, code_uri, mode): + def test_dotnet_al2_in_container(self, runtime, code_uri, mode): overrides = { "Runtime": runtime, "CodeUri": code_uri, @@ -135,7 +135,7 @@ def test_dotnet_al2(self, runtime, code_uri, mode): ("dotnet6", "Dotnet6", "debug"), ] ) - def test_dotnet_6(self, runtime, code_uri, mode): + def test_dotnet_6_in_container(self, runtime, code_uri, mode): overrides = { "Runtime": runtime, "CodeUri": code_uri, @@ -155,7 +155,7 @@ def test_dotnet_6(self, runtime, code_uri, mode): ) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) @pytest.mark.al2023 - def test_dotnet_al2023(self, runtime, code_uri, mode): + def test_dotnet_al2023_in_container(self, runtime, code_uri, mode): overrides = { "Runtime": runtime, "CodeUri": code_uri, @@ -168,7 +168,7 @@ def test_dotnet_al2023(self, runtime, code_uri, mode): self.validate_invoke_command(overrides, runtime) @parameterized.expand([("dotnet6", "Dotnet6"), ("dotnet8", "Dotnet8")]) - def test_must_fail_on_container_mount_without_write_interactive(self, runtime, code_uri): + def test_must_fail_in_container_mount_without_write_interactive(self, runtime, code_uri): use_container = True overrides = { "Runtime": runtime, diff --git a/tests/integration/buildcmd/test_build_cmd_node.py b/tests/integration/buildcmd/test_build_cmd_node.py index f4bc7a9a469..72e4ffb8143 100644 --- a/tests/integration/buildcmd/test_build_cmd_node.py +++ b/tests/integration/buildcmd/test_build_cmd_node.py @@ -13,6 +13,7 @@ from tests.integration.buildcmd.build_integ_base import ( BuildIntegNodeBase, BuildIntegEsbuildBase, + show_container_in_test_name, ) @@ -32,7 +33,8 @@ class TestBuildCommand_NodeFunctions_With_External_Manifest(BuildIntegNodeBase): [ ("nodejs20.x",), ("nodejs22.x",), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_building_default_package_json_al2023(self, runtime): @@ -46,7 +48,8 @@ class TestBuildCommand_EsbuildFunctions(BuildIntegEsbuildBase): [ ("nodejs20.x", "Esbuild/Node", {"main.js", "main.js.map"}, "main.lambdaHandler", False, "x86_64"), ("nodejs20.x", "Esbuild/TypeScript", {"app.js", "app.js.map"}, "app.lambdaHandler", False, "x86_64"), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_building_default_package_json_al2023( @@ -78,7 +81,8 @@ class TestBuildCommand_EsbuildFunctions_With_External_Manifest(BuildIntegEsbuild False, "x86_64", ), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_building_default_package_json( @@ -103,7 +107,8 @@ class TestBuildCommand_EsbuildFunctionProperties(BuildIntegEsbuildBase): [ ("nodejs20.x", "../Esbuild/TypeScript", "app.lambdaHandler", "x86_64"), ("nodejs20.x", "../Esbuild/TypeScript", "nested/function/app.lambdaHandler", "x86_64"), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_environment_generates_sourcemap_al2023(self, runtime, code_uri, handler, architecture): @@ -125,7 +130,8 @@ class TestBuildCommand_NodeFunctions_With_Specified_Architecture(BuildIntegNodeB ("nodejs22.x", False, "x86_64"), ("nodejs20.x", "use_container", "x86_64"), ("nodejs22.x", "use_container", "x86_64"), - ] + ], + name_func=show_container_in_test_name, ) def test_building_default_package_json(self, runtime, use_container, architecture): self._test_with_default_package_json(runtime, use_container, self.test_data_path, architecture) diff --git a/tests/integration/buildcmd/test_build_cmd_provided.py b/tests/integration/buildcmd/test_build_cmd_provided.py index e072b938a6a..dfbe5595972 100644 --- a/tests/integration/buildcmd/test_build_cmd_provided.py +++ b/tests/integration/buildcmd/test_build_cmd_provided.py @@ -13,6 +13,7 @@ ) from tests.integration.buildcmd.build_integ_base import ( BuildIntegProvidedBase, + show_container_in_test_name, ) @@ -39,7 +40,8 @@ class TestBuildCommand_ProvidedFunctions(BuildIntegProvidedBase): ("provided", "use_container", "Makefile-container"), ("provided.al2", False, None), ("provided.al2", "use_container", "Makefile-container"), - ] + ], + name_func=show_container_in_test_name, ) def test_building_Makefile(self, runtime, use_container, manifest): if use_container: @@ -51,7 +53,8 @@ def test_building_Makefile(self, runtime, use_container, manifest): [ ("provided.al2023", False, None), ("provided.al2023", "use_container", "Makefile-container"), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_building_Makefile_al2023(self, runtime, use_container, manifest): @@ -114,7 +117,8 @@ class TestBuildCommand_ProvidedFunctionsWithCustomMetadata(BuildIntegProvidedBas [ ("provided", False, None), ("provided.al2", False, None), - ] + ], + name_func=show_container_in_test_name, ) def test_building_Makefile(self, runtime, use_container, manifest): self._test_with_Makefile(runtime, use_container, manifest) @@ -122,7 +126,8 @@ def test_building_Makefile(self, runtime, use_container, manifest): @parameterized.expand( [ ("provided.al2023", False, None), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_building_Makefile_al2023(self, runtime, use_container, manifest): diff --git a/tests/integration/buildcmd/test_build_cmd_python.py b/tests/integration/buildcmd/test_build_cmd_python.py index f822f206e2e..4956ab15022 100644 --- a/tests/integration/buildcmd/test_build_cmd_python.py +++ b/tests/integration/buildcmd/test_build_cmd_python.py @@ -10,6 +10,7 @@ from tests.integration.buildcmd.build_integ_base import ( BuildIntegBase, BuildIntegPythonBase, + show_container_in_test_name, ) from tests.testing_utils import ( CI_OVERRIDE, @@ -348,7 +349,7 @@ class TestBuildCommand_PythonFunctions_WithDocker(BuildIntegPythonBase): ("python3.11",), ] ) - def test_with_default_requirements(self, runtime): + def test_with_default_requirements_in_container(self, runtime): self._test_with_default_requirements( runtime, self.codeuri, @@ -365,7 +366,7 @@ def test_with_default_requirements(self, runtime): ] ) @pytest.mark.al2023 - def test_with_default_requirements_al2023(self, runtime): + def test_with_default_requirements_al2023_in_container(self, runtime): self._test_with_default_requirements( runtime, self.codeuri, @@ -473,7 +474,8 @@ class TestBuildCommand_PythonFunctions_With_Specified_Architecture(BuildIntegPyt ("python3.9", "Python", "use_container", "x86_64"), ("python3.10", "Python", "use_container", "x86_64"), ("python3.11", "Python", "use_container", "x86_64"), - ] + ], + name_func=show_container_in_test_name, ) def test_with_default_requirements(self, runtime, codeuri, use_container, architecture): self._test_with_default_requirements( @@ -488,7 +490,8 @@ def test_with_default_requirements(self, runtime, codeuri, use_container, archit ("python3.13", "Python", False, "x86_64"), ("python3.13", "PythonPEP600", False, "x86_64"), ("python3.13", "Python", "use_container", "x86_64"), - ] + ], + name_func=show_container_in_test_name, ) @pytest.mark.al2023 def test_with_default_requirements_al2023(self, runtime, codeuri, use_container, architecture): diff --git a/tests/integration/buildcmd/test_build_cmd_rust.py b/tests/integration/buildcmd/test_build_cmd_rust.py index 2c5b2dd7c5b..f50502d8ce2 100644 --- a/tests/integration/buildcmd/test_build_cmd_rust.py +++ b/tests/integration/buildcmd/test_build_cmd_rust.py @@ -15,6 +15,7 @@ from .build_integ_base import ( BuildIntegRustBase, rust_parameterized_class, + show_container_in_test_name, ) LOG = logging.getLogger(__name__) @@ -32,7 +33,8 @@ class TestBuildCommand_Rust(BuildIntegRustBase): ("provided.al2", "x86_64", "debug", False), ("provided.al2023", "x86_64", None, False), ("provided.al2023", "x86_64", "debug", False), - ] + ], + name_func=show_container_in_test_name, ) def test_build(self, runtime, architecture, build_mode, use_container): self._test_with_rust_cargo_lambda( diff --git a/tests/integration/remote/test_event/remote_test_event_integ_base.py b/tests/integration/remote/test_event/remote_test_event_integ_base.py index 4d4ad3bb362..22b1aa73d39 100644 --- a/tests/integration/remote/test_event/remote_test_event_integ_base.py +++ b/tests/integration/remote/test_event/remote_test_event_integ_base.py @@ -35,7 +35,10 @@ def tearDownClass(cls): cls.delete_all_test_events() # Delete the deployed stack cls.cfn_client.delete_stack(StackName=cls.stack_name) - cls.schemas_client.delete_registry(RegistryName=LAMBDA_TEST_EVENT_REGISTRY) + try: + cls.schemas_client.delete_registry(RegistryName=LAMBDA_TEST_EVENT_REGISTRY) + except Exception as e: + LOG.info("test event schema cleanup failed %s", e) @classmethod def create_resources_and_boto_clients(cls): diff --git a/tests/integration/testdata/sync/infra/after/Ruby/function/.ruby-version b/tests/integration/testdata/sync/infra/after/Ruby/function/.ruby-version index 406ebcbd95f..2aa51319921 100644 --- a/tests/integration/testdata/sync/infra/after/Ruby/function/.ruby-version +++ b/tests/integration/testdata/sync/infra/after/Ruby/function/.ruby-version @@ -1 +1 @@ -3.2.7 +3.4.7 diff --git a/tests/integration/testdata/sync/infra/after/Ruby/function/Gemfile b/tests/integration/testdata/sync/infra/after/Ruby/function/Gemfile index ffdfcccc1ed..40ebd6d26ab 100644 --- a/tests/integration/testdata/sync/infra/after/Ruby/function/Gemfile +++ b/tests/integration/testdata/sync/infra/after/Ruby/function/Gemfile @@ -2,4 +2,4 @@ source "https://rubygems.org" gem "ruby-statistics" -ruby '~> 3.2' +ruby '~> 3.4' diff --git a/tests/integration/testdata/sync/infra/after/Ruby/layer/.ruby-version b/tests/integration/testdata/sync/infra/after/Ruby/layer/.ruby-version index 406ebcbd95f..2aa51319921 100644 --- a/tests/integration/testdata/sync/infra/after/Ruby/layer/.ruby-version +++ b/tests/integration/testdata/sync/infra/after/Ruby/layer/.ruby-version @@ -1 +1 @@ -3.2.7 +3.4.7 diff --git a/tests/integration/testdata/sync/infra/after/Ruby/layer/Gemfile b/tests/integration/testdata/sync/infra/after/Ruby/layer/Gemfile index 8d2ee9edd67..5549805b974 100644 --- a/tests/integration/testdata/sync/infra/after/Ruby/layer/Gemfile +++ b/tests/integration/testdata/sync/infra/after/Ruby/layer/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -ruby '~> 3.2' +ruby '~> 3.4' diff --git a/tests/integration/testdata/sync/infra/template-ruby-after.yaml b/tests/integration/testdata/sync/infra/template-ruby-after.yaml index 2a4a8b11ce1..84ef4d7e871 100644 --- a/tests/integration/testdata/sync/infra/template-ruby-after.yaml +++ b/tests/integration/testdata/sync/infra/template-ruby-after.yaml @@ -17,7 +17,7 @@ Resources: AutoPublishAlias: Hello1Alias CodeUri: after/Ruby/function/ Handler: app.lambda_handler - Runtime: ruby3.2 + Runtime: ruby3.4 Architectures: - x86_64 Layers: @@ -30,6 +30,6 @@ Resources: Description: Hello World Ruby Layer ContentUri: after/Ruby/layer/ CompatibleRuntimes: - - ruby3.2 + - ruby3.4 Metadata: - BuildMethod: ruby3.2 + BuildMethod: ruby3.4 diff --git a/tests/integration/testdata/sync/infra/template-ruby-before.yaml b/tests/integration/testdata/sync/infra/template-ruby-before.yaml index 0449e40e1e8..df1fab060c3 100644 --- a/tests/integration/testdata/sync/infra/template-ruby-before.yaml +++ b/tests/integration/testdata/sync/infra/template-ruby-before.yaml @@ -17,7 +17,7 @@ Resources: AutoPublishAlias: Hello1Alias CodeUri: before/Ruby/function/ Handler: app.lambda_handler - Runtime: ruby3.2 + Runtime: ruby3.4 Architectures: - x86_64 Layers: @@ -30,6 +30,6 @@ Resources: Description: Hello World Ruby Layer ContentUri: before/Ruby/layer/ CompatibleRuntimes: - - ruby3.2 + - ruby3.4 Metadata: - BuildMethod: ruby3.2 + BuildMethod: ruby3.4 diff --git a/tests/testing_utils.py b/tests/testing_utils.py index f93a12e78b6..13a0331fe88 100644 --- a/tests/testing_utils.py +++ b/tests/testing_utils.py @@ -29,7 +29,11 @@ RUNNING_TEST_FOR_MASTER_ON_CI = ( os.environ.get("APPVEYOR_REPO_BRANCH", os.environ.get("GITHUB_REF_NAME", "master")) != "master" ) -CI_OVERRIDE = os.environ.get("APPVEYOR_CI_OVERRIDE", False) or os.environ.get("CI_OVERRIDE", False) +CI_OVERRIDE = ( + os.environ.get("APPVEYOR_CI_OVERRIDE", False) + or os.environ.get("CI_OVERRIDE", False) + or os.environ.get("GITHUB_ACTIONS_INTEG", False) +) RUN_BY_CANARY = os.environ.get("BY_CANARY", False) USING_FINCH_RUNTIME = os.environ.get("CONTAINER_RUNTIME") == "finch"