From 9c4c75b4afbe51811b71fe44ab40dd1a7e769588 Mon Sep 17 00:00:00 2001 From: jysheng123 <141280295+jysheng123@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:01:15 -0700 Subject: [PATCH] chore(tests): Refactor tests to support easily invoking AL2023 based runtimes (#7251) * Initial al2023 test marking and AppVeyor yml file * Remove the matrix * Remove cache * Collect tests to test marker * Update test to consider use-container arg * Clean up markers * Refactored portions of build integ tests into two * Removed unused consts * Cache on job failures * Removed runtime method check from test base class * Fixed broken tests * Install ruby3.3 * Removed build cache * ls docker directory * Add runtime check back to node tests and cleaned appveyor configs * Fix condition check * Test ruby changes * Update path for ruby3.3 * Removed al2023 mark for terraform tests * Removed unused import * make pr * Check for docker test before checking runtime * Remove check from missing docker tests * chore: separate node,python builds into al2023 * chore: separate provided, java build tests into al2023 * remove runtime_supported_by_docker * update node version * update pytest name * update formatting * update test-files run * update tests * reformat * fix failing tests * separate test * update node test --------- Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- appveyor-windows-al2023.yml | 178 +++ appveyor-windows-binary.yml | 13 +- appveyor-windows.yml | 9 +- pytest.ini | 1 + .../integration/buildcmd/build_integ_base.py | 124 +- tests/integration/buildcmd/test_build_cmd.py | 1252 +---------------- .../buildcmd/test_build_cmd_arm64.py | 81 +- .../buildcmd/test_build_cmd_dotnet.py | 168 +++ .../buildcmd/test_build_cmd_java.py | 299 ++++ .../buildcmd/test_build_cmd_node.py | 176 +++ .../buildcmd/test_build_cmd_provided.py | 129 ++ .../buildcmd/test_build_cmd_python.py | 520 +++++++ tests/testing_utils.py | 29 +- 14 files changed, 1669 insertions(+), 1312 deletions(-) create mode 100644 appveyor-windows-al2023.yml create mode 100644 tests/integration/buildcmd/test_build_cmd_dotnet.py create mode 100644 tests/integration/buildcmd/test_build_cmd_java.py create mode 100644 tests/integration/buildcmd/test_build_cmd_node.py create mode 100644 tests/integration/buildcmd/test_build_cmd_provided.py create mode 100644 tests/integration/buildcmd/test_build_cmd_python.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 307cd24326e..1aea07e6522 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -279,4 +279,4 @@ jobs: - name: Init samdev run: make init - name: Run tests without Docker - run: pytest -vv tests/integration/buildcmd/test_build_cmd.py -k TestBuildCommand_PythonFunctions_WithoutDocker + run: pytest -vv tests/integration/buildcmd/test_build_cmd_python.py -k TestBuildCommand_PythonFunctions_WithoutDocker diff --git a/appveyor-windows-al2023.yml b/appveyor-windows-al2023.yml new file mode 100644 index 00000000000..1f3dbbb2c48 --- /dev/null +++ b/appveyor-windows-al2023.yml @@ -0,0 +1,178 @@ +version: 1.0.{build} +image: ws2022-azure +build: off + +clone_folder: C:\source + +environment: + AWS_DEFAULT_REGION: us-east-1 + SAM_CLI_DEV: 1 + CARGO_LAMBDA_VERSION: "v0.17.1" + + # Python uses $TMPDIR envvar to find root of tempdir + TMPDIR: "%TEMP%" + TMP: "%TEMP%" + + # MSI Installers only use Py3.8. It is sufficient to test with this version here. + PYTHON_HOME: "C:\\Python38-x64" + PYTHON_SCRIPTS: "C:\\Python38-x64\\Scripts" + PYTHON_EXE: "C:\\Python38-x64\\python.exe" + PYTHON_ARCH: "64" + HOME: 'C:\Users\appveyor' + HOMEDRIVE: "C:" + HOMEPATH: 'C:\Users\appveyor' + NOSE_PARAMETERIZED_NO_WARN: 1 + AWS_S3: "AWS_S3_TESTING" + AWS_ECR: "AWS_ECR_TESTING" + APPVEYOR_CONSOLE_DISABLE_PTY: true + APPVEYOR_BUILD_WORKER_IMAGE: "ws2022-azure" + +init: + # Uncomment this for RDP + # - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) + - ps: gcim Win32_Processor | % { "$($_.NumberOfLogicalProcessors) logical CPUs" } + - ps: gcim Win32_OperatingSystem | % { "$([int]($_.TotalVisibleMemorySize/1mb)) Gb" } + - git config --global core.autocrlf false + - ps: New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force + - ps: git config --system core.longpaths true + +install: + # upgrade chocolately + - choco upgrade chocolatey + # setup make + - "choco install make" + # setup Java, Maven and Gradle + - choco install correttojdk --version=21.0.0 + - 'set JAVA_HOME=C:\Program Files\Amazon Corretto\jdk21.0.0_35' + - 'set PATH=%JAVA_HOME%\bin;%PATH%' + - java --version + - javac --version + - choco upgrade gradle --version=8.4.0 + - "gradle -v" + - "mvn --version" + + # Make sure the temp directory exists for Python to use. + - ps: "mkdir -Force C:\\tmp" + - 'set PATH=%PYTHON_HOME%;C:\Ruby33-x64\bin;C:\Ruby32-x64\bin;%PATH%;C:\Python39-x64;C:\Python310-x64;C:\Python311-x64;C:\Python312-x64' + - "echo %PYTHON_HOME%" + - "echo %PATH%" + - "python --version" + - "ruby --version" + - ps: "Restart-Service docker" + # Switch to Docker Linux containers + - ps: '& $Env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchLinuxEngine' + - "docker info" + - "docker version" + + # Upgrade setuptools, wheel and virtualenv + - "python -m pip install --upgrade setuptools wheel virtualenv" + + # Install Node 20 + - ps: "Install-Product node 20" + + # Install AWS CLI Globally via pip3 + - "pip install awscli" + + # Check for git executable + - "git --version" + + # Get testing env vars + - ps: " + If (Test-Path env:BY_CANARY){ + python -m virtualenv venv_env_vars; + ./venv_env_vars/Scripts/pip install boto3; + $test_env_var = ./venv_env_vars/Scripts/python tests/get_testing_resources.py; + $test_env_var_json = ConvertFrom-Json $test_env_var; + + $env:CI_ACCESS_ROLE_AWS_ACCESS_KEY_ID = $env:AWS_ACCESS_KEY_ID; + $env:CI_ACCESS_ROLE_AWS_SECRET_ACCESS_KEY = $env:AWS_SECRET_ACCESS_KEY; + $env:CI_ACCESS_ROLE_AWS_SESSION_TOKEN = $env:AWS_SESSION_TOKEN; + + $env:AWS_ACCESS_KEY_ID = $test_env_var_json.accessKeyID; + $env:AWS_SECRET_ACCESS_KEY = $test_env_var_json.secretAccessKey; + $env:AWS_SESSION_TOKEN = $test_env_var_json.sessionToken; + $env:TASK_TOKEN = $test_env_var_json.taskToken; + $env:AWS_S3_TESTING = $test_env_var_json.TestBucketName; + $env:AWS_ECR_TESTING = $test_env_var_json.TestECRURI; + $env:AWS_KMS_KEY = $test_env_var_json.TestKMSKeyArn; + $env:AWS_SIGNING_PROFILE_NAME = $test_env_var_json.TestSigningProfileName; + $env:AWS_SIGNING_PROFILE_VERSION_ARN = $test_env_var_json.TestSigningProfileARN; + }" + + + # Create new virtual environment with chosen python version and activate it + - "python -m virtualenv venv" + - "venv\\Scripts\\activate" + - "python --version" + + # Actually install SAM CLI's dependencies + - 'pip install -e ".[dev]"' + + # Install aws cli + - "pip install awscli" + + # Echo final Path + - "echo %PATH%" + + # use amazon-ecr-credential-helper + - choco install amazon-ecr-credential-helper + - ps: "ls $env:HOME/.docker" + - ps: " + $docker_config = Get-Content $env:HOME/.docker/config.json -raw | ConvertFrom-Json; + $docker_config.credsStore = 'ecr-login'; + $docker_config | ConvertTo-Json | set-content $env:HOME/.docker/config.json; + " + - ps: "get-content $env:HOME/.docker/config.json" + + # claim some disk space before starting the tests + - "docker system prune -a -f" + # activate virtual environment + - "venv\\Scripts\\activate" + + + +# Final clean up no matter success or failure +on_finish: + # Upload test reports as artifacts + - ps: Get-ChildItem .\TEST_REPORT-*.json | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } + - ps: ' + If (Test-Path env:BY_CANARY){ + $env:AWS_ACCESS_KEY_ID = $env:TEST_REPORT_S3_BUCKET_ACCESS_KEY_ID; + $env:AWS_SECRET_ACCESS_KEY = $env:TEST_REPORT_S3_BUCKET_SECRET_ACCESS_KEY; + $env:AWS_SESSION_TOKEN = $env:TEST_REPORT_S3_BUCKET_SESSION_TOKEN; + aws s3 cp ".\" "s3://$env:TEST_REPORT_S3_BUCKET_NAME/appveyor/$env:APPVEYOR_PROJECT_SLUG/$env:APPVEYOR_BUILD_ID/$env:APPVEYOR_JOB_ID/" --recursive --exclude "*" --include "TEST_REPORT-*.json" --region us-west-2 + }' + + # notify success + - ps: " + If (Test-Path env:BY_CANARY){ + $env:AWS_ACCESS_KEY_ID = $env:CI_ACCESS_ROLE_AWS_ACCESS_KEY_ID; + $env:AWS_SECRET_ACCESS_KEY = $env:CI_ACCESS_ROLE_AWS_SECRET_ACCESS_KEY; + $env:AWS_SESSION_TOKEN = $env:CI_ACCESS_ROLE_AWS_SESSION_TOKEN; + aws stepfunctions send-task-success --task-token \"$env:TASK_TOKEN\" --task-output \"{}\" --region us-west-2; + }" + +build_script: + # install Rust in build_script to not override the default "install" actions + - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain stable + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - set RUST_BACKTRACE=1 + - 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 + - ps: "choco install zig" + - ps: Invoke-WebRequest -Uri https://github.com/cargo-lambda/cargo-lambda/releases/download/$env:CARGO_LAMBDA_VERSION/cargo-lambda-$env:CARGO_LAMBDA_VERSION.windows-x64.zip -OutFile C:\Users\appveyor\cargo-lambda.zip + - ps: Expand-Archive -DestinationPath C:\Users\appveyor\.cargo\bin C:\Users\appveyor\cargo-lambda.zip + - rustc -V + - cargo -V + - cargo lambda -V + +test_script: + - ps: "pytest -vv -n 2 --reruns 3 -m 'al2023' tests/integration/buildcmd --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" + +# Uncomment for RDP +# on_finish: +# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) + diff --git a/appveyor-windows-binary.yml b/appveyor-windows-binary.yml index 3d2a65b2b34..86432d42ccc 100644 --- a/appveyor-windows-binary.yml +++ b/appveyor-windows-binary.yml @@ -49,10 +49,6 @@ init: - ps: New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force - ps: git config --system core.longpaths true -cache: - - C:\ProgramData\chocolatey\bin -> appveyor.yml - - C:\ProgramData\chocolatey\lib -> appveyor.yml - install: # upgrade chocolately - choco upgrade chocolatey @@ -71,7 +67,7 @@ install: # Make sure the temp directory exists for Python to use. - ps: "mkdir -Force C:\\tmp" - - 'set PATH=%PYTHON_HOME%;C:\Ruby32-x64\bin;%PATH%;C:\Python37-x64;C:\Python39-x64;C:\Python310-x64;C:\Python38-x64;C:\Python312-x64' + - 'set PATH=%PYTHON_HOME%;C:\Ruby32-x64\bin;%PATH%;C:\Python39-x64;C:\Python310-x64;C:\Python38-x64;C:\Python312-x64' - "echo %PYTHON_HOME%" - "echo %PATH%" - "python --version" @@ -84,7 +80,6 @@ install: # Upgrade setuptools, wheel and virtualenv - "python -m pip install --upgrade setuptools wheel virtualenv" # Install pip for the python versions which is used by the tests - - "C:\\Python37-x64\\python.exe -m pip install --upgrade pip" - "C:\\Python39-x64\\python.exe -m pip install --upgrade pip" - "C:\\Python310-x64\\python.exe -m pip install --upgrade pip" @@ -185,7 +180,7 @@ for: - configuration: BuildIntegTesting test_script: - - ps: "pytest -vv -n 2 --reruns 3 tests/integration/buildcmd -m 'not java and not python and not 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 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" + - ps: "pytest -vv -n 2 --reruns 3 tests/integration/buildcmd -m 'not java and not python and not provided and not al2023' --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 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" - matrix: only: @@ -209,7 +204,7 @@ for: - cargo lambda -V test_script: - - ps: "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 --json-report --json-report-file=TEST_REPORT-integration-buildcmd-java-python-provided.json" + - ps: "pytest -vv -n 2 --reruns 3 tests/integration/buildcmd -m 'java and not al2023 or python and not al2023 or provided and not al2023' --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 --json-report --json-report-file=TEST_REPORT-integration-buildcmd-java-python-provided.json" #Integ testing build arm64 - matrix: @@ -234,7 +229,7 @@ for: - cargo lambda -V test_script: - - ps: "pytest -vv --reruns 3 tests/integration/buildcmd/test_build_cmd_arm64.py --json-report --json-report-file=TEST_REPORT-integration-buildcmd-arm64.json" + - ps: "pytest -vv --reruns 3 -m 'not al2023' tests/integration/buildcmd/test_build_cmd_arm64.py --json-report --json-report-file=TEST_REPORT-integration-buildcmd-arm64.json" #Integ testing Terraform build - matrix: diff --git a/appveyor-windows.yml b/appveyor-windows.yml index f73b6ef62a8..3100a1d6550 100644 --- a/appveyor-windows.yml +++ b/appveyor-windows.yml @@ -66,7 +66,7 @@ install: # Make sure the temp directory exists for Python to use. - ps: "mkdir -Force C:\\tmp" - - 'set PATH=%PYTHON_HOME%;C:\Ruby32-x64\bin;%PATH%;C:\Python37-x64;C:\Python39-x64;C:\Python310-x64;C:\Python311-x64;C:\Python312-x64' + - 'set PATH=%PYTHON_HOME%;C:\Ruby32-x64\bin;%PATH%;C:\Python39-x64;C:\Python310-x64;C:\Python311-x64;C:\Python312-x64' - "echo %PYTHON_HOME%" - "echo %PATH%" - "python --version" @@ -79,7 +79,6 @@ install: # Upgrade setuptools, wheel and virtualenv - "python -m pip install --upgrade setuptools wheel virtualenv" # Install pip for the python versions which is used by the tests - - "C:\\Python37-x64\\python.exe -m pip install --upgrade pip" - "C:\\Python39-x64\\python.exe -m pip install --upgrade pip" - "C:\\Python310-x64\\python.exe -m pip install --upgrade pip" @@ -172,7 +171,7 @@ for: - configuration: BuildIntegTesting test_script: - - ps: "pytest -vv -n 2 --reruns 3 tests/integration/buildcmd -m 'not java and not python and not 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 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" + - ps: "pytest -vv -n 2 --reruns 3 tests/integration/buildcmd -m 'not java and not python and not provided and not al2023' --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 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" - matrix: only: @@ -196,7 +195,7 @@ for: - cargo lambda -V test_script: - - ps: "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 --json-report --json-report-file=TEST_REPORT-integration-buildcmd-java-python-provided.json" + - ps: "pytest -vv -n 2 --reruns 3 tests/integration/buildcmd -m 'java and not al2023 or python and not al2023 or provided and not al2023' --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 --json-report --json-report-file=TEST_REPORT-integration-buildcmd-java-python-provided.json" #Integ testing build arm64 - matrix: @@ -221,7 +220,7 @@ for: - cargo lambda -V test_script: - - ps: "pytest -vv --reruns 3 tests/integration/buildcmd/test_build_cmd_arm64.py --json-report --json-report-file=TEST_REPORT-integration-buildcmd-arm64.json" + - ps: "pytest -vv --reruns 3 -m 'not al2023' tests/integration/buildcmd/test_build_cmd_arm64.py --json-report --json-report-file=TEST_REPORT-integration-buildcmd-arm64.json" #Integ testing Terraform build - matrix: diff --git a/pytest.ini b/pytest.ini index c48ab402e64..a02d5bd6f59 100644 --- a/pytest.ini +++ b/pytest.ini @@ -24,3 +24,4 @@ markers = python provided dotnet + al2023: Marker to note tests that require a newer Docker version on Windows diff --git a/tests/integration/buildcmd/build_integ_base.py b/tests/integration/buildcmd/build_integ_base.py index e946564ad4a..859c325bb1b 100644 --- a/tests/integration/buildcmd/build_integ_base.py +++ b/tests/integration/buildcmd/build_integ_base.py @@ -17,19 +17,19 @@ import pytest from parameterized import parameterized_class -from samcli.commands.build.utils import MountMode from samcli.lib.utils import osutils -from samcli.lib.utils.architecture import ARM64, X86_64, has_runtime_multi_arch_image +from samcli.lib.utils.architecture import ARM64, X86_64 from samcli.local.docker.lambda_build_container import LambdaBuildContainer from samcli.yamlhelper import yaml_parse from tests.testing_utils import ( IS_WINDOWS, + CommandResult, run_command, SKIP_DOCKER_TESTS, SKIP_DOCKER_MESSAGE, SKIP_DOCKER_BUILD, get_sam_command, - runtime_supported_by_docker, + run_command_with_input, ) LOG = logging.getLogger(__name__) @@ -379,7 +379,7 @@ def _test_with_default_package_json( ) expected = {"body": '{"message":"hello world!"}', "statusCode": 200} - if not SKIP_DOCKER_TESTS and architecture == X86_64 and runtime_supported_by_docker(runtime): + if not SKIP_DOCKER_TESTS and architecture == X86_64: # ARM64 is not supported yet for invoking self._verify_invoke_built_function( self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected @@ -398,7 +398,7 @@ def _test_with_various_properties(self, overrides, runtime): run_command(cmdlist, cwd=self.working_dir) expected = {"body": '{"message":"hello world!"}', "statusCode": 200} - if not SKIP_DOCKER_TESTS and overrides["Architectures"] == X86_64 and runtime_supported_by_docker(runtime): + if not SKIP_DOCKER_TESTS and overrides["Architectures"] == X86_64: # ARM64 is not supported yet for invoking self._verify_invoke_built_function( self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected @@ -472,7 +472,7 @@ def _test_with_default_package_json(self, runtime, use_container, relative_path, ) expected = {"body": '{"message":"hello world!"}', "statusCode": 200} - if not SKIP_DOCKER_TESTS and self.TEST_INVOKE and runtime_supported_by_docker(runtime): + if not SKIP_DOCKER_TESTS and self.TEST_INVOKE: self._verify_invoke_built_function( self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected ) @@ -559,7 +559,7 @@ def _test_with_go(self, runtime, code_uri, mode, relative_path, architecture=Non ) expected = "{'message': 'Hello World'}" - if not SKIP_DOCKER_TESTS and architecture == X86_64 and runtime_supported_by_docker(runtime): + if not SKIP_DOCKER_TESTS and architecture == X86_64: # ARM64 is not supported yet for invoking self._verify_invoke_built_function( self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected @@ -664,7 +664,7 @@ def _test_with_building_java( # If we are testing in the container, invoke the function as well. Otherwise we cannot guarantee docker is on appveyor if use_container: expected = "Hello World" - if not SKIP_DOCKER_TESTS and runtime_supported_by_docker(runtime): + if not SKIP_DOCKER_TESTS: self._verify_invoke_built_function( self.built_template, self.FUNCTION_LOGICAL_ID, @@ -759,7 +759,7 @@ def _test_with_default_requirements( ) expected = {"pi": "3.14"} - if not SKIP_DOCKER_TESTS and runtime_supported_by_docker(runtime): + if not SKIP_DOCKER_TESTS: self._verify_invoke_built_function( self.built_template, self.FUNCTION_LOGICAL_ID, @@ -833,7 +833,7 @@ def _test_with_Makefile( expected = "2.23.0" # Building was done with a makefile, but invoke should be checked with corresponding python image. overrides["Runtime"] = self._get_python_version() - if not SKIP_DOCKER_TESTS and runtime_supported_by_docker(runtime): + if not SKIP_DOCKER_TESTS: self._verify_invoke_built_function( self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected ) @@ -1120,12 +1120,7 @@ def _test_with_rust_cargo_lambda( self.default_build_dir, self.FUNCTION_LOGICAL_ID, self.EXPECTED_FILES_PROJECT_MANIFEST ) - if ( - expected_invoke_result - and not SKIP_DOCKER_TESTS - and architecture == X86_64 - and runtime_supported_by_docker(runtime) - ): + if expected_invoke_result and not SKIP_DOCKER_TESTS and architecture == X86_64: # ARM64 is not supported yet for local invoke self._verify_invoke_built_function( self.built_template, @@ -1154,3 +1149,100 @@ def _verify_built_artifact(self, build_dir, function_logical_id, expected_files) all_artifacts = set(os.listdir(str(resource_artifact_dir))) actual_files = all_artifacts.intersection(expected_files) self.assertEqual(actual_files, expected_files) + + +@pytest.mark.dotnet +class BuildIntegDotnetBase(BuildIntegBase): + FUNCTION_LOGICAL_ID = "Function" + EXPECTED_FILES_PROJECT_MANIFEST = { + "Amazon.Lambda.APIGatewayEvents.dll", + "Amazon.Lambda.Core.dll", + "HelloWorld.runtimeconfig.json", + "Amazon.Lambda.Serialization.Json.dll", + "Newtonsoft.Json.dll", + "HelloWorld.deps.json", + "HelloWorld.dll", + } + + EXPECTED_FILES_PROJECT_MANIFEST_PROVIDED = { + "bootstrap", + } + + def validate_build_command(self, overrides, mode, mount_mode=None, use_container=False, input=None) -> None: + do_use_container = mount_mode != None or use_container + + cmdlist = self.get_command_list( + use_container=do_use_container, parameter_overrides=overrides, mount_with=mount_mode + ) + + if do_use_container: + cmdlist += ["--container-env-var", "DOTNET_CLI_HOME=/tmp/dotnet"] + cmdlist += ["--container-env-var", "XDG_DATA_HOME=/tmp/xdg"] + + LOG.info("Running with SAM_BUILD_MODE={}".format(mode)) + + newenv = os.environ.copy() + if mode: + newenv["SAM_BUILD_MODE"] = mode + + if input: + command_result = run_command_with_input(cmdlist, input.encode(), cwd=self.working_dir, env=newenv) + else: + command_result = run_command(cmdlist, cwd=self.working_dir, env=newenv) + + self.assertEqual(command_result.process.returncode, 0) + + def validate_invoke_command(self, overrides, runtime): + expected = "{'message': 'Hello World'}" + + self._verify_invoke_built_function( + self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected + ) + self.verify_docker_container_cleanedup(runtime) + + def validate_build_artifacts(self, expected_manifest): + self._verify_built_artifact( + self.default_build_dir, + self.FUNCTION_LOGICAL_ID, + expected_manifest, + ) + + self._verify_built_properties() + + def _verify_built_properties(self): + self._verify_resource_property( + str(self.built_template), + "OtherRelativePathResource", + "BodyS3Location", + os.path.relpath( + os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), + str(self.default_build_dir), + ), + ) + + self._verify_resource_property( + str(self.built_template), + "GlueResource", + "Command.ScriptLocation", + os.path.relpath( + os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), + str(self.default_build_dir), + ), + ) + + def _verify_built_artifact(self, build_dir, function_logical_id, expected_files): + self.assertTrue(build_dir.exists(), "Build directory should be created") + + build_dir_files = os.listdir(str(build_dir)) + self.assertIn("template.yaml", build_dir_files) + self.assertIn(function_logical_id, build_dir_files) + + template_path = build_dir.joinpath("template.yaml") + resource_artifact_dir = build_dir.joinpath(function_logical_id) + + # Make sure the template has correct CodeUri for resource + self._verify_resource_property(str(template_path), function_logical_id, "CodeUri", function_logical_id) + + all_artifacts = set(os.listdir(str(resource_artifact_dir))) + actual_files = all_artifacts.intersection(expected_files) + self.assertEqual(actual_files, expected_files) diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py index 4133ccdf172..7b37ef680ad 100644 --- a/tests/integration/buildcmd/test_build_cmd.py +++ b/tests/integration/buildcmd/test_build_cmd.py @@ -13,7 +13,6 @@ import pytest from parameterized import parameterized, parameterized_class -from samcli.commands.build.utils import MountMode from samcli.lib.utils import osutils from samcli.yamlhelper import yaml_parse from tests.testing_utils import ( @@ -26,24 +25,17 @@ SKIP_DOCKER_TESTS, SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE, - run_command_with_input, UpdatableSARTemplate, - runtime_supported_by_docker, - RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG, ) -from .build_integ_base import ( +from tests.integration.buildcmd.build_integ_base import ( BuildIntegBase, DedupBuildIntegBase, CachedBuildIntegBase, BuildIntegRubyBase, NestedBuildIntegBase, IntrinsicIntegBase, - BuildIntegNodeBase, BuildIntegGoBase, - BuildIntegProvidedBase, BuildIntegPythonBase, - BuildIntegJavaBase, - BuildIntegEsbuildBase, ) @@ -126,227 +118,6 @@ def test_load_success(self): (not RUN_BY_CANARY and not CI_OVERRIDE), "Skip build tests on windows when running in CI unless overridden", ) -@pytest.mark.python -class TestBuildCommand_PythonFunctions_Images(BuildIntegBase): - template = "template_image.yaml" - - EXPECTED_FILES_PROJECT_MANIFEST: Set[str] = set() - - FUNCTION_LOGICAL_ID_IMAGE = "ImageFunction" - - @parameterized.expand([("3.8", False), ("3.9", False), ("3.10", False), ("3.11", False), ("3.12", False)]) - def test_with_default_requirements(self, runtime, use_container): - if IS_WINDOWS and not runtime_supported_by_docker(f"python{runtime}"): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - _tag = uuid4().hex - overrides = { - "Runtime": runtime, - "Handler": "main.handler", - "DockerFile": "Dockerfile", - "Tag": _tag, - } - cmdlist = self.get_command_list(use_container=use_container, parameter_overrides=overrides) - - command_result = run_command(cmdlist, cwd=self.working_dir) - self.assertEqual(command_result.process.returncode, 0) - - self._verify_image_build_artifact( - self.built_template, - self.FUNCTION_LOGICAL_ID_IMAGE, - "ImageUri", - f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{_tag}", - ) - - expected = {"pi": "3.14"} - self._verify_invoke_built_function( - self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected - ) - - @parameterized.expand( - [ - ("3.8", False), - ("3.9", False), - ("3.10", False), - ("3.11", False), - ("3.12", False), - ] - ) - def test_with_dockerfile_extension(self, runtime, use_container): - if not runtime_supported_by_docker(f"python{runtime}") and IS_WINDOWS: - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - - _tag = uuid4().hex - overrides = { - "Runtime": runtime, - "Handler": "main.handler", - "DockerFile": "Dockerfile.production", - "Tag": _tag, - } - cmdlist = self.get_command_list(use_container=use_container, parameter_overrides=overrides) - - command_result = run_command(cmdlist, cwd=self.working_dir) - self.assertEqual(command_result.process.returncode, 0) - - self._verify_image_build_artifact( - self.built_template, - self.FUNCTION_LOGICAL_ID_IMAGE, - "ImageUri", - f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{_tag}", - ) - - expected = {"pi": "3.14"} - self._verify_invoke_built_function( - self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected - ) - - def test_intermediate_container_deleted(self): - _tag = uuid4().hex - overrides = { - "Runtime": "3.9", - "Handler": "main.handler", - "DockerFile": "Dockerfile", - "Tag": _tag, - } - cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) - - _num_of_containers_before_build = self.get_number_of_created_containers() - command_result = run_command(cmdlist, cwd=self.working_dir) - self.assertEqual(command_result.process.returncode, 0) - _num_of_containers_after_build = self.get_number_of_created_containers() - - self._verify_image_build_artifact( - self.built_template, - self.FUNCTION_LOGICAL_ID_IMAGE, - "ImageUri", - f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{_tag}", - ) - - expected = {"pi": "3.14"} - self._verify_invoke_built_function( - self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected - ) - - self.assertEqual( - _num_of_containers_before_build, _num_of_containers_after_build, "Intermediate containers are not removed" - ) - - -@skipIf( - # Hits public ECR pull limitation, move it to canary tests - (not RUN_BY_CANARY and not CI_OVERRIDE), - "Skip build tests on windows when running in CI unless overridden", -) -@pytest.mark.python -class TestBuildCommand_PythonFunctions_ImagesWithSharedCode(BuildIntegBase): - template = "template_images_with_shared_code.yaml" - - EXPECTED_FILES_PROJECT_MANIFEST: Set[str] = set() - - FUNCTION_LOGICAL_ID_IMAGE = "ImageFunction" - - @parameterized.expand( - [ - *[ - (runtime, "feature_phi/Dockerfile", {"phi": "1.62"}) - for runtime in ["3.8", "3.9", "3.10", "3.11", "3.12"] - ], - *[(runtime, "feature_pi/Dockerfile", {"pi": "3.14"}) for runtime in ["3.8", "3.9", "3.10", "3.11", "3.12"]], - ] - ) - def test_with_default_requirements(self, runtime, dockerfile, expected): - if IS_WINDOWS and not runtime_supported_by_docker(f"python{runtime}"): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - _tag = uuid4().hex - overrides = { - "Runtime": runtime, - "Handler": "main.handler", - "DockerFile": dockerfile, - "Tag": _tag, - } - cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) - - command_result = run_command(cmdlist, cwd=self.working_dir) - self.assertEqual(command_result.process.returncode, 0) - - self._verify_image_build_artifact( - self.built_template, - self.FUNCTION_LOGICAL_ID_IMAGE, - "ImageUri", - f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{_tag}", - ) - - self._verify_invoke_built_function( - self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected - ) - - @parameterized.expand( - [ - ("feature_phi/Dockerfile", {"phi": "1.62"}), - ("feature_pi/Dockerfile", {"pi": "3.14"}), - ] - ) - def test_intermediate_container_deleted(self, dockerfile, expected): - _tag = uuid4().hex - overrides = { - "Runtime": "3.9", - "Handler": "main.handler", - "DockerFile": dockerfile, - "Tag": _tag, - } - cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) - - _num_of_containers_before_build = self.get_number_of_created_containers() - command_result = run_command(cmdlist, cwd=self.working_dir) - self.assertEqual(command_result.process.returncode, 0) - _num_of_containers_after_build = self.get_number_of_created_containers() - - self._verify_image_build_artifact( - self.built_template, - self.FUNCTION_LOGICAL_ID_IMAGE, - "ImageUri", - f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{_tag}", - ) - - self._verify_invoke_built_function( - self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected - ) - - self.assertEqual( - _num_of_containers_before_build, _num_of_containers_after_build, "Intermediate containers are not removed" - ) - - @parameterized.expand( - [ - ("feature_phi\\Dockerfile", {"phi": "1.62"}), - ("feature_pi\\Dockerfile", {"pi": "3.14"}), - ] - ) - @skipIf(not IS_WINDOWS, "Skipping passing Windows path for dockerfile path on non Windows platform") - def test_windows_dockerfile_present_sub_dir(self, dockerfile, expected): - _tag = uuid4().hex - overrides = { - "Runtime": "3.9", - "Handler": "main.handler", - "DockerFile": dockerfile, - "Tag": _tag, - } - cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) - - command_result = run_command(cmdlist, cwd=self.working_dir) - self.assertEqual(command_result.process.returncode, 0) - - self._verify_image_build_artifact( - self.built_template, - self.FUNCTION_LOGICAL_ID_IMAGE, - "ImageUri", - f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{_tag}", - ) - - self._verify_invoke_built_function( - self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected - ) - - @skipIf( # Hits public ECR pull limitation, move it to canary tests ((not RUN_BY_CANARY) or (IS_WINDOWS and RUNNING_ON_CI) and not CI_OVERRIDE), @@ -492,389 +263,36 @@ def _validate_skipped_built_function( ) -@parameterized_class( - ( - "template", - "FUNCTION_LOGICAL_ID", - "overrides", - "runtime", - "codeuri", - "check_function_only", - "prop", - ), - [ - ("template.yaml", "Function", True, "python3.8", "Python", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.9", "Python", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.10", "Python", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.11", "Python", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.12", "Python", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.8", "PythonPEP600", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.9", "PythonPEP600", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.10", "PythonPEP600", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.11", "PythonPEP600", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.12", "PythonPEP600", False, "CodeUri"), - ], -) -class TestBuildCommand_PythonFunctions_WithoutDocker(BuildIntegPythonBase): - overrides = True - runtime = "python3.9" - codeuri = "Python" - check_function_only = False - use_container = False - - def test_with_default_requirements(self): - self._test_with_default_requirements( - self.runtime, - self.codeuri, - self.use_container, - self.test_data_path, - do_override=self.overrides, - check_function_only=self.check_function_only, - ) - - -@skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) -@parameterized_class( - ( - "template", - "FUNCTION_LOGICAL_ID", - "overrides", - "runtime", - "codeuri", - "check_function_only", - "prop", - ), - [ - ("template.yaml", "Function", True, "python3.8", "Python", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.9", "Python", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.10", "Python", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.11", "Python", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.12", "Python", False, "CodeUri"), - ], -) -class TestBuildCommand_PythonFunctions_WithDocker(BuildIntegPythonBase): - overrides = True - runtime = "python3.9" - codeuri = "Python" - use_container = "use_container" - check_function_only = False - - def test_with_default_requirements(self): - if not runtime_supported_by_docker(self.runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_default_requirements( - self.runtime, - self.codeuri, - self.use_container, - self.test_data_path, - do_override=self.overrides, - check_function_only=self.check_function_only, - ) - - -@skipIf( - # Hits public ECR pull limitation, move it to canary tests - SKIP_DOCKER_TESTS, - "Skip build tests that requires Docker in CI environment", -) -@parameterized_class( - ( - "template", - "FUNCTION_LOGICAL_ID", - "overrides", - "runtime", - "codeuri", - "use_container", - "check_function_only", - "prop", - ), - [ - ( - "cdk_v1_synthesized_template_zip_image_functions.json", - "RandomCitiesFunction5C47A2B8", - False, - None, - None, - False, - True, - "Code", - ), - ], -) -class TestBuildCommand_PythonFunctions_CDK(TestBuildCommand_PythonFunctions_WithoutDocker): - use_container = False - - def test_cdk_app_with_default_requirements(self): - self._test_with_default_requirements( - self.runtime, - self.codeuri, - self.use_container, - self.test_data_path, - do_override=self.overrides, - check_function_only=self.check_function_only, - ) - - -@skipIf( - # Hits public ECR pull limitation, move it to canary tests - SKIP_DOCKER_TESTS, - "Skip build tests that requires Docker in CI environment", -) -@parameterized_class( - ( - "template", - "FUNCTION_LOGICAL_ID", - "overrides", - "use_container", - "prop", - ), - [ - ( - "cdk_v2_synthesized_template_image_function_shared_code.json", - "TestLambdaFunctionC089708A", - False, - False, - "Code.ImageUri", - ), - ], -) -class TestBuildCommandCDKPythonImageFunctionSharedCode(BuildIntegPythonBase): - def test_cdk_app_with_default_requirements(self): - expected = "Hello World" - cmdlist = self.get_command_list(use_container=self.use_container) - command_result = run_command(cmdlist, cwd=self.working_dir) - self.assertEqual(command_result.process.returncode, 0) - - self._verify_image_build_artifact( - self.built_template, - self.FUNCTION_LOGICAL_ID, - self.prop, - f"{self.FUNCTION_LOGICAL_ID.lower()}:latest", - ) - - self._verify_invoke_built_function(self.built_template, self.FUNCTION_LOGICAL_ID, {}, expected) - - -class TestBuildCommand_PythonFunctions_With_Specified_Architecture(BuildIntegPythonBase): - template = "template_with_architecture.yaml" - - @parameterized.expand( - [ - ("python3.8", "Python", False, "x86_64"), - ("python3.9", "Python", False, "x86_64"), - ("python3.10", "Python", False, "x86_64"), - ("python3.11", "Python", False, "x86_64"), - ("python3.12", "Python", False, "x86_64"), - ("python3.8", "PythonPEP600", False, "x86_64"), - ("python3.9", "PythonPEP600", False, "x86_64"), - ("python3.10", "PythonPEP600", False, "x86_64"), - ("python3.11", "PythonPEP600", False, "x86_64"), - ("python3.12", "PythonPEP600", False, "x86_64"), - ("python3.8", "Python", "use_container", "x86_64"), - ("python3.9", "Python", "use_container", "x86_64"), - ("python3.10", "Python", "use_container", "x86_64"), - ("python3.11", "Python", "use_container", "x86_64"), - ("python3.12", "Python", "use_container", "x86_64"), - ] - ) - def test_with_default_requirements(self, runtime, codeuri, use_container, architecture): - if use_container and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_default_requirements( - runtime, codeuri, use_container, self.test_data_path, architecture=architecture - ) - - def test_invalid_architecture(self): - overrides = {"Runtime": "python3.11", "Architectures": "fake"} - cmdlist = self.get_command_list(parameter_overrides=overrides) - process_execute = run_command(cmdlist, cwd=self.working_dir) - - self.assertEqual(1, process_execute.process.returncode) - - self.assertIn("Build Failed", str(process_execute.stdout)) - self.assertIn("Architecture fake is not supported", str(process_execute.stderr)) - - -class TestBuildCommand_ErrorCases(BuildIntegBase): - def test_unsupported_runtime(self): - overrides = {"Runtime": "unsupportedpython", "CodeUri": "Python"} - cmdlist = self.get_command_list(parameter_overrides=overrides) - - process_execute = run_command(cmdlist, cwd=self.working_dir) - self.assertEqual(1, process_execute.process.returncode) - - self.assertIn("Build Failed", str(process_execute.stdout)) - - -class TestBuildCommand_NodeFunctions(BuildIntegNodeBase): - @parameterized.expand( - [ - ("nodejs16.x", False), - ("nodejs18.x", False), - ("nodejs20.x", False), - ("nodejs16.x", "use_container"), - ("nodejs18.x", "use_container"), - ("nodejs20.x", "use_container"), - ] - ) - def test_building_default_package_json(self, runtime, use_container): - if use_container: - if SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: - self.skipTest(SKIP_DOCKER_MESSAGE) - if not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_default_package_json(runtime, use_container, self.test_data_path) - - -class TestBuildCommand_NodeFunctions_With_External_Manifest(BuildIntegNodeBase): - CODE_URI = "Node_without_manifest" - TEST_INVOKE = True - MANIFEST_PATH = "npm_manifest/package.json" - - @parameterized.expand( - [ - ("nodejs16.x",), - ("nodejs18.x",), - ("nodejs20.x",), - ] - ) - def test_building_default_package_json(self, runtime): - self._test_with_default_package_json(runtime, False, self.test_data_path) - - -class TestBuildCommand_EsbuildFunctions(BuildIntegEsbuildBase): - template = "template_with_metadata_esbuild.yaml" - - @parameterized.expand( - [ - ("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"), - # Keeping container tests as Node.js18 until our CI platform can run Node.js20 container tests - ("nodejs18.x", "Esbuild/Node", {"main.js", "main.js.map"}, "main.lambdaHandler", "use_container", "x86_64"), - ( - "nodejs18.x", - "Esbuild/TypeScript", - {"app.js", "app.js.map"}, - "app.lambdaHandler", - "use_container", - "x86_64", - ), - ] - ) - def test_building_default_package_json( - self, runtime, code_uri, expected_files, handler, use_container, architecture - ): - self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, architecture) - - -class TestBuildCommand_EsbuildFunctions_With_External_Manifest(BuildIntegEsbuildBase): - template = "template_with_metadata_esbuild.yaml" - MANIFEST_PATH = "Esbuild/npm_manifest/package.json" - - @parameterized.expand( - [ - ( - "nodejs20.x", - "Esbuild/Node_without_manifest", - {"main.js", "main.js.map"}, - "main.lambdaHandler", - False, - "x86_64", - ), - ( - "nodejs20.x", - "Esbuild/TypeScript_without_manifest", - {"app.js", "app.js.map"}, - "app.lambdaHandler", - False, - "x86_64", - ), - ] - ) - def test_building_default_package_json( - self, runtime, code_uri, expected_files, handler, use_container, architecture - ): - self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, architecture) - - -@skipIf( - ((IS_WINDOWS and RUNNING_ON_CI) and not CI_OVERRIDE), - "Skip build tests on windows when running in CI unless overridden", -) -@parameterized_class( - ("template",), - [ - ("esbuild_templates/template_with_metadata_node_options.yaml",), - ("esbuild_templates/template_with_metadata_global_node_options.yaml",), - ], -) -class TestBuildCommand_EsbuildFunctionProperties(BuildIntegEsbuildBase): - @parameterized.expand( - [ - ("nodejs16.x", "../Esbuild/TypeScript", "app.lambdaHandler", "x86_64"), - ("nodejs18.x", "../Esbuild/TypeScript", "app.lambdaHandler", "x86_64"), - ("nodejs20.x", "../Esbuild/TypeScript", "app.lambdaHandler", "x86_64"), - ("nodejs16.x", "../Esbuild/TypeScript", "nested/function/app.lambdaHandler", "x86_64"), - ("nodejs18.x", "../Esbuild/TypeScript", "nested/function/app.lambdaHandler", "x86_64"), - ("nodejs20.x", "../Esbuild/TypeScript", "nested/function/app.lambdaHandler", "x86_64"), - ] - ) - def test_environment_generates_sourcemap(self, runtime, code_uri, handler, architecture): - overrides = { - "runtime": runtime, - "code_uri": code_uri, - "handler": handler, - "architecture": architecture, - } - self._test_with_various_properties(overrides, runtime) - - -class TestBuildCommand_NodeFunctions_With_Specified_Architecture(BuildIntegNodeBase): - template = "template_with_architecture.yaml" - - @parameterized.expand( - [ - ("nodejs16.x", False, "x86_64"), - ("nodejs18.x", False, "x86_64"), - ("nodejs20.x", False, "x86_64"), - ("nodejs16.x", "use_container", "x86_64"), - ("nodejs18.x", "use_container", "x86_64"), - ] - ) - def test_building_default_package_json(self, runtime, use_container, architecture): - if use_container and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_default_package_json(runtime, use_container, self.test_data_path, architecture) +@pytest.mark.ruby +class TestBuildCommand_RubyFunctions(BuildIntegRubyBase): + @parameterized.expand([(False,), ("use_container",)]) + 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) + self._test_with_default_gemfile("ruby3.2", use_container, "Ruby", self.test_data_path) -class TestBuildCommand_RubyFunctions(BuildIntegRubyBase): - @parameterized.expand(["ruby3.2", "ruby3.3"]) + @parameterized.expand([("ruby3.3",)]) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) - def test_building_ruby_in_container(self, runtime): - if IS_WINDOWS and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) + @pytest.mark.al2023 + def test_building_ruby_al2023(self, runtime): self._test_with_default_gemfile(runtime, "use_container", "Ruby", self.test_data_path) - @parameterized.expand(["ruby3.2"]) - def test_building_ruby_in_process(self, runtime): - self._test_with_default_gemfile(runtime, False, "Ruby", self.test_data_path) - class TestBuildCommand_RubyFunctions_With_Architecture(BuildIntegRubyBase): template = "template_with_architecture.yaml" - @parameterized.expand([("ruby3.2", "Ruby32"), ("ruby3.3", "Ruby33")]) + @parameterized.expand([(False,), ("use_container",)]) + 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) + self._test_with_default_gemfile("ruby3.2", use_container, "Ruby32", self.test_data_path, "x86_64") + + @parameterized.expand([("ruby3.3", "Ruby33", False), ("ruby3.3", "Ruby33", True)]) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) - def test_building_ruby_in_container_with_specified_architecture(self, runtime, codeuri): - if IS_WINDOWS and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_default_gemfile(runtime, "use_container", codeuri, self.test_data_path, "x86_64") - - @parameterized.expand([("ruby3.2", "Ruby32"), ("ruby3.3", "Ruby33")]) - def test_building_ruby_in_process_with_specified_architecture(self, runtime, codeuri): - if IS_WINDOWS and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_default_gemfile(runtime, False, codeuri, self.test_data_path, "x86_64") + @pytest.mark.al2023 + def test_building_ruby_al2023(self, runtime, codeuri, use_container): + self._test_with_default_gemfile(runtime, use_container, codeuri, self.test_data_path, "x86_64") class TestBuildCommand_RubyFunctionsWithGemfileInTheRoot(BuildIntegRubyBase): @@ -909,549 +327,6 @@ def _prepare_application_environment(self): self.template_path = str(Path(self.working_dir).joinpath("template.yaml")) -class TestBuildCommand_Java(BuildIntegJavaBase): - @parameterized.expand( - [ - ( - "java8.al2", - "8", - BuildIntegJavaBase.USING_GRADLE_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java8.al2", - "8", - BuildIntegJavaBase.USING_GRADLEW_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java8.al2", - "8", - BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java8.al2", - "8", - BuildIntegJavaBase.USING_MAVEN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, - BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, - ), - ( - "java11", - "11", - BuildIntegJavaBase.USING_GRADLE_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java11", - "11", - BuildIntegJavaBase.USING_GRADLEW_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java11", - "11", - BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java11", - "11", - BuildIntegJavaBase.USING_MAVEN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, - BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, - ), - ( - "java17", - "17", - BuildIntegJavaBase.USING_GRADLE_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java17", - "17", - BuildIntegJavaBase.USING_GRADLEW_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java17", - "17", - BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java17", - "17", - BuildIntegJavaBase.USING_MAVEN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, - BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, - ), - ( - "java21", - "21", - BuildIntegJavaBase.USING_GRADLE_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java21", - "21", - BuildIntegJavaBase.USING_GRADLEW_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java21", - "21", - BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java21", - "21", - BuildIntegJavaBase.USING_MAVEN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, - BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, - ), - ] - ) - @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) - def test_building_java_in_container( - self, runtime, runtime_version, code_path, expected_files, expected_dependencies - ): - if not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_building_java( - runtime, - os.path.join(code_path, runtime_version), - expected_files, - expected_dependencies, - "use_container", - self.test_data_path, - ) - - @parameterized.expand( - [ - ( - "java8.al2", - "8", - BuildIntegJavaBase.USING_GRADLE_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java8.al2", - "8", - BuildIntegJavaBase.USING_GRADLEW_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java8.al2", - "8", - BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java8.al2", - "8", - BuildIntegJavaBase.USING_MAVEN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, - BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, - ), - ( - "java11", - "11", - BuildIntegJavaBase.USING_GRADLE_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java11", - "11", - BuildIntegJavaBase.USING_GRADLEW_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java11", - "11", - BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java11", - "11", - BuildIntegJavaBase.USING_MAVEN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, - BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, - ), - ( - "java17", - "17", - BuildIntegJavaBase.USING_GRADLE_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java17", - "17", - BuildIntegJavaBase.USING_GRADLEW_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java17", - "17", - BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java17", - "17", - BuildIntegJavaBase.USING_MAVEN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, - BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, - ), - ( - "java21", - "21", - BuildIntegJavaBase.USING_GRADLE_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java21", - "21", - BuildIntegJavaBase.USING_GRADLEW_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java21", - "21", - BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, - BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, - ), - ( - "java21", - "21", - BuildIntegJavaBase.USING_MAVEN_PATH, - BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, - BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, - ), - ] - ) - def test_building_java_in_process(self, runtime, runtime_version, code_path, expected_files, expected_dependencies): - self._test_with_building_java( - runtime, - os.path.join(code_path, runtime_version), - expected_files, - expected_dependencies, - False, - self.test_data_path, - ) - - -@pytest.mark.dotnet -class TestBuildCommand_Dotnet_cli_package(BuildIntegBase): - FUNCTION_LOGICAL_ID = "Function" - EXPECTED_FILES_PROJECT_MANIFEST = { - "Amazon.Lambda.APIGatewayEvents.dll", - "Amazon.Lambda.Core.dll", - "HelloWorld.runtimeconfig.json", - "Amazon.Lambda.Serialization.Json.dll", - "Newtonsoft.Json.dll", - "HelloWorld.deps.json", - "HelloWorld.dll", - } - - EXPECTED_FILES_PROJECT_MANIFEST_PROVIDED = { - "bootstrap", - } - - @parameterized.expand( - [ - ("dotnet6", "Dotnet6", None), - ("dotnet6", "Dotnet6", "debug"), - ("provided.al2", "Dotnet7", None), - ("dotnet8", "Dotnet8", None), - ("dotnet8", "Dotnet8", "debug"), - ] - ) - def test_dotnet_in_process(self, runtime, code_uri, mode, architecture="x86_64"): - # dotnet7 requires docker to build the function - if code_uri == "Dotnet7" and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): - self.skipTest(SKIP_DOCKER_MESSAGE) - overrides = { - "Runtime": runtime, - "CodeUri": code_uri, - "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", - "Architectures": architecture, - } - - if runtime == "provided.al2": - self.template_path = self.template_path.replace("template.yaml", "template_build_method_dotnet_7.yaml") - - cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) - LOG.info("Running with SAM_BUILD_MODE={}".format(mode)) - - newenv = os.environ.copy() - if mode: - newenv["SAM_BUILD_MODE"] = mode - - command_result = run_command(cmdlist, cwd=self.working_dir, env=newenv) - self.assertEqual(command_result.process.returncode, 0) - - if not runtime_supported_by_docker(runtime) and IS_WINDOWS: - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - - self._verify_built_artifact( - self.default_build_dir, - self.FUNCTION_LOGICAL_ID, - ( - self.EXPECTED_FILES_PROJECT_MANIFEST - if runtime != "provided.al2" - else self.EXPECTED_FILES_PROJECT_MANIFEST_PROVIDED - ), - ) - - self._verify_resource_property( - str(self.built_template), - "OtherRelativePathResource", - "BodyS3Location", - os.path.relpath( - os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), - str(self.default_build_dir), - ), - ) - - self._verify_resource_property( - str(self.built_template), - "GlueResource", - "Command.ScriptLocation", - os.path.relpath( - os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), - str(self.default_build_dir), - ), - ) - - expected = "{'message': 'Hello World'}" - if not SKIP_DOCKER_TESTS: - self._verify_invoke_built_function( - self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected - ) - self.verify_docker_container_cleanedup(runtime) - - @parameterized.expand( - [ - ("dotnet6", "Dotnet6", None), - ("dotnet6", "Dotnet6", "debug"), - # force to run tests on arm64 machines may cause dotnet7 test failing - # because Native AOT Lambda functions require the host and lambda architectures to match - ("provided.al2", "Dotnet7", None), - ("dotnet8", "Dotnet8", None), - ("dotnet8", "Dotnet8", "debug"), - ] - ) - @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) - def test_dotnet_in_container_mount_with_write_explicit(self, runtime, code_uri, mode, architecture="x86_64"): - if not runtime_supported_by_docker(runtime) and IS_WINDOWS: - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - - overrides = { - "Runtime": runtime, - "CodeUri": code_uri, - "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", - "Architectures": architecture, - } - - if runtime == "provided.al2": - self.template_path = self.template_path.replace("template.yaml", "template_build_method_dotnet_7.yaml") - - # test with explicit mount_with_write flag - cmdlist = self.get_command_list(use_container=True, parameter_overrides=overrides, mount_with=MountMode.WRITE) - # env vars needed for testing unless set by dotnet images on public.ecr.aws - cmdlist += ["--container-env-var", "DOTNET_CLI_HOME=/tmp/dotnet"] - cmdlist += ["--container-env-var", "XDG_DATA_HOME=/tmp/xdg"] - - LOG.info("Running with SAM_BUILD_MODE={}".format(mode)) - - newenv = os.environ.copy() - if mode: - newenv["SAM_BUILD_MODE"] = mode - - command_result = run_command(cmdlist, cwd=self.working_dir, env=newenv) - self.assertEqual(command_result.process.returncode, 0) - - self._verify_built_artifact( - self.default_build_dir, - self.FUNCTION_LOGICAL_ID, - ( - self.EXPECTED_FILES_PROJECT_MANIFEST - if runtime != "provided.al2" - else self.EXPECTED_FILES_PROJECT_MANIFEST_PROVIDED - ), - ) - - self._verify_resource_property( - str(self.built_template), - "OtherRelativePathResource", - "BodyS3Location", - os.path.relpath( - os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), - str(self.default_build_dir), - ), - ) - - self._verify_resource_property( - str(self.built_template), - "GlueResource", - "Command.ScriptLocation", - os.path.relpath( - os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), - str(self.default_build_dir), - ), - ) - - expected = "{'message': 'Hello World'}" - self._verify_invoke_built_function( - self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected - ) - self.verify_docker_container_cleanedup(runtime) - - @parameterized.expand( - [ - ("dotnet6", "Dotnet6", None), - ("dotnet6", "Dotnet6", "debug"), - # force to run tests on arm64 machines may cause dotnet7 test failing - # because Native AOT Lambda functions require the host and lambda architectures to match - ("provided.al2", "Dotnet7", None), - ("dotnet8", "Dotnet8", None), - ("dotnet8", "Dotnet8", "debug"), - ] - ) - @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) - def test_dotnet_in_container_mount_with_write_interactive( - self, - runtime, - code_uri, - mode, - architecture="x86_64", - ): - if not runtime_supported_by_docker(runtime) and IS_WINDOWS: - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - - overrides = { - "Runtime": runtime, - "CodeUri": code_uri, - "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", - "Architectures": architecture, - } - - if runtime == "provided.al2": - self.template_path = self.template_path.replace("template.yaml", "template_build_method_dotnet_7.yaml") - - # test without explicit mount_with_write flag - cmdlist = self.get_command_list(use_container=True, parameter_overrides=overrides) - # env vars needed for testing unless set by dotnet images on public.ecr.aws - cmdlist += ["--container-env-var", "DOTNET_CLI_HOME=/tmp/dotnet"] - cmdlist += ["--container-env-var", "XDG_DATA_HOME=/tmp/xdg"] - - LOG.info("Running with SAM_BUILD_MODE={}".format(mode)) - - # mock user input to mount with write - user_click_confirm_input = "y" - command_result = run_command_with_input(cmdlist, user_click_confirm_input.encode(), cwd=self.working_dir) - self.assertEqual(command_result.process.returncode, 0) - - self._verify_built_artifact( - self.default_build_dir, - self.FUNCTION_LOGICAL_ID, - ( - self.EXPECTED_FILES_PROJECT_MANIFEST - if runtime != "provided.al2" - else self.EXPECTED_FILES_PROJECT_MANIFEST_PROVIDED - ), - ) - - self._verify_resource_property( - str(self.built_template), - "OtherRelativePathResource", - "BodyS3Location", - os.path.relpath( - os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), - str(self.default_build_dir), - ), - ) - - self._verify_resource_property( - str(self.built_template), - "GlueResource", - "Command.ScriptLocation", - os.path.relpath( - os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), - str(self.default_build_dir), - ), - ) - - expected = "{'message': 'Hello World'}" - self._verify_invoke_built_function( - self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected - ) - self.verify_docker_container_cleanedup(runtime) - - @parameterized.expand([("dotnet6", "Dotnet6"), ("dotnet8", "Dotnet8")]) - @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) - def test_must_fail_on_container_mount_without_write_interactive(self, runtime, code_uri): - use_container = True - overrides = { - "Runtime": runtime, - "CodeUri": code_uri, - "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", - } - cmdlist = self.get_command_list(use_container=use_container, parameter_overrides=overrides) - - # mock user input to not allow mounting with write - user_click_confirm_input = "N" - process_execute = run_command_with_input(cmdlist, user_click_confirm_input.encode()) - - # Must error out, because mounting with write is not allowed - self.assertEqual(process_execute.process.returncode, 1) - - def _verify_built_artifact(self, build_dir, function_logical_id, expected_files): - self.assertTrue(build_dir.exists(), "Build directory should be created") - - build_dir_files = os.listdir(str(build_dir)) - self.assertIn("template.yaml", build_dir_files) - self.assertIn(function_logical_id, build_dir_files) - - template_path = build_dir.joinpath("template.yaml") - resource_artifact_dir = build_dir.joinpath(function_logical_id) - - # Make sure the template has correct CodeUri for resource - self._verify_resource_property(str(template_path), function_logical_id, "CodeUri", function_logical_id) - - all_artifacts = set(os.listdir(str(resource_artifact_dir))) - actual_files = all_artifacts.intersection(expected_files) - self.assertEqual(actual_files, expected_files) - - class TestBuildCommand_Go_Modules(BuildIntegGoBase): @parameterized.expand([("go1.x", "Go", None, False), ("go1.x", "Go", "debug", True)]) def test_building_go(self, runtime, code_uri, mode, use_container): @@ -1912,91 +787,6 @@ def _get_python_version(self): return "python{}.{}".format(sys.version_info.major, sys.version_info.minor) -@parameterized_class( - ("template", "is_nested_parent"), - [ - (os.path.join("nested-parent", "template-parent.yaml"), "is_nested_parent"), - ("template.yaml", False), - ], -) -class TestBuildCommand_ProvidedFunctions(BuildIntegProvidedBase): - # Test Suite for runtime: provided and where selection of the build workflow is implicitly makefile builder - # if the makefile is present. - @parameterized.expand( - [ - ("provided", False, None), - ("provided", "use_container", "Makefile-container"), - ("provided.al2", False, None), - ("provided.al2", "use_container", "Makefile-container"), - ("provided.al2023", False, None), - ("provided.al2023", "use_container", "Makefile-container"), - ] - ) - def test_building_Makefile(self, runtime, use_container, manifest): - if use_container: - if SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: - self.skipTest(SKIP_DOCKER_MESSAGE) - if not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_Makefile(runtime, use_container, manifest) - - -@parameterized_class( - ("template", "is_nested_parent"), - [ - (os.path.join("nested-parent", "template-parent.yaml"), "is_nested_parent"), - ("template.yaml", False), - ], -) -class TestBuildCommand_ProvidedFunctions_With_Specified_Architecture(BuildIntegProvidedBase): - # Test Suite for runtime: provided and where selection of the build workflow is implicitly makefile builder - # if the makefile is present. - @parameterized.expand( - [ - ("provided", False, None, "x86_64"), - ("provided", "use_container", "Makefile-container", "x86_64"), - ("provided.al2", False, None, "x86_64"), - ("provided.al2", "use_container", "Makefile-container", "x86_64"), - ("provided.al2023", False, None, "x86_64"), - ("provided.al2023", "use_container", "Makefile-container", "x86_64"), - ] - ) - def test_building_Makefile(self, runtime, use_container, manifest, architecture): - if use_container and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_Makefile(runtime, use_container, manifest, architecture) - - -@parameterized_class( - ("template", "code_uri", "is_nested_parent"), - [ - ("custom_build_with_custom_root_project_path.yaml", "empty_src_code", False), - ("custom_build_with_custom_make_file_path.yaml", "provided_src_code_without_makefile", False), - ("custom_build_with_custom_working_dir.yaml", "custom_working_dir_src_code", False), - ("custom_build_with_custom_root_project_path_and_custom_makefile_path.yaml", "empty_src_code", False), - ( - "custom_build_with_custom_root_project_path_custom_makefile_path_and_custom_working_dir.yaml", - "empty_src_code", - False, - ), - ], -) -class TestBuildCommand_ProvidedFunctionsWithCustomMetadata(BuildIntegProvidedBase): - # Test Suite for runtime: provided and where selection of the build workflow is implicitly makefile builder - # if the makefile is present. - @parameterized.expand( - [ - ("provided", False, None), - ("provided.al2", False, None), - ("provided.al2023", False, None), - ] - ) - def test_building_Makefile(self, runtime, use_container, manifest): - if use_container and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) - self._test_with_Makefile(runtime, use_container, manifest) - - @skipIf( ((IS_WINDOWS and RUNNING_ON_CI) and not CI_OVERRIDE), "Skip build tests on windows when running in CI unless overridden", diff --git a/tests/integration/buildcmd/test_build_cmd_arm64.py b/tests/integration/buildcmd/test_build_cmd_arm64.py index 403c6e269b6..b1eb8acb7b6 100644 --- a/tests/integration/buildcmd/test_build_cmd_arm64.py +++ b/tests/integration/buildcmd/test_build_cmd_arm64.py @@ -1,5 +1,6 @@ import os from unittest import skipIf +import pytest from parameterized import parameterized, parameterized_class @@ -22,11 +23,10 @@ CI_OVERRIDE, IS_WINDOWS, RUNNING_ON_CI, - runtime_supported_by_docker, - RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG, ) +@pytest.mark.python class TestBuildCommand_PythonFunctions_With_Specified_Architecture_arm64(BuildIntegPythonBase): template = "template_with_architecture.yaml" @@ -46,12 +46,18 @@ class TestBuildCommand_PythonFunctions_With_Specified_Architecture_arm64(BuildIn ("python3.9", "Python", "use_container"), ("python3.10", "Python", "use_container"), ("python3.11", "Python", "use_container"), - ("python3.12", "Python", "use_container"), ] ) def test_with_default_requirements(self, runtime, codeuri, use_container): - if use_container and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) + self._test_with_default_requirements(runtime, codeuri, use_container, self.test_data_path, architecture=ARM64) + + @parameterized.expand( + [ + ("python3.12", "Python", "use_container"), + ] + ) + @pytest.mark.al2023 + def test_with_default_requirements_al2023(self, runtime, codeuri, use_container): self._test_with_default_requirements(runtime, codeuri, use_container, self.test_data_path, architecture=ARM64) @@ -76,6 +82,7 @@ def test_building_default_package_json(self, runtime, code_uri, expected_files, self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, ARM64) +@pytest.mark.nodejs class TestBuildCommand_EsbuildFunctions_With_External_Manifest_arm64(BuildIntegEsbuildBase): template = "template_with_metadata_esbuild.yaml" MANIFEST_PATH = "Esbuild/npm_manifest/package.json" @@ -127,11 +134,10 @@ class TestBuildCommand_EsbuildFunctions_With_External_Manifest_arm64(BuildIntegE ] ) def test_building_default_package_json(self, runtime, code_uri, expected_files, handler, use_container): - if use_container and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, ARM64) +@pytest.mark.nodejs class TestBuildCommand_NodeFunctions_With_Specified_Architecture_arm64(BuildIntegNodeBase): template = "template_with_architecture.yaml" @@ -142,12 +148,18 @@ class TestBuildCommand_NodeFunctions_With_Specified_Architecture_arm64(BuildInte ("nodejs20.x", False), ("nodejs16.x", "use_container"), ("nodejs18.x", "use_container"), - ("nodejs20.x", "use_container"), ] ) def test_building_default_package_json(self, runtime, use_container): - if use_container and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) + self._test_with_default_package_json(runtime, use_container, self.test_data_path, ARM64) + + @parameterized.expand( + [ + ("nodejs20.x", "use_container"), + ] + ) + @pytest.mark.al2023 + def test_building_default_package_json_al2023(self, runtime, use_container): self._test_with_default_package_json(runtime, use_container, self.test_data_path, ARM64) @@ -168,6 +180,7 @@ def test_building_ruby_in_process_with_specified_architecture(self, runtime, cod (IS_WINDOWS and not CI_OVERRIDE), "Skip build tests on windows when running in CI unless overridden", ) +@pytest.mark.java class TestBuildCommand_Java_With_Specified_Architecture_arm64(BuildIntegJavaBase): template = "template_with_architecture.yaml" @@ -257,6 +270,24 @@ class TestBuildCommand_Java_With_Specified_Architecture_arm64(BuildIntegJavaBase BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, ), + ] + ) + @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) + def test_building_java_in_container_with_arm64_architecture( + self, runtime, runtime_version, code_path, expected_files, expected_dependencies + ): + self._test_with_building_java( + runtime, + os.path.join(code_path, runtime_version), + expected_files, + expected_dependencies, + "use_container", + self.test_data_path, + ARM64, + ) + + @parameterized.expand( + [ ( "java21", "21", @@ -288,11 +319,10 @@ class TestBuildCommand_Java_With_Specified_Architecture_arm64(BuildIntegJavaBase ] ) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) - def test_building_java_in_container_with_arm64_architecture( + @pytest.mark.al2023 + def test_building_java_in_container_with_arm64_architecture_al2023( self, runtime, runtime_version, code_path, expected_files, expected_dependencies ): - if not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) self._test_with_building_java( runtime, os.path.join(code_path, runtime_version), @@ -469,6 +499,11 @@ class TestBuildCommand_ProvidedFunctions_With_Specified_Architecture_arm64(Build False, None, ), + ( + "provided.al2023", + False, + None, + ), ( "provided", "use_container", @@ -484,11 +519,13 @@ class TestBuildCommand_ProvidedFunctions_With_Specified_Architecture_arm64(Build "use_container", "Makefile-container", ), - ( - "provided.al2023", - False, - None, - ), + ] + ) + def test_building_Makefile(self, runtime, use_container, manifest): + self._test_with_Makefile(runtime, use_container, manifest, ARM64) + + @parameterized.expand( + [ ( "provided.al2023", "use_container", @@ -496,9 +533,8 @@ class TestBuildCommand_ProvidedFunctions_With_Specified_Architecture_arm64(Build ), ] ) - def test_building_Makefile(self, runtime, use_container, manifest): - if use_container and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) + @pytest.mark.al2023 + def test_building_Makefile_al2023(self, runtime, use_container, manifest): self._test_with_Makefile(runtime, use_container, manifest, ARM64) @@ -507,6 +543,7 @@ def test_building_Makefile(self, runtime, use_container, manifest): "Skip build tests on windows when running in CI unless overridden", ) @rust_parameterized_class +@pytest.mark.provided class TestBuildCommand_Rust_arm64(BuildIntegRustBase): @parameterized.expand( [ @@ -517,8 +554,6 @@ class TestBuildCommand_Rust_arm64(BuildIntegRustBase): ] ) def test_build(self, runtime, build_mode, use_container): - if use_container and not runtime_supported_by_docker(runtime): - self.skipTest(RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG) self._test_with_rust_cargo_lambda( runtime=runtime, code_uri=self.code_uri, diff --git a/tests/integration/buildcmd/test_build_cmd_dotnet.py b/tests/integration/buildcmd/test_build_cmd_dotnet.py new file mode 100644 index 00000000000..bc9cd9b24c1 --- /dev/null +++ b/tests/integration/buildcmd/test_build_cmd_dotnet.py @@ -0,0 +1,168 @@ +import logging +from unittest import skipIf + +import pytest +from parameterized import parameterized + +from samcli.commands.build.utils import MountMode +from tests.testing_utils import ( + SKIP_DOCKER_TESTS, + SKIP_DOCKER_BUILD, + SKIP_DOCKER_MESSAGE, + run_command_with_input, +) +from tests.integration.buildcmd.build_integ_base import ( + BuildIntegDotnetBase, +) + + +LOG = logging.getLogger(__name__) + + +@pytest.mark.dotnet +class TestBuildCommand_Dotnet_cli_package(BuildIntegDotnetBase): + @parameterized.expand( + [ + ("provided.al2", "Dotnet7", None, None), + ("provided.al2", "Dotnet7", None, MountMode.WRITE), + ] + ) + @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) + def test_dotnet_al2(self, runtime, code_uri, mode, mount_mode): + overrides = { + "Runtime": runtime, + "CodeUri": code_uri, + "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", + "Architectures": "x86_64", + } + + self.template_path = self.template_path.replace("template.yaml", "template_build_method_dotnet_7.yaml") + + self.validate_build_command(overrides, mode, mount_mode) + self.validate_build_artifacts(self.EXPECTED_FILES_PROJECT_MANIFEST_PROVIDED) + self.validate_invoke_command(overrides, runtime) + + @parameterized.expand( + [ + ("dotnet6", "Dotnet6", None, None), + ("dotnet6", "Dotnet6", None, MountMode.WRITE), + ("dotnet6", "Dotnet6", "debug", None), + ("dotnet6", "Dotnet6", "debug", MountMode.WRITE), + ] + ) + def test_dotnet_6(self, runtime, code_uri, mode, mount_mode): + if mount_mode and SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: + self.skipTest(SKIP_DOCKER_MESSAGE) + + overrides = { + "Runtime": runtime, + "CodeUri": code_uri, + "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", + "Architectures": "x86_64", + } + + self.validate_build_command(overrides, mode, mount_mode) + self.validate_build_artifacts(self.EXPECTED_FILES_PROJECT_MANIFEST) + + if not SKIP_DOCKER_TESTS: + self.validate_invoke_command(overrides, runtime) + + @parameterized.expand( + [ + ("dotnet8", "Dotnet8", None, None), + ("dotnet8", "Dotnet8", None, MountMode.WRITE), + ("dotnet8", "Dotnet8", "debug", None), + ("dotnet8", "Dotnet8", "debug", MountMode.WRITE), + ] + ) + @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) + @pytest.mark.al2023 + def test_dotnet_al2023(self, runtime, code_uri, mode, mount_mode): + overrides = { + "Runtime": runtime, + "CodeUri": code_uri, + "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", + "Architectures": "x86_64", + } + + self.validate_build_command(overrides, mode, mount_mode) + self.validate_build_artifacts(self.EXPECTED_FILES_PROJECT_MANIFEST) + self.validate_invoke_command(overrides, runtime) + + +@pytest.mark.dotnet +@skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) +class TestBuildCommand_Dotnet_cli_package_interactive(BuildIntegDotnetBase): + @parameterized.expand( + [ + ("provided.al2", "Dotnet7", None), + ] + ) + def test_dotnet_al2(self, runtime, code_uri, mode): + overrides = { + "Runtime": runtime, + "CodeUri": code_uri, + "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", + "Architectures": "x86_64", + } + + self.template_path = self.template_path.replace("template.yaml", "template_build_method_dotnet_7.yaml") + + self.validate_build_command(overrides, mode, use_container=True, input="y") + self.validate_build_artifacts(self.EXPECTED_FILES_PROJECT_MANIFEST_PROVIDED) + self.validate_invoke_command(overrides, runtime) + + @parameterized.expand( + [ + ("dotnet6", "Dotnet6", None), + ("dotnet6", "Dotnet6", "debug"), + ] + ) + def test_dotnet_6(self, runtime, code_uri, mode): + overrides = { + "Runtime": runtime, + "CodeUri": code_uri, + "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", + "Architectures": "x86_64", + } + + self.validate_build_command(overrides, mode, use_container=True, input="y") + self.validate_build_artifacts(self.EXPECTED_FILES_PROJECT_MANIFEST) + self.validate_invoke_command(overrides, runtime) + + @parameterized.expand( + [ + ("dotnet8", "Dotnet8", None), + ("dotnet8", "Dotnet8", "debug"), + ] + ) + @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) + @pytest.mark.al2023 + def test_dotnet_al2023(self, runtime, code_uri, mode): + overrides = { + "Runtime": runtime, + "CodeUri": code_uri, + "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", + "Architectures": "x86_64", + } + + self.validate_build_command(overrides, mode, use_container=True, input="y") + self.validate_build_artifacts(self.EXPECTED_FILES_PROJECT_MANIFEST) + 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): + use_container = True + overrides = { + "Runtime": runtime, + "CodeUri": code_uri, + "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler", + } + cmdlist = self.get_command_list(use_container=use_container, parameter_overrides=overrides) + + # mock user input to not allow mounting with write + user_click_confirm_input = "N" + process_execute = run_command_with_input(cmdlist, user_click_confirm_input.encode()) + + # Must error out, because mounting with write is not allowed + self.assertEqual(process_execute.process.returncode, 1) diff --git a/tests/integration/buildcmd/test_build_cmd_java.py b/tests/integration/buildcmd/test_build_cmd_java.py new file mode 100644 index 00000000000..2b4f0a29ec8 --- /dev/null +++ b/tests/integration/buildcmd/test_build_cmd_java.py @@ -0,0 +1,299 @@ +import logging +import os +from unittest import skipIf + +import pytest +from parameterized import parameterized +from tests.testing_utils import ( + RUNNING_ON_CI, + RUNNING_TEST_FOR_MASTER_ON_CI, + RUN_BY_CANARY, + SKIP_DOCKER_TESTS, + SKIP_DOCKER_BUILD, + SKIP_DOCKER_MESSAGE, +) +from tests.integration.buildcmd.build_integ_base import ( + BuildIntegJavaBase, +) + + +LOG = logging.getLogger(__name__) + +# SAR tests require credentials. This is to skip running the test where credentials are not available. +SKIP_SAR_TESTS = RUNNING_ON_CI and RUNNING_TEST_FOR_MASTER_ON_CI and not RUN_BY_CANARY + + +@pytest.mark.java +class TestBuildCommand_Java(BuildIntegJavaBase): + @parameterized.expand( + [ + ( + "java8.al2", + "8", + BuildIntegJavaBase.USING_GRADLE_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java8.al2", + "8", + BuildIntegJavaBase.USING_GRADLEW_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java8.al2", + "8", + BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java8.al2", + "8", + BuildIntegJavaBase.USING_MAVEN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, + BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, + ), + ( + "java11", + "11", + BuildIntegJavaBase.USING_GRADLE_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java11", + "11", + BuildIntegJavaBase.USING_GRADLEW_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java11", + "11", + BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java11", + "11", + BuildIntegJavaBase.USING_MAVEN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, + BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, + ), + ( + "java17", + "17", + BuildIntegJavaBase.USING_GRADLE_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java17", + "17", + BuildIntegJavaBase.USING_GRADLEW_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java17", + "17", + BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java17", + "17", + BuildIntegJavaBase.USING_MAVEN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, + BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, + ), + ] + ) + @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) + def test_building_java_in_container( + self, runtime, runtime_version, code_path, expected_files, expected_dependencies + ): + self._test_with_building_java( + runtime, + os.path.join(code_path, runtime_version), + expected_files, + expected_dependencies, + "use_container", + self.test_data_path, + ) + + @parameterized.expand( + [ + ( + "java21", + "21", + BuildIntegJavaBase.USING_GRADLE_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java21", + "21", + BuildIntegJavaBase.USING_GRADLEW_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java21", + "21", + BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java21", + "21", + BuildIntegJavaBase.USING_MAVEN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, + BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, + ), + ] + ) + @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) + @pytest.mark.al2023 + def test_building_java_in_container_al2023( + self, runtime, runtime_version, code_path, expected_files, expected_dependencies + ): + self._test_with_building_java( + runtime, + os.path.join(code_path, runtime_version), + expected_files, + expected_dependencies, + "use_container", + self.test_data_path, + ) + + @parameterized.expand( + [ + ( + "java8.al2", + "8", + BuildIntegJavaBase.USING_GRADLE_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java8.al2", + "8", + BuildIntegJavaBase.USING_GRADLEW_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java8.al2", + "8", + BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java8.al2", + "8", + BuildIntegJavaBase.USING_MAVEN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, + BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, + ), + ( + "java11", + "11", + BuildIntegJavaBase.USING_GRADLE_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java11", + "11", + BuildIntegJavaBase.USING_GRADLEW_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java11", + "11", + BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java11", + "11", + BuildIntegJavaBase.USING_MAVEN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, + BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, + ), + ( + "java17", + "17", + BuildIntegJavaBase.USING_GRADLE_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java17", + "17", + BuildIntegJavaBase.USING_GRADLEW_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java17", + "17", + BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java17", + "17", + BuildIntegJavaBase.USING_MAVEN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, + BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, + ), + ( + "java21", + "21", + BuildIntegJavaBase.USING_GRADLE_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java21", + "21", + BuildIntegJavaBase.USING_GRADLEW_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java21", + "21", + BuildIntegJavaBase.USING_GRADLE_KOTLIN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_GRADLE, + BuildIntegJavaBase.EXPECTED_GRADLE_DEPENDENCIES, + ), + ( + "java21", + "21", + BuildIntegJavaBase.USING_MAVEN_PATH, + BuildIntegJavaBase.EXPECTED_FILES_PROJECT_MANIFEST_MAVEN, + BuildIntegJavaBase.EXPECTED_MAVEN_DEPENDENCIES, + ), + ] + ) + def test_building_java_in_process(self, runtime, runtime_version, code_path, expected_files, expected_dependencies): + self._test_with_building_java( + runtime, + os.path.join(code_path, runtime_version), + expected_files, + expected_dependencies, + False, + self.test_data_path, + ) diff --git a/tests/integration/buildcmd/test_build_cmd_node.py b/tests/integration/buildcmd/test_build_cmd_node.py new file mode 100644 index 00000000000..b649f87017c --- /dev/null +++ b/tests/integration/buildcmd/test_build_cmd_node.py @@ -0,0 +1,176 @@ +import logging +from unittest import skipIf +from parameterized import parameterized, parameterized_class +import pytest + +from tests.testing_utils import ( + IS_WINDOWS, + RUNNING_ON_CI, + RUNNING_TEST_FOR_MASTER_ON_CI, + RUN_BY_CANARY, + CI_OVERRIDE, +) +from tests.integration.buildcmd.build_integ_base import ( + BuildIntegNodeBase, + BuildIntegEsbuildBase, +) + + +LOG = logging.getLogger(__name__) + +# SAR tests require credentials. This is to skip running the test where credentials are not available. +SKIP_SAR_TESTS = RUNNING_ON_CI and RUNNING_TEST_FOR_MASTER_ON_CI and not RUN_BY_CANARY + + +@pytest.mark.nodejs +class TestBuildCommand_NodeFunctions_With_External_Manifest(BuildIntegNodeBase): + CODE_URI = "Node_without_manifest" + TEST_INVOKE = True + MANIFEST_PATH = "npm_manifest/package.json" + + @parameterized.expand( + [ + ("nodejs16.x",), + ("nodejs18.x",), + ] + ) + def test_building_default_package_json(self, runtime): + + self._test_with_default_package_json(runtime, False, self.test_data_path) + + @parameterized.expand( + [ + ("nodejs20.x",), + ] + ) + @pytest.mark.al2023 + def test_building_default_package_json_al2023(self, runtime): + self._test_with_default_package_json(runtime, False, self.test_data_path) + + +class TestBuildCommand_EsbuildFunctions(BuildIntegEsbuildBase): + template = "template_with_metadata_esbuild.yaml" + + @parameterized.expand( + [ + ("nodejs18.x", "Esbuild/Node", {"main.js", "main.js.map"}, "main.lambdaHandler", "use_container", "x86_64"), + ( + "nodejs18.x", + "Esbuild/TypeScript", + {"app.js", "app.js.map"}, + "app.lambdaHandler", + "use_container", + "x86_64", + ), + ] + ) + def test_building_default_package_json( + self, runtime, code_uri, expected_files, handler, use_container, architecture + ): + self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, architecture) + + @parameterized.expand( + [ + ("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"), + ] + ) + @pytest.mark.al2023 + def test_building_default_package_json_al2023( + self, runtime, code_uri, expected_files, handler, use_container, architecture + ): + + self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, architecture) + + +class TestBuildCommand_EsbuildFunctions_With_External_Manifest(BuildIntegEsbuildBase): + template = "template_with_metadata_esbuild.yaml" + MANIFEST_PATH = "Esbuild/npm_manifest/package.json" + + @parameterized.expand( + [ + ( + "nodejs20.x", + "Esbuild/Node_without_manifest", + {"main.js", "main.js.map"}, + "main.lambdaHandler", + False, + "x86_64", + ), + ( + "nodejs20.x", + "Esbuild/TypeScript_without_manifest", + {"app.js", "app.js.map"}, + "app.lambdaHandler", + False, + "x86_64", + ), + ] + ) + @pytest.mark.al2023 + def test_building_default_package_json( + self, runtime, code_uri, expected_files, handler, use_container, architecture + ): + self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, architecture) + + +@skipIf( + ((IS_WINDOWS and RUNNING_ON_CI) and not CI_OVERRIDE), + "Skip build tests on windows when running in CI unless overridden", +) +@parameterized_class( + ("template",), + [ + ("esbuild_templates/template_with_metadata_node_options.yaml",), + ("esbuild_templates/template_with_metadata_global_node_options.yaml",), + ], +) +class TestBuildCommand_EsbuildFunctionProperties(BuildIntegEsbuildBase): + @parameterized.expand( + [ + ("nodejs16.x", "../Esbuild/TypeScript", "app.lambdaHandler", "x86_64"), + ("nodejs18.x", "../Esbuild/TypeScript", "app.lambdaHandler", "x86_64"), + ("nodejs16.x", "../Esbuild/TypeScript", "nested/function/app.lambdaHandler", "x86_64"), + ("nodejs18.x", "../Esbuild/TypeScript", "nested/function/app.lambdaHandler", "x86_64"), + ] + ) + def test_environment_generates_sourcemap(self, runtime, code_uri, handler, architecture): + overrides = { + "runtime": runtime, + "code_uri": code_uri, + "handler": handler, + "architecture": architecture, + } + self._test_with_various_properties(overrides, runtime) + + @parameterized.expand( + [ + ("nodejs20.x", "../Esbuild/TypeScript", "app.lambdaHandler", "x86_64"), + ("nodejs20.x", "../Esbuild/TypeScript", "nested/function/app.lambdaHandler", "x86_64"), + ] + ) + @pytest.mark.al2023 + def test_environment_generates_sourcemap_al2023(self, runtime, code_uri, handler, architecture): + overrides = { + "runtime": runtime, + "code_uri": code_uri, + "handler": handler, + "architecture": architecture, + } + self._test_with_various_properties(overrides, runtime) + + +class TestBuildCommand_NodeFunctions_With_Specified_Architecture(BuildIntegNodeBase): + template = "template_with_architecture.yaml" + + @parameterized.expand( + [ + ("nodejs16.x", False, "x86_64"), + ("nodejs18.x", False, "x86_64"), + ("nodejs16.x", "use_container", "x86_64"), + ("nodejs18.x", "use_container", "x86_64"), + ("nodejs20.x", False, "x86_64"), + ] + ) + 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 new file mode 100644 index 00000000000..e072b938a6a --- /dev/null +++ b/tests/integration/buildcmd/test_build_cmd_provided.py @@ -0,0 +1,129 @@ +import logging +import os +import pytest +from parameterized import parameterized, parameterized_class + +from tests.testing_utils import ( + RUNNING_ON_CI, + RUNNING_TEST_FOR_MASTER_ON_CI, + RUN_BY_CANARY, + SKIP_DOCKER_TESTS, + SKIP_DOCKER_BUILD, + SKIP_DOCKER_MESSAGE, +) +from tests.integration.buildcmd.build_integ_base import ( + BuildIntegProvidedBase, +) + + +LOG = logging.getLogger(__name__) + +# SAR tests require credentials. This is to skip running the test where credentials are not available. +SKIP_SAR_TESTS = RUNNING_ON_CI and RUNNING_TEST_FOR_MASTER_ON_CI and not RUN_BY_CANARY + + +@parameterized_class( + ("template", "is_nested_parent"), + [ + (os.path.join("nested-parent", "template-parent.yaml"), "is_nested_parent"), + ("template.yaml", False), + ], +) +@pytest.mark.provided +class TestBuildCommand_ProvidedFunctions(BuildIntegProvidedBase): + # Test Suite for runtime: provided and where selection of the build workflow is implicitly makefile builder + # if the makefile is present. + @parameterized.expand( + [ + ("provided", False, None), + ("provided", "use_container", "Makefile-container"), + ("provided.al2", False, None), + ("provided.al2", "use_container", "Makefile-container"), + ] + ) + def test_building_Makefile(self, runtime, use_container, manifest): + if use_container: + if SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: + self.skipTest(SKIP_DOCKER_MESSAGE) + self._test_with_Makefile(runtime, use_container, manifest) + + @parameterized.expand( + [ + ("provided.al2023", False, None), + ("provided.al2023", "use_container", "Makefile-container"), + ] + ) + @pytest.mark.al2023 + def test_building_Makefile_al2023(self, runtime, use_container, manifest): + if use_container: + if SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD: + self.skipTest(SKIP_DOCKER_MESSAGE) + self._test_with_Makefile(runtime, use_container, manifest) + + +@parameterized_class( + ("template", "is_nested_parent"), + [ + (os.path.join("nested-parent", "template-parent.yaml"), "is_nested_parent"), + ("template.yaml", False), + ], +) +class TestBuildCommand_ProvidedFunctions_With_Specified_Architecture(BuildIntegProvidedBase): + # Test Suite for runtime: provided and where selection of the build workflow is implicitly makefile builder + # if the makefile is present. + @parameterized.expand( + [ + ("provided", False, None, "x86_64"), + ("provided", "use_container", "Makefile-container", "x86_64"), + ("provided.al2", False, None, "x86_64"), + ("provided.al2", "use_container", "Makefile-container", "x86_64"), + ] + ) + def test_building_Makefile(self, runtime, use_container, manifest, architecture): + self._test_with_Makefile(runtime, use_container, manifest, architecture) + + @parameterized.expand( + [ + ("provided.al2023", False, None, "x86_64"), + ("provided.al2023", "use_container", "Makefile-container", "x86_64"), + ] + ) + @pytest.mark.al2023 + def test_building_Makefile_al2023(self, runtime, use_container, manifest, architecture): + self._test_with_Makefile(runtime, use_container, manifest, architecture) + + +@parameterized_class( + ("template", "code_uri", "is_nested_parent"), + [ + ("custom_build_with_custom_root_project_path.yaml", "empty_src_code", False), + ("custom_build_with_custom_make_file_path.yaml", "provided_src_code_without_makefile", False), + ("custom_build_with_custom_working_dir.yaml", "custom_working_dir_src_code", False), + ("custom_build_with_custom_root_project_path_and_custom_makefile_path.yaml", "empty_src_code", False), + ( + "custom_build_with_custom_root_project_path_custom_makefile_path_and_custom_working_dir.yaml", + "empty_src_code", + False, + ), + ], +) +class TestBuildCommand_ProvidedFunctionsWithCustomMetadata(BuildIntegProvidedBase): + # Test Suite for runtime: provided and where selection of the build workflow is implicitly makefile builder + # if the makefile is present. + @parameterized.expand( + [ + ("provided", False, None), + ("provided.al2", False, None), + ] + ) + def test_building_Makefile(self, runtime, use_container, manifest): + self._test_with_Makefile(runtime, use_container, manifest) + + @parameterized.expand( + [ + ("provided.al2023", False, None), + ] + ) + @pytest.mark.al2023 + def test_building_Makefile_al2023(self, runtime, use_container, manifest): + self._test_with_Makefile(runtime, use_container, manifest) diff --git a/tests/integration/buildcmd/test_build_cmd_python.py b/tests/integration/buildcmd/test_build_cmd_python.py new file mode 100644 index 00000000000..14ed5400824 --- /dev/null +++ b/tests/integration/buildcmd/test_build_cmd_python.py @@ -0,0 +1,520 @@ +import logging +from typing import Set +from unittest import skipIf +from uuid import uuid4 + +import pytest +from parameterized import parameterized, parameterized_class + +from tests.testing_utils import ( + IS_WINDOWS, + RUNNING_ON_CI, + RUNNING_TEST_FOR_MASTER_ON_CI, + RUN_BY_CANARY, + CI_OVERRIDE, + run_command, + SKIP_DOCKER_TESTS, + SKIP_DOCKER_BUILD, + SKIP_DOCKER_MESSAGE, +) +from tests.integration.buildcmd.build_integ_base import ( + BuildIntegBase, + BuildIntegPythonBase, +) + + +LOG = logging.getLogger(__name__) + +# SAR tests require credentials. This is to skip running the test where credentials are not available. +SKIP_SAR_TESTS = RUNNING_ON_CI and RUNNING_TEST_FOR_MASTER_ON_CI and not RUN_BY_CANARY + + +@skipIf( + # Hits public ECR pull limitation, move it to canary tests + (not RUN_BY_CANARY and not CI_OVERRIDE), + "Skip build tests on windows when running in CI unless overridden", +) +@pytest.mark.python +class TestBuildCommand_PythonFunctions_Images(BuildIntegBase): + template = "template_image.yaml" + + EXPECTED_FILES_PROJECT_MANIFEST: Set[str] = set() + + FUNCTION_LOGICAL_ID_IMAGE = "ImageFunction" + + def _test_default_requirements_wrapper(self, runtime, dockerfile): + tag = uuid4().hex + overrides = { + "Runtime": runtime, + "Handler": "main.handler", + "DockerFile": dockerfile, + "Tag": tag, + } + cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) + + command_result = run_command(cmdlist, cwd=self.working_dir) + self.assertEqual(command_result.process.returncode, 0) + + self._verify_image_build_artifact( + self.built_template, + self.FUNCTION_LOGICAL_ID_IMAGE, + "ImageUri", + f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{tag}", + ) + + expected = {"pi": "3.14"} + self._verify_invoke_built_function( + self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected + ) + + @parameterized.expand( + [ + *[(runtime, "Dockerfile") for runtime in ["3.8", "3.9", "3.10", "3.11"]], + *[(runtime, "Dockerfile.production") for runtime in ["3.8", "3.9", "3.10", "3.11"]], + ] + ) + def test_with_default_requirements(self, runtime, dockerfile): + self._test_default_requirements_wrapper(runtime, dockerfile) + + @parameterized.expand( + [ + *[(runtime, "Dockerfile") for runtime in ["3.12"]], + *[(runtime, "Dockerfile.production") for runtime in ["3.12"]], + ] + ) + @pytest.mark.al2023 + def test_with_default_requirements_al2023(self, runtime, dockerfile): + self._test_default_requirements_wrapper(runtime, dockerfile) + + def test_intermediate_container_deleted(self): + _tag = uuid4().hex + overrides = { + "Runtime": "3.9", + "Handler": "main.handler", + "DockerFile": "Dockerfile", + "Tag": _tag, + } + cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) + + _num_of_containers_before_build = self.get_number_of_created_containers() + command_result = run_command(cmdlist, cwd=self.working_dir) + self.assertEqual(command_result.process.returncode, 0) + _num_of_containers_after_build = self.get_number_of_created_containers() + + self._verify_image_build_artifact( + self.built_template, + self.FUNCTION_LOGICAL_ID_IMAGE, + "ImageUri", + f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{_tag}", + ) + + expected = {"pi": "3.14"} + self._verify_invoke_built_function( + self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected + ) + + self.assertEqual( + _num_of_containers_before_build, _num_of_containers_after_build, "Intermediate containers are not removed" + ) + + +@skipIf( + # Hits public ECR pull limitation, move it to canary tests + (not RUN_BY_CANARY and not CI_OVERRIDE), + "Skip build tests on windows when running in CI unless overridden", +) +@pytest.mark.python +class TestBuildCommand_PythonFunctions_ImagesWithSharedCode(BuildIntegBase): + template = "template_images_with_shared_code.yaml" + + EXPECTED_FILES_PROJECT_MANIFEST: Set[str] = set() + + FUNCTION_LOGICAL_ID_IMAGE = "ImageFunction" + + def _test_default_requirements_wrapper(self, runtime, dockerfile, expected): + tag = uuid4().hex + overrides = { + "Runtime": runtime, + "Handler": "main.handler", + "DockerFile": dockerfile, + "Tag": tag, + } + + cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) + + command_result = run_command(cmdlist, cwd=self.working_dir) + self.assertEqual(command_result.process.returncode, 0) + + self._verify_image_build_artifact( + self.built_template, + self.FUNCTION_LOGICAL_ID_IMAGE, + "ImageUri", + f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{tag}", + ) + + self._verify_invoke_built_function( + self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected + ) + + @parameterized.expand( + [ + *[(runtime, "feature_phi/Dockerfile", {"phi": "1.62"}) for runtime in ["3.8", "3.9", "3.10", "3.11"]], + *[(runtime, "feature_pi/Dockerfile", {"pi": "3.14"}) for runtime in ["3.8", "3.9", "3.10", "3.11"]], + ] + ) + def test_with_default_requirements(self, runtime, dockerfile, expected): + self._test_default_requirements_wrapper(runtime, dockerfile, expected) + + @parameterized.expand( + [ + *[(runtime, "feature_phi/Dockerfile", {"phi": "1.62"}) for runtime in ["3.12"]], + *[(runtime, "feature_pi/Dockerfile", {"pi": "3.14"}) for runtime in ["3.12"]], + ] + ) + @pytest.mark.al2023 + def test_with_default_requirements_al2023(self, runtime, dockerfile, expected): + self._test_default_requirements_wrapper(runtime, dockerfile, expected) + + @parameterized.expand( + [ + ("feature_phi/Dockerfile", {"phi": "1.62"}), + ("feature_pi/Dockerfile", {"pi": "3.14"}), + ] + ) + def test_intermediate_container_deleted(self, dockerfile, expected): + _tag = uuid4().hex + overrides = { + "Runtime": "3.9", + "Handler": "main.handler", + "DockerFile": dockerfile, + "Tag": _tag, + } + cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) + + _num_of_containers_before_build = self.get_number_of_created_containers() + command_result = run_command(cmdlist, cwd=self.working_dir) + self.assertEqual(command_result.process.returncode, 0) + _num_of_containers_after_build = self.get_number_of_created_containers() + + self._verify_image_build_artifact( + self.built_template, + self.FUNCTION_LOGICAL_ID_IMAGE, + "ImageUri", + f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{_tag}", + ) + + self._verify_invoke_built_function( + self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected + ) + + self.assertEqual( + _num_of_containers_before_build, _num_of_containers_after_build, "Intermediate containers are not removed" + ) + + @parameterized.expand( + [ + ("feature_phi\\Dockerfile", {"phi": "1.62"}), + ("feature_pi\\Dockerfile", {"pi": "3.14"}), + ] + ) + @skipIf(not IS_WINDOWS, "Skipping passing Windows path for dockerfile path on non Windows platform") + def test_windows_dockerfile_present_sub_dir(self, dockerfile, expected): + _tag = uuid4().hex + overrides = { + "Runtime": "3.9", + "Handler": "main.handler", + "DockerFile": dockerfile, + "Tag": _tag, + } + cmdlist = self.get_command_list(use_container=False, parameter_overrides=overrides) + + command_result = run_command(cmdlist, cwd=self.working_dir) + self.assertEqual(command_result.process.returncode, 0) + + self._verify_image_build_artifact( + self.built_template, + self.FUNCTION_LOGICAL_ID_IMAGE, + "ImageUri", + f"{self.FUNCTION_LOGICAL_ID_IMAGE.lower()}:{_tag}", + ) + + self._verify_invoke_built_function( + self.built_template, self.FUNCTION_LOGICAL_ID_IMAGE, self._make_parameter_override_arg(overrides), expected + ) + + +# @skipIf( +# # Hits public ECR pull limitation, move it to canary tests +# ((not RUN_BY_CANARY) or (IS_WINDOWS and RUNNING_ON_CI) and not CI_OVERRIDE), +# "Skip build tests on windows when running in CI unless overridden", +# ) +@parameterized_class( + ("template", "prop"), + [ + ("template_local_prebuilt_image.yaml", "ImageUri"), + ("template_cfn_local_prebuilt_image.yaml", "Code.ImageUri"), + ], +) +@parameterized_class( + ( + "runtime", + "codeuri", + ), + [ + ("python3.8", "Python"), + ("python3.9", "Python"), + ("python3.10", "Python"), + ("python3.11", "Python"), + ("python3.8", "PythonPEP600"), + ("python3.9", "PythonPEP600"), + ("python3.10", "PythonPEP600"), + ("python3.11", "PythonPEP600"), + ], +) +class TestBuildCommand_PythonFunctions_WithoutDocker(BuildIntegPythonBase): + template = "template.yaml" + FUNCTION_LOGICAL_ID = "Function" + overrides = True + runtime = "python3.9" + codeuri = "Python" + check_function_only = False + use_container = False + prop = "CodeUri" + + def test_with_default_requirements(self): + self._test_with_default_requirements( + self.runtime, + self.codeuri, + self.use_container, + self.test_data_path, + do_override=self.overrides, + check_function_only=self.check_function_only, + ) + + +@parameterized_class( + ("template", "prop"), + [ + ("template_local_prebuilt_image.yaml", "ImageUri"), + ("template_cfn_local_prebuilt_image.yaml", "Code.ImageUri"), + ], +) +@parameterized_class( + ( + "runtime", + "codeuri", + ), + [ + ("python3.12", "Python"), + ("python3.12", "PythonPEP600"), + ], +) +@pytest.mark.al2023 +class TestBuildCommand_PythonFunctions_WithoutDocker_al2023(BuildIntegPythonBase): + template = "template.yaml" + FUNCTION_LOGICAL_ID = "Function" + overrides = True + runtime = "python3.9" + codeuri = "Python" + check_function_only = False + use_container = False + prop = "CodeUri" + + def test_with_default_requirements(self): + self._test_with_default_requirements( + self.runtime, + self.codeuri, + self.use_container, + self.test_data_path, + do_override=self.overrides, + check_function_only=self.check_function_only, + ) + + +@skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) +class TestBuildCommand_PythonFunctions_WithDocker(BuildIntegPythonBase): + template = "template.yaml" + FUNCTION_LOGICAL_ID = "Function" + overrides = True + codeuri = "Python" + use_container = "use_container" + check_function_only = False + prop = "CodeUri" + + @parameterized.expand( + [ + ("python3.8",), + ("python3.9",), + ("python3.10",), + ("python3.11",), + ] + ) + def test_with_default_requirements(self, runtime): + self._test_with_default_requirements( + runtime, + self.codeuri, + self.use_container, + self.test_data_path, + do_override=self.overrides, + check_function_only=self.check_function_only, + ) + + @parameterized.expand( + [ + ("python3.12",), + ] + ) + @pytest.mark.al2023 + def test_with_default_requirements_al2023(self, runtime): + self._test_with_default_requirements( + runtime, + self.codeuri, + self.use_container, + self.test_data_path, + do_override=self.overrides, + check_function_only=self.check_function_only, + ) + + +@skipIf( + # Hits public ECR pull limitation, move it to canary tests + SKIP_DOCKER_TESTS, + "Skip build tests that requires Docker in CI environment", +) +@parameterized_class( + ( + "template", + "FUNCTION_LOGICAL_ID", + "overrides", + "runtime", + "codeuri", + "use_container", + "check_function_only", + "prop", + ), + [ + ( + "cdk_v1_synthesized_template_zip_image_functions.json", + "RandomCitiesFunction5C47A2B8", + False, + None, + None, + False, + True, + "Code", + ), + ], +) +class TestBuildCommand_PythonFunctions_CDK(TestBuildCommand_PythonFunctions_WithoutDocker): + use_container = False + + def test_cdk_app_with_default_requirements(self): + self._test_with_default_requirements( + self.runtime, + self.codeuri, + self.use_container, + self.test_data_path, + do_override=self.overrides, + check_function_only=self.check_function_only, + ) + + +@skipIf( + # Hits public ECR pull limitation, move it to canary tests + SKIP_DOCKER_TESTS, + "Skip build tests that requires Docker in CI environment", +) +@parameterized_class( + ( + "template", + "FUNCTION_LOGICAL_ID", + "overrides", + "use_container", + "prop", + ), + [ + ( + "cdk_v2_synthesized_template_image_function_shared_code.json", + "TestLambdaFunctionC089708A", + False, + False, + "Code.ImageUri", + ), + ], +) +class TestBuildCommandCDKPythonImageFunctionSharedCode(BuildIntegPythonBase): + def test_cdk_app_with_default_requirements(self): + expected = "Hello World" + cmdlist = self.get_command_list(use_container=self.use_container) + command_result = run_command(cmdlist, cwd=self.working_dir) + self.assertEqual(command_result.process.returncode, 0) + + self._verify_image_build_artifact( + self.built_template, + self.FUNCTION_LOGICAL_ID, + self.prop, + f"{self.FUNCTION_LOGICAL_ID.lower()}:latest", + ) + + self._verify_invoke_built_function(self.built_template, self.FUNCTION_LOGICAL_ID, {}, expected) + + +class TestBuildCommand_PythonFunctions_With_Specified_Architecture(BuildIntegPythonBase): + template = "template_with_architecture.yaml" + + @parameterized.expand( + [ + ("python3.8", "Python", False, "x86_64"), + ("python3.9", "Python", False, "x86_64"), + ("python3.10", "Python", False, "x86_64"), + ("python3.11", "Python", False, "x86_64"), + ("python3.8", "PythonPEP600", False, "x86_64"), + ("python3.9", "PythonPEP600", False, "x86_64"), + ("python3.10", "PythonPEP600", False, "x86_64"), + ("python3.11", "PythonPEP600", False, "x86_64"), + ("python3.8", "Python", "use_container", "x86_64"), + ("python3.9", "Python", "use_container", "x86_64"), + ("python3.10", "Python", "use_container", "x86_64"), + ("python3.11", "Python", "use_container", "x86_64"), + ] + ) + def test_with_default_requirements(self, runtime, codeuri, use_container, architecture): + + self._test_with_default_requirements( + runtime, codeuri, use_container, self.test_data_path, architecture=architecture + ) + + @parameterized.expand( + [ + ("python3.12", "Python", False, "x86_64"), + ("python3.12", "PythonPEP600", False, "x86_64"), + ("python3.12", "Python", "use_container", "x86_64"), + ] + ) + @pytest.mark.al2023 + def test_with_default_requirements_al2023(self, runtime, codeuri, use_container, architecture): + + self._test_with_default_requirements( + runtime, codeuri, use_container, self.test_data_path, architecture=architecture + ) + + def test_invalid_architecture(self): + overrides = {"Runtime": "python3.11", "Architectures": "fake"} + cmdlist = self.get_command_list(parameter_overrides=overrides) + process_execute = run_command(cmdlist, cwd=self.working_dir) + + self.assertEqual(1, process_execute.process.returncode) + + self.assertIn("Build Failed", str(process_execute.stdout)) + self.assertIn("Architecture fake is not supported", str(process_execute.stderr)) + + +class TestBuildCommand_ErrorCases(BuildIntegBase): + def test_unsupported_runtime(self): + overrides = {"Runtime": "unsupportedpython", "CodeUri": "Python"} + cmdlist = self.get_command_list(parameter_overrides=overrides) + + process_execute = run_command(cmdlist, cwd=self.working_dir) + self.assertEqual(1, process_execute.process.returncode) + + self.assertIn("Build Failed", str(process_execute.stdout)) diff --git a/tests/testing_utils.py b/tests/testing_utils.py index 37f04972cf3..a8b0d56ab56 100644 --- a/tests/testing_utils.py +++ b/tests/testing_utils.py @@ -79,10 +79,10 @@ def run_command(command_list, cwd=None, env=None, timeout=TIMEOUT) -> CommandRes raise -def run_command_with_input(command_list, stdin_input, timeout=TIMEOUT, cwd=None) -> CommandResult: +def run_command_with_input(command_list, stdin_input, timeout=TIMEOUT, cwd=None, env=None) -> CommandResult: LOG.info("Running command: %s", " ".join(command_list)) LOG.info("With input: %s", stdin_input) - process_execute = Popen(command_list, cwd=cwd, stdout=PIPE, stderr=PIPE, stdin=PIPE) + process_execute = Popen(command_list, cwd=cwd, env=env, stdout=PIPE, stderr=PIPE, stdin=PIPE) try: stdout_data, stderr_data = process_execute.communicate(stdin_input, timeout=timeout) LOG.info(f"Stdout: {stdout_data.decode('utf-8')}") @@ -296,31 +296,6 @@ def __exit__(self, *args): self.clean() -RUNTIME_NOT_SUPPORTED_BY_DOCKER_MSG = "Runtime is not supported the installed Docker version." - - -def runtime_supported_by_docker(runtime: str) -> bool: - """ - To determine if a test ca on runtime and docker_version, in case the test is run in an environment with a very old version of docker - - Container test for AL2023-based runtimes (i.e. provided.al2023, java21, python3.12 and nodejs20.x as of 2023-12-01) requires docker 20.10.10+ - See: https://docs.docker.com/engine/release-notes/20.10/#201010 - - """ - al2023_based_runtimes = { - "provided.al2023", - "nodejs20.x", - "java21", - "python3.12", - "dotnet8", - "ruby3.3", - } - min_docker_version = "20.10.10" - return runtime not in al2023_based_runtimes or ( - runtime in al2023_based_runtimes and _version_gte(get_docker_version(), min_docker_version) - ) - - def _version_gte(version1: str, version2: str) -> bool: v1 = packaging.version.parse(version1) v2 = packaging.version.parse(version2)