diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc3a0503f..60d168e24 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,7 +80,7 @@ jobs: run: make func-test node-integration: - name: ${{ matrix.os }} / ${{ matrix.python }} / node / npm ${{ matrix.npm }}.x + name: ${{ matrix.os }} / ${{ matrix.python }} / node ${{ matrix.nodejs }} / npm ${{ matrix.npm }}.x if: github.repository_owner == 'aws' runs-on: ${{ matrix.os }} strategy: @@ -90,17 +90,16 @@ jobs: - ubuntu-latest - windows-latest python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - "3.13" - - "3.14" npm: - 8 - 9 - 10 - 11 + nodejs: + - 20 + - 22 + - 24 steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 @@ -108,7 +107,7 @@ jobs: python-version: ${{ matrix.python }} - uses: actions/setup-node@v6 with: - node-version: 22 + node-version: ${{ matrix.nodejs }} - if: ${{ matrix.npm }} run: npm install -g npm@${{ matrix.npm }} - run: npm --version @@ -116,7 +115,7 @@ jobs: - run: pytest -vv tests/integration/workflows/nodejs_npm node-esbuild-integration: - name: ${{ matrix.os }} / ${{ matrix.python }} / esbuild / npm ${{ matrix.npm }}.x + name: ${{ matrix.os }} / ${{ matrix.python }} / esbuild / node ${{ matrix.nodejs }} / npm ${{ matrix.npm }}.x if: github.repository_owner == 'aws' runs-on: ${{ matrix.os }} strategy: @@ -126,17 +125,16 @@ jobs: - ubuntu-latest - windows-latest python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - "3.13" - - "3.14" npm: - 8 - 9 - 10 - 11 + nodejs: + - 20 + - 22 + - 24 steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 @@ -144,7 +142,7 @@ jobs: python-version: ${{ matrix.python }} - uses: actions/setup-node@v6 with: - node-version: 22 + node-version: ${{ matrix.nodejs }} - if: ${{ matrix.npm }} run: npm install -g npm@${{ matrix.npm }} - run: npm --version @@ -162,12 +160,7 @@ jobs: - ubuntu-latest - windows-latest python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - "3.13" - - "3.14" steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 @@ -190,12 +183,7 @@ jobs: - ubuntu-latest - windows-latest python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - "3.13" - - "3.14" java: - "21" - "25" @@ -224,12 +212,7 @@ jobs: - ubuntu-latest - windows-latest python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - "3.13" - - "3.14" java: - "21" - "25" @@ -256,12 +239,7 @@ jobs: - ubuntu-latest - windows-latest python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - "3.13" - - "3.14" steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 @@ -310,12 +288,7 @@ jobs: - ubuntu-latest - windows-latest python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - "3.13" - - "3.14" steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 @@ -338,12 +311,7 @@ jobs: - ubuntu-latest - windows-latest python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - "3.13" - - "3.14" steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 @@ -368,12 +336,7 @@ jobs: - ubuntu-latest - windows-latest python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - "3.13" - - "3.14" rust: - stable steps: diff --git a/aws_lambda_builders/supported_runtimes.py b/aws_lambda_builders/supported_runtimes.py new file mode 100644 index 000000000..5cf237bbe --- /dev/null +++ b/aws_lambda_builders/supported_runtimes.py @@ -0,0 +1,67 @@ +""" +Centralized definition of supported AWS Lambda runtimes. + +This module provides a single source of truth for all supported runtimes +across the aws-lambda-builders package. +""" + +from aws_lambda_builders.architecture import ARM64, X86_64 + +# Node.js runtimes +NODEJS_RUNTIMES = [ + "nodejs16.x", + "nodejs18.x", + "nodejs20.x", + "nodejs22.x", + "nodejs24.x", +] + +# Python runtimes +PYTHON_RUNTIMES = [ + "python3.8", + "python3.9", + "python3.10", + "python3.11", + "python3.12", + "python3.13", + "python3.14", +] + +# Ruby runtimes +RUBY_RUNTIMES = [ + "ruby3.2", + "ruby3.3", + "ruby3.4", +] + +# Java runtimes +JAVA_RUNTIMES = [ + "java8", + "java11", + "java17", + "java21", + "java25", +] + +# Go runtimes +GO_RUNTIMES = [ + "go1.x", +] + +# .NET runtimes +DOTNET_RUNTIMES = [ + "dotnet6", + "dotnet8", +] + +# Custom runtimes +CUSTOM_RUNTIMES = ["provided", "provided.al2", "provided.al2023"] + +# Combined list of all supported runtimes +ALL_RUNTIMES = ( + NODEJS_RUNTIMES + PYTHON_RUNTIMES + RUBY_RUNTIMES + JAVA_RUNTIMES + GO_RUNTIMES + DOTNET_RUNTIMES + CUSTOM_RUNTIMES +) + +# Runtime to architecture mapping +# All current runtimes support both ARM64 and X86_64 +RUNTIME_ARCHITECTURES = {runtime: [ARM64, X86_64] for runtime in ALL_RUNTIMES} diff --git a/aws_lambda_builders/validator.py b/aws_lambda_builders/validator.py index d8b0ac9ab..4951588d2 100644 --- a/aws_lambda_builders/validator.py +++ b/aws_lambda_builders/validator.py @@ -4,37 +4,11 @@ import logging -from aws_lambda_builders.architecture import ARM64, X86_64 from aws_lambda_builders.exceptions import UnsupportedArchitectureError, UnsupportedRuntimeError +from aws_lambda_builders.supported_runtimes import RUNTIME_ARCHITECTURES as SUPPORTED_RUNTIMES LOG = logging.getLogger(__name__) -SUPPORTED_RUNTIMES = { - "nodejs16.x": [ARM64, X86_64], - "nodejs18.x": [ARM64, X86_64], - "nodejs20.x": [ARM64, X86_64], - "nodejs22.x": [ARM64, X86_64], - "python3.8": [ARM64, X86_64], - "python3.9": [ARM64, X86_64], - "python3.10": [ARM64, X86_64], - "python3.11": [ARM64, X86_64], - "python3.12": [ARM64, X86_64], - "python3.13": [ARM64, X86_64], - "python3.14": [ARM64, X86_64], - "ruby3.2": [ARM64, X86_64], - "ruby3.3": [ARM64, X86_64], - "ruby3.4": [ARM64, X86_64], - "java8": [ARM64, X86_64], - "java11": [ARM64, X86_64], - "java17": [ARM64, X86_64], - "java21": [ARM64, X86_64], - "java25": [ARM64, X86_64], - "go1.x": [ARM64, X86_64], - "dotnet6": [ARM64, X86_64], - "dotnet8": [ARM64, X86_64], - "provided": [ARM64, X86_64], -} - class RuntimeValidator(object): def __init__(self, runtime, architecture): diff --git a/tests/integration/workflows/dotnet_clipackage/test_dotnet.py b/tests/integration/workflows/dotnet_clipackage/test_dotnet.py index 95743d112..2e85e902d 100644 --- a/tests/integration/workflows/dotnet_clipackage/test_dotnet.py +++ b/tests/integration/workflows/dotnet_clipackage/test_dotnet.py @@ -137,7 +137,7 @@ def test_with_defaults_file_arm64(self, runtime, version, test_project): @parameterized.expand( [ - ("dotnet6", "6.0", "CustomRuntime6"), + # ("dotnet6", "6.0", "CustomRuntime6"), ("dotnet8", "8.0", "CustomRuntime8"), ] ) diff --git a/tests/integration/workflows/nodejs_npm/test_nodejs_npm.py b/tests/integration/workflows/nodejs_npm/test_nodejs_npm.py index 1228e5b86..cc613b60b 100644 --- a/tests/integration/workflows/nodejs_npm/test_nodejs_npm.py +++ b/tests/integration/workflows/nodejs_npm/test_nodejs_npm.py @@ -1,3 +1,4 @@ +import itertools import logging import os import shutil @@ -9,6 +10,7 @@ from aws_lambda_builders.builder import LambdaBuilder from aws_lambda_builders.exceptions import WorkflowFailedError +from aws_lambda_builders.supported_runtimes import NODEJS_RUNTIMES from tests.testing_utils import read_link_without_junction_prefix logger = logging.getLogger("aws_lambda_builders.workflows.nodejs_npm.workflow") @@ -21,6 +23,13 @@ class TestNodejsNpmWorkflow(TestCase): TEST_DATA_FOLDER = os.path.join(os.path.dirname(__file__), "testdata") + # Use centralized Node.js runtimes + SUPPORTED_RUNTIMES = [(runtime,) for runtime in NODEJS_RUNTIMES] + + # Generate combinations of runtimes and lockfile types + LOCKFILE_TYPES = ["package-lock", "shrinkwrap", "package-lock-and-shrinkwrap"] + RUNTIME_LOCKFILE_COMBINATIONS = list(itertools.product(NODEJS_RUNTIMES, LOCKFILE_TYPES)) + def setUp(self): self.artifacts_dir = tempfile.mkdtemp() self.scratch_dir = tempfile.mkdtemp() @@ -41,7 +50,7 @@ def tearDown(self): shutil.rmtree(self.dependencies_dir) shutil.rmtree(self.temp_dir) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_without_dependencies(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "no-deps") @@ -57,7 +66,7 @@ def test_builds_project_without_dependencies(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_without_manifest(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "no-manifest") @@ -75,7 +84,7 @@ def test_builds_project_without_manifest(self, runtime): mock_warning.assert_called_once_with("package.json file not found. Continuing the build without dependencies.") self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_and_excludes_hidden_aws_sam(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "excluded-files") @@ -91,7 +100,7 @@ def test_builds_project_and_excludes_hidden_aws_sam(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_remote_dependencies(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "npm-deps") @@ -111,7 +120,7 @@ def test_builds_project_with_remote_dependencies(self, runtime): output_modules = set(os.listdir(os.path.join(self.artifacts_dir, "node_modules"))) self.assertEqual(expected_modules, output_modules) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_npmrc(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "npmrc") @@ -132,22 +141,7 @@ def test_builds_project_with_npmrc(self, runtime): output_modules = set(os.listdir(os.path.join(self.artifacts_dir, "node_modules"))) self.assertEqual(expected_modules, output_modules) - @parameterized.expand( - [ - ("nodejs16.x", "package-lock"), - ("nodejs18.x", "package-lock"), - ("nodejs20.x", "package-lock"), - ("nodejs22.x", "package-lock"), - ("nodejs16.x", "shrinkwrap"), - ("nodejs18.x", "shrinkwrap"), - ("nodejs20.x", "shrinkwrap"), - ("nodejs22.x", "shrinkwrap"), - ("nodejs16.x", "package-lock-and-shrinkwrap"), - ("nodejs18.x", "package-lock-and-shrinkwrap"), - ("nodejs20.x", "package-lock-and-shrinkwrap"), - ("nodejs22.x", "package-lock-and-shrinkwrap"), - ] - ) + @parameterized.expand(RUNTIME_LOCKFILE_COMBINATIONS) def test_builds_project_with_lockfile(self, runtime, dir_name): expected_files_common = {"package.json", "included.js", "node_modules"} expected_files_by_dir_name = { @@ -172,7 +166,7 @@ def test_builds_project_with_lockfile(self, runtime, dir_name): self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_fails_if_npm_cannot_resolve_dependencies(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "broken-deps") @@ -187,7 +181,7 @@ def test_fails_if_npm_cannot_resolve_dependencies(self, runtime): self.assertIn("No matching version found for aws-sdk@2.997.999", str(ctx.exception)) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_remote_dependencies_without_download_dependencies_with_dependencies_dir(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "npm-deps") @@ -205,7 +199,7 @@ def test_builds_project_with_remote_dependencies_without_download_dependencies_w output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_remote_dependencies_with_download_dependencies_and_dependencies_dir(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "npm-deps") @@ -235,7 +229,7 @@ def test_builds_project_with_remote_dependencies_with_download_dependencies_and_ output_dependencies_files = set(os.listdir(os.path.join(self.dependencies_dir))) self.assertNotIn(expected_dependencies_files, output_dependencies_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_remote_dependencies_without_download_dependencies_without_dependencies_dir( self, runtime ): @@ -256,7 +250,7 @@ def test_builds_project_with_remote_dependencies_without_download_dependencies_w output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_without_combine_dependencies(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "npm-deps") @@ -283,7 +277,7 @@ def test_builds_project_without_combine_dependencies(self, runtime): output_dependencies_files = set(os.listdir(os.path.join(self.dependencies_dir))) self.assertNotIn(expected_dependencies_files, output_dependencies_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_build_in_source_with_download_dependencies(self, runtime): source_dir = os.path.join(self.temp_testdata_dir, "npm-deps") @@ -312,7 +306,7 @@ def test_build_in_source_with_download_dependencies(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_build_in_source_with_removed_dependencies(self, runtime): # run a build with default requirements and confirm dependencies are downloaded source_dir = os.path.join(self.temp_testdata_dir, "npm-deps") @@ -352,7 +346,7 @@ def test_build_in_source_with_removed_dependencies(self, runtime): self.assertIn(".package-lock.json", set(os.listdir(source_node_modules))) self.assertNotIn("minimal-request-promise", set(os.listdir(source_node_modules))) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_build_in_source_with_download_dependencies_local_dependency(self, runtime): source_dir = os.path.join(self.temp_testdata_dir, "with-local-dependency") @@ -381,7 +375,7 @@ def test_build_in_source_with_download_dependencies_local_dependency(self, runti output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_build_in_source_with_download_dependencies_and_dependencies_dir(self, runtime): source_dir = os.path.join(self.temp_testdata_dir, "npm-deps") @@ -416,7 +410,7 @@ def test_build_in_source_with_download_dependencies_and_dependencies_dir(self, r output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_build_in_source_with_download_dependencies_and_dependencies_dir_without_combine_dependencies( self, runtime ): @@ -449,7 +443,7 @@ def test_build_in_source_with_download_dependencies_and_dependencies_dir_without output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_build_in_source_reuse_saved_dependencies_dir(self, runtime): source_dir = os.path.join(self.temp_testdata_dir, "npm-deps") @@ -478,7 +472,7 @@ def test_build_in_source_reuse_saved_dependencies_dir(self, runtime): self.artifacts_dir, self.scratch_dir, os.path.join(source_dir, "package.json"), - runtime="nodejs16.x", + runtime=runtime, build_in_source=True, dependencies_dir=self.dependencies_dir, download_dependencies=False, @@ -505,7 +499,7 @@ def test_build_in_source_reuse_saved_dependencies_dir(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_manifest_outside_root(self, runtime): base_dir = os.path.join(self.temp_testdata_dir, "manifest-outside-root") source_dir = os.path.join(base_dir, "src") @@ -528,7 +522,7 @@ def test_builds_project_with_manifest_outside_root(self, runtime): output_modules = set(os.listdir(os.path.join(self.artifacts_dir, "node_modules"))) self.assertEqual(expected_modules, output_modules) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_manifest_outside_root_with_reuse_saved_dependencies_dir(self, runtime): base_dir = os.path.join(self.temp_testdata_dir, "manifest-outside-root") source_dir = os.path.join(base_dir, "src") @@ -575,7 +569,7 @@ def test_builds_project_with_manifest_outside_root_with_reuse_saved_dependencies output_modules = set(os.listdir(os.path.join(self.artifacts_dir, "node_modules"))) self.assertEqual(expected_modules, output_modules) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_manifest_outside_root_with_dependencies_dir_and_not_combine(self, runtime): base_dir = os.path.join(self.temp_testdata_dir, "manifest-outside-root") source_dir = os.path.join(base_dir, "src") @@ -600,7 +594,7 @@ def test_builds_project_with_manifest_outside_root_with_dependencies_dir_and_not dependencies_dir_modules = set(os.listdir(os.path.join(self.dependencies_dir, "node_modules"))) self.assertEqual(expected_modules, dependencies_dir_modules) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_manifest_outside_root_with_dependencies_dir_and_combine(self, runtime): base_dir = os.path.join(self.temp_testdata_dir, "manifest-outside-root") source_dir = os.path.join(base_dir, "src") @@ -629,7 +623,7 @@ def test_builds_project_with_manifest_outside_root_with_dependencies_dir_and_com dependencies_dir_modules = set(os.listdir(os.path.join(self.dependencies_dir, "node_modules"))) self.assertEqual(expected_modules, dependencies_dir_modules) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_manifest_outside_root_and_local_dependencies(self, runtime): base_dir = os.path.join(self.temp_testdata_dir, "manifest-outside-root-with-local-dependency") source_dir = os.path.join(base_dir, "src") @@ -657,7 +651,7 @@ def test_builds_project_with_manifest_outside_root_and_local_dependencies(self, source_modules = set(os.listdir(os.path.join(source_dir, "node_modules"))) self.assertTrue(all(expected_module in source_modules for expected_module in expected_modules)) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_manifest_outside_root_and_local_dependencies_with_reuse_saved_dependencies_dir( self, runtime ): @@ -712,7 +706,7 @@ def test_builds_project_with_manifest_outside_root_and_local_dependencies_with_r source_modules = set(os.listdir(os.path.join(source_dir, "node_modules"))) self.assertTrue(all(expected_module in source_modules for expected_module in expected_modules)) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_manifest_outside_root_and_local_dependencies_with_dependencies_dir_and_not_combine( self, runtime ): @@ -744,7 +738,7 @@ def test_builds_project_with_manifest_outside_root_and_local_dependencies_with_d source_modules = set(os.listdir(os.path.join(source_dir, "node_modules"))) self.assertTrue(all(expected_module in source_modules for expected_module in expected_modules)) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_manifest_outside_root_and_local_dependencies_with_dependencies_dir_and_combine( self, runtime ): @@ -780,7 +774,7 @@ def test_builds_project_with_manifest_outside_root_and_local_dependencies_with_d source_modules = set(os.listdir(os.path.join(source_dir, "node_modules"))) self.assertTrue(all(expected_module in source_modules for expected_module in expected_modules)) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) @mock.patch.dict("os.environ", {"SAM_NPM_RUN_TEST_WITH_BUILD": "true"}) def test_runs_test_script_if_specified(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "test-script-to-create-file") @@ -797,7 +791,7 @@ def test_runs_test_script_if_specified(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_does_not_run_test_script_if_env_var_not_specified(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "test-script-to-create-file") @@ -813,7 +807,7 @@ def test_does_not_run_test_script_if_env_var_not_specified(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_does_not_raise_error_if_empty_test_script(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "empty-test-script") diff --git a/tests/integration/workflows/nodejs_npm_esbuild/test_nodejs_npm_with_esbuild.py b/tests/integration/workflows/nodejs_npm_esbuild/test_nodejs_npm_with_esbuild.py index 4a759f13b..badff3675 100644 --- a/tests/integration/workflows/nodejs_npm_esbuild/test_nodejs_npm_with_esbuild.py +++ b/tests/integration/workflows/nodejs_npm_esbuild/test_nodejs_npm_with_esbuild.py @@ -7,6 +7,7 @@ from aws_lambda_builders.builder import LambdaBuilder from aws_lambda_builders.exceptions import WorkflowFailedError +from aws_lambda_builders.supported_runtimes import NODEJS_RUNTIMES from aws_lambda_builders.workflows.nodejs_npm.npm import SubprocessNpm from aws_lambda_builders.workflows.nodejs_npm.utils import OSUtils from aws_lambda_builders.workflows.nodejs_npm_esbuild.esbuild import EsbuildExecutionError @@ -19,6 +20,7 @@ class TestNodejsNpmWorkflowWithEsbuild(TestCase): """ TEST_DATA_FOLDER = os.path.join(os.path.dirname(__file__), "testdata") + SUPPORTED_RUNTIMES = [(runtime,) for runtime in NODEJS_RUNTIMES] def setUp(self): self.source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild") @@ -52,7 +54,7 @@ def tearDown(self): if os.path.exists(source_dependencies): shutil.rmtree(source_dependencies) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_javascript_project_with_dependencies(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild") @@ -73,7 +75,7 @@ def test_builds_javascript_project_with_dependencies(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_javascript_project_with_multiple_entrypoints(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild-multiple-entrypoints") @@ -94,7 +96,7 @@ def test_builds_javascript_project_with_multiple_entrypoints(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_typescript_projects(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild-typescript") @@ -115,7 +117,7 @@ def test_builds_typescript_projects(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_with_external_esbuild(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "no-deps-esbuild") options = {"entry_points": ["included.js"]} @@ -135,7 +137,7 @@ def test_builds_with_external_esbuild(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_no_options_passed_to_esbuild(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild") @@ -152,7 +154,7 @@ def test_no_options_passed_to_esbuild(self, runtime): self.assertEqual(str(context.exception), "NodejsNpmEsbuildBuilder:EsbuildBundle - entry_points not set ({})") - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_bundle_with_implicit_file_types(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "implicit-file-types") @@ -173,7 +175,7 @@ def test_bundle_with_implicit_file_types(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_bundles_project_without_dependencies(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "no-package-esbuild") options = {"entry_points": ["included"]} @@ -199,7 +201,7 @@ def test_bundles_project_without_dependencies(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_remote_dependencies_without_download_dependencies_with_dependencies_dir(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-no-node_modules") options = {"entry_points": ["included.js"]} @@ -227,7 +229,7 @@ def test_builds_project_with_remote_dependencies_without_download_dependencies_w output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_remote_dependencies_with_download_dependencies_and_dependencies_dir(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-no-node_modules") options = {"entry_points": ["included.js"]} @@ -257,7 +259,7 @@ def test_builds_project_with_remote_dependencies_with_download_dependencies_and_ output_dependencies_files = set(os.listdir(os.path.join(self.dependencies_dir))) self.assertNotIn(expected_dependencies_files, output_dependencies_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_with_remote_dependencies_without_download_dependencies_without_dependencies_dir( self, runtime ): @@ -282,7 +284,7 @@ def test_builds_project_with_remote_dependencies_without_download_dependencies_w "dependencies directory was not provided and downloading dependencies is disabled.", ) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_project_without_combine_dependencies(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-no-node_modules") options = {"entry_points": ["included.js"]} @@ -313,7 +315,7 @@ def test_builds_project_without_combine_dependencies(self, runtime): output_dependencies_files = set(os.listdir(os.path.join(self.dependencies_dir))) self.assertNotIn(expected_dependencies_files, output_dependencies_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_javascript_project_with_external(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild-externals") @@ -338,7 +340,7 @@ def test_builds_javascript_project_with_external(self, runtime): # Check that the module has been require() instead of bundled self.assertIn('require("minimal-request-promise")', js_file) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_javascript_project_with_loader(self, runtime): osutils = OSUtils() source_dir = os.path.join(self.TEST_DATA_FOLDER, "no-deps-esbuild-loader") @@ -380,7 +382,7 @@ def test_builds_javascript_project_with_loader(self, runtime): ), ) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_includes_sourcemap_if_requested(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild") @@ -401,7 +403,7 @@ def test_includes_sourcemap_if_requested(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_esbuild_produces_mjs_output_files(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild") options = {"entry_points": ["included.js"], "sourcemap": True, "out_extension": [".js=.mjs"]} @@ -421,7 +423,7 @@ def test_esbuild_produces_mjs_output_files(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_esbuild_produces_sourcemap_without_source_contents(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild") options = {"entry_points": ["included.js"], "sourcemap": True, "sources_content": "false"} @@ -444,7 +446,7 @@ def test_esbuild_produces_sourcemap_without_source_contents(self, runtime): self.assertNotIn("sourcesContent", sourcemap) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_esbuild_can_build_in_source(self, runtime): options = {"entry_points": ["included.js"]} @@ -470,7 +472,7 @@ def test_esbuild_can_build_in_source(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_esbuild_can_build_in_source_with_local_dependency(self, runtime): self.source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-local-dependency") @@ -498,7 +500,7 @@ def test_esbuild_can_build_in_source_with_local_dependency(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_javascript_project_ignoring_relevant_flags(self, runtime): source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps-esbuild") @@ -519,7 +521,7 @@ def test_builds_javascript_project_ignoring_relevant_flags(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_typescript_projects_with_external_manifest(self, runtime): base_dir = os.path.join(self.TEST_DATA_FOLDER, "esbuild-manifest-outside-root") source_dir = os.path.join(base_dir, "src") @@ -541,7 +543,7 @@ def test_builds_typescript_projects_with_external_manifest(self, runtime): output_files = set(os.listdir(self.artifacts_dir)) self.assertEqual(expected_files, output_files) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_typescript_projects_with_external_manifest_with_dependencies_dir_without_combine(self, runtime): base_dir = os.path.join(self.TEST_DATA_FOLDER, "esbuild-manifest-outside-root") source_dir = os.path.join(base_dir, "src") @@ -570,7 +572,7 @@ def test_builds_typescript_projects_with_external_manifest_with_dependencies_dir output_modules = set(os.listdir(os.path.join(self.dependencies_dir, "node_modules"))) self.assertIn(expected_modules, output_modules) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_typescript_projects_with_external_manifest_with_dependencies_dir_with_combine(self, runtime): base_dir = os.path.join(self.TEST_DATA_FOLDER, "esbuild-manifest-outside-root") source_dir = os.path.join(base_dir, "src") @@ -599,7 +601,7 @@ def test_builds_typescript_projects_with_external_manifest_with_dependencies_dir output_modules = set(os.listdir(os.path.join(self.dependencies_dir, "node_modules"))) self.assertIn(expected_modules, output_modules) - @parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",), ("nodejs22.x",)]) + @parameterized.expand(SUPPORTED_RUNTIMES) def test_builds_typescript_projects_with_external_manifest_and_local_depends(self, runtime): base_dir = os.path.join(self.temp_testdata_dir, "esbuild-manifest-outside-root-with-local-depends") source_dir = os.path.join(base_dir, "src") diff --git a/tests/unit/test_validator.py b/tests/unit/test_validator.py index 5c5a6b545..29d725a4c 100644 --- a/tests/unit/test_validator.py +++ b/tests/unit/test_validator.py @@ -3,6 +3,15 @@ from aws_lambda_builders.validator import RuntimeValidator, SUPPORTED_RUNTIMES from aws_lambda_builders.exceptions import UnsupportedRuntimeError, UnsupportedArchitectureError from aws_lambda_builders.architecture import ARM64, X86_64 +from aws_lambda_builders.supported_runtimes import ( + NODEJS_RUNTIMES, + PYTHON_RUNTIMES, + RUBY_RUNTIMES, + JAVA_RUNTIMES, + DOTNET_RUNTIMES, + GO_RUNTIMES, + CUSTOM_RUNTIMES, +) class TestRuntimeValidator(TestCase): @@ -61,8 +70,7 @@ def test_exception_messages_contain_runtime_info(self): def test_all_nodejs_runtimes_supported(self): """Test all Node.js runtimes are supported with both architectures""" - nodejs_runtimes = ["nodejs16.x", "nodejs18.x", "nodejs20.x", "nodejs22.x"] - for runtime in nodejs_runtimes: + for runtime in NODEJS_RUNTIMES: for arch in [ARM64, X86_64]: validator = RuntimeValidator(runtime=runtime, architecture=arch) result = validator.validate("/usr/bin/node") @@ -71,16 +79,7 @@ def test_all_nodejs_runtimes_supported(self): def test_all_python_runtimes_supported(self): """Test all Python runtimes are supported with both architectures""" - python_runtimes = [ - "python3.8", - "python3.9", - "python3.10", - "python3.11", - "python3.12", - "python3.13", - "python3.14", - ] - for runtime in python_runtimes: + for runtime in PYTHON_RUNTIMES: for arch in [ARM64, X86_64]: validator = RuntimeValidator(runtime=runtime, architecture=arch) result = validator.validate(f"/usr/bin/{runtime}") @@ -89,8 +88,7 @@ def test_all_python_runtimes_supported(self): def test_all_ruby_runtimes_supported(self): """Test all Ruby runtimes are supported with both architectures""" - ruby_runtimes = ["ruby3.2", "ruby3.3", "ruby3.4"] - for runtime in ruby_runtimes: + for runtime in RUBY_RUNTIMES: for arch in [ARM64, X86_64]: validator = RuntimeValidator(runtime=runtime, architecture=arch) result = validator.validate("/usr/bin/ruby") @@ -99,8 +97,7 @@ def test_all_ruby_runtimes_supported(self): def test_all_java_runtimes_supported(self): """Test all Java runtimes are supported with both architectures""" - java_runtimes = ["java8", "java11", "java17", "java21", "java25"] - for runtime in java_runtimes: + for runtime in JAVA_RUNTIMES: for arch in [ARM64, X86_64]: validator = RuntimeValidator(runtime=runtime, architecture=arch) result = validator.validate("/usr/bin/java") @@ -109,17 +106,16 @@ def test_all_java_runtimes_supported(self): def test_go_runtime_supported(self): """Test Go runtime is supported with both architectures""" - runtime = "go1.x" - for arch in [ARM64, X86_64]: - validator = RuntimeValidator(runtime=runtime, architecture=arch) - result = validator.validate("/usr/bin/go") - self.assertEqual(result, "/usr/bin/go") - self.assertEqual(validator._runtime_path, "/usr/bin/go") + for runtime in GO_RUNTIMES: + for arch in [ARM64, X86_64]: + validator = RuntimeValidator(runtime=runtime, architecture=arch) + result = validator.validate("/usr/bin/go") + self.assertEqual(result, "/usr/bin/go") + self.assertEqual(validator._runtime_path, "/usr/bin/go") def test_all_dotnet_runtimes_supported(self): """Test all .NET runtimes are supported with both architectures""" - dotnet_runtimes = ["dotnet6", "dotnet8"] - for runtime in dotnet_runtimes: + for runtime in DOTNET_RUNTIMES: for arch in [ARM64, X86_64]: validator = RuntimeValidator(runtime=runtime, architecture=arch) result = validator.validate("/usr/bin/dotnet") @@ -128,12 +124,12 @@ def test_all_dotnet_runtimes_supported(self): def test_provided_runtime_supported(self): """Test provided runtime is supported with both architectures""" - runtime = "provided" - for arch in [ARM64, X86_64]: - validator = RuntimeValidator(runtime=runtime, architecture=arch) - result = validator.validate("/opt/bootstrap") - self.assertEqual(result, "/opt/bootstrap") - self.assertEqual(validator._runtime_path, "/opt/bootstrap") + for runtime in CUSTOM_RUNTIMES: + for arch in [ARM64, X86_64]: + validator = RuntimeValidator(runtime=runtime, architecture=arch) + result = validator.validate("/opt/bootstrap") + self.assertEqual(result, "/opt/bootstrap") + self.assertEqual(validator._runtime_path, "/opt/bootstrap") def test_all_runtimes_support_both_architectures(self): """Test that all supported runtimes support both ARM64 and X86_64 architectures""" @@ -149,6 +145,8 @@ def test_validate_returns_original_path(self): ("nodejs20.x", "/opt/node/bin/node"), ("java17", "/usr/lib/jvm/java-17/bin/java"), ("provided", "/var/runtime/bootstrap"), + ("provided.al2", "/var/runtime/bootstrap"), + ("provided.al2023", "/var/runtime/bootstrap"), ] for runtime, path in test_cases: