diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed5588b7d..d7f97513f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,14 +33,14 @@ jobs: matrix: stack: ["heroku-20", "heroku-22", "heroku-24"] env: - HATCHET_APP_LIMIT: 200 + HATCHET_APP_LIMIT: 300 HATCHET_DEFAULT_STACK: ${{ matrix.stack }} HATCHET_EXPENSIVE_MODE: 1 HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} HEROKU_API_USER: ${{ secrets.HEROKU_API_USER }} HEROKU_DISABLE_AUTOUPDATE: 1 - PARALLEL_SPLIT_TEST_PROCESSES: 60 - RSPEC_RETRY_RETRY_COUNT: 3 + PARALLEL_SPLIT_TEST_PROCESSES: 70 + RSPEC_RETRY_RETRY_COUNT: 2 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/Gemfile.lock b/Gemfile.lock index 6dc35eb1b..7b8aeedce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ GEM base64 (0.2.0) diff-lcs (1.5.1) erubis (2.7.0) - excon (0.110.0) + excon (0.111.0) heroics (0.1.3) base64 erubis (~> 2.0) @@ -27,7 +27,7 @@ GEM parallel_split_test (0.10.0) parallel (>= 0.5.13) rspec-core (>= 3.9.0) - parser (3.3.4.2) + parser (3.3.5.0) ast (~> 2.4.1) racc platform-api (3.7.0) @@ -39,32 +39,32 @@ GEM rate_throttle_client (0.1.2) regexp_parser (2.9.2) rrrretry (1.0.0) - rspec-core (3.13.0) + rspec-core (3.13.1) rspec-support (~> 3.13.0) - rspec-expectations (3.13.2) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.13.1) - rubocop (1.66.0) + rubocop (1.66.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.4, < 3.0) - rubocop-ast (>= 1.32.1, < 2.0) + rubocop-ast (>= 1.32.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.1) + rubocop-ast (1.32.3) parser (>= 3.3.1.0) - rubocop-rspec (3.0.4) + rubocop-rspec (3.0.5) rubocop (~> 1.61) ruby-progressbar (1.13.0) - thor (1.3.1) + thor (1.3.2) threaded (0.0.4) - unicode-display_width (2.5.0) + unicode-display_width (2.6.0) webrick (1.8.1) PLATFORMS @@ -80,7 +80,7 @@ DEPENDENCIES rubocop-rspec RUBY VERSION - ruby 3.3.2p78 + ruby 3.3.5p100 BUNDLED WITH - 2.5.11 + 2.5.18 diff --git a/bin/steps/pipenv b/bin/steps/pipenv index d7a190b4b..1d97941c7 100755 --- a/bin/steps/pipenv +++ b/bin/steps/pipenv @@ -15,6 +15,7 @@ if [[ -f Pipfile ]]; then meta_set "package_manager" "pipenv" # Skip installing dependencies using pip later. + # TODO: Stop leaking this env var into subshells such as post_compile hooks. export SKIP_PIP_INSTALL=1 # Set Pip env vars diff --git a/spec/fixtures/ci_pipenv/Pipfile b/spec/fixtures/ci_pipenv/Pipfile new file mode 100644 index 000000000..6d6473eb4 --- /dev/null +++ b/spec/fixtures/ci_pipenv/Pipfile @@ -0,0 +1,10 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +typing-extensions = "*" + +[dev-packages] +pytest = "*" diff --git a/spec/fixtures/ci_pipenv/Pipfile.lock b/spec/fixtures/ci_pipenv/Pipfile.lock new file mode 100644 index 000000000..aa11d8ecf --- /dev/null +++ b/spec/fixtures/ci_pipenv/Pipfile.lock @@ -0,0 +1,62 @@ +{ + "_meta": { + "hash": { + "sha256": "9ac30f761973e7bb9a0425635eb284370fede0e49a74b475c418f98bf13f3075" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "typing-extensions": { + "hashes": [ + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==4.12.2" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pluggy": { + "hashes": [ + "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" + }, + "pytest": { + "hashes": [ + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==8.3.3" + } + } +} diff --git a/spec/fixtures/ci_pipenv/app.json b/spec/fixtures/ci_pipenv/app.json new file mode 100644 index 000000000..59617c169 --- /dev/null +++ b/spec/fixtures/ci_pipenv/app.json @@ -0,0 +1,9 @@ +{ + "environments": { + "test": { + "scripts": { + "test": "./bin/print-env-vars.sh && pytest --version" + } + } + } +} diff --git a/spec/fixtures/ci_pipenv/bin/compile b/spec/fixtures/ci_pipenv/bin/compile new file mode 100755 index 000000000..fcb7055e3 --- /dev/null +++ b/spec/fixtures/ci_pipenv/bin/compile @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# This file is run by the inline buildpack, and tests that the environment is +# configured as expected for buildpacks that run after the Python buildpack. + +set -euo pipefail + +BUILD_DIR="${1}" + +cd "${BUILD_DIR}" + +exec bin/print-env-vars.sh diff --git a/spec/fixtures/ci_pipenv/bin/detect b/spec/fixtures/ci_pipenv/bin/detect new file mode 100755 index 000000000..68cdcc4a2 --- /dev/null +++ b/spec/fixtures/ci_pipenv/bin/detect @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# This file is run by the inline buildpack. + +set -euo pipefail + +echo "Inline" diff --git a/spec/fixtures/ci_pipenv/bin/post_compile b/spec/fixtures/ci_pipenv/bin/post_compile new file mode 100644 index 000000000..15362f6b1 --- /dev/null +++ b/spec/fixtures/ci_pipenv/bin/post_compile @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euo pipefail + +exec bin/print-env-vars.sh diff --git a/spec/fixtures/ci_pipenv/bin/print-env-vars.sh b/spec/fixtures/ci_pipenv/bin/print-env-vars.sh new file mode 100755 index 000000000..9e0bebe6b --- /dev/null +++ b/spec/fixtures/ci_pipenv/bin/print-env-vars.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euo pipefail + +printenv | sort | grep -vE '^(_|BUILDPACK_LOG_FILE|BUILD_DIR|CACHE_DIR|CI_NODE_.+|DYNO|ENV_DIR|HEROKU_TEST_RUN_.+|HOME|OLDPWD|PORT|PWD|SHLVL|STACK|TERM)=' diff --git a/spec/fixtures/ci_requirements/app.json b/spec/fixtures/ci_requirements/app.json index 99d1d237f..59617c169 100644 --- a/spec/fixtures/ci_requirements/app.json +++ b/spec/fixtures/ci_requirements/app.json @@ -2,7 +2,7 @@ "environments": { "test": { "scripts": { - "test": "pytest --version" + "test": "./bin/print-env-vars.sh && pytest --version" } } } diff --git a/spec/fixtures/ci_requirements/bin/compile b/spec/fixtures/ci_requirements/bin/compile new file mode 100755 index 000000000..fcb7055e3 --- /dev/null +++ b/spec/fixtures/ci_requirements/bin/compile @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# This file is run by the inline buildpack, and tests that the environment is +# configured as expected for buildpacks that run after the Python buildpack. + +set -euo pipefail + +BUILD_DIR="${1}" + +cd "${BUILD_DIR}" + +exec bin/print-env-vars.sh diff --git a/spec/fixtures/ci_requirements/bin/detect b/spec/fixtures/ci_requirements/bin/detect new file mode 100755 index 000000000..68cdcc4a2 --- /dev/null +++ b/spec/fixtures/ci_requirements/bin/detect @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# This file is run by the inline buildpack. + +set -euo pipefail + +echo "Inline" diff --git a/spec/fixtures/ci_requirements/bin/post_compile b/spec/fixtures/ci_requirements/bin/post_compile new file mode 100644 index 000000000..15362f6b1 --- /dev/null +++ b/spec/fixtures/ci_requirements/bin/post_compile @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euo pipefail + +exec bin/print-env-vars.sh diff --git a/spec/fixtures/ci_requirements/bin/print-env-vars.sh b/spec/fixtures/ci_requirements/bin/print-env-vars.sh new file mode 100755 index 000000000..9e0bebe6b --- /dev/null +++ b/spec/fixtures/ci_requirements/bin/print-env-vars.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euo pipefail + +printenv | sort | grep -vE '^(_|BUILDPACK_LOG_FILE|BUILD_DIR|CACHE_DIR|CI_NODE_.+|DYNO|ENV_DIR|HEROKU_TEST_RUN_.+|HOME|OLDPWD|PORT|PWD|SHLVL|STACK|TERM)=' diff --git a/spec/fixtures/ci_requirements/requirements-test.txt b/spec/fixtures/ci_requirements/requirements-test.txt index e079f8a60..40543aaba 100644 --- a/spec/fixtures/ci_requirements/requirements-test.txt +++ b/spec/fixtures/ci_requirements/requirements-test.txt @@ -1 +1 @@ -pytest +pytest==8.3.3 diff --git a/spec/fixtures/ci_requirements/requirements.txt b/spec/fixtures/ci_requirements/requirements.txt index a42590beb..eec3a2223 100644 --- a/spec/fixtures/ci_requirements/requirements.txt +++ b/spec/fixtures/ci_requirements/requirements.txt @@ -1 +1,2 @@ -urllib3 +# This package has been picked since it has no dependencies and is small/fast to install. +typing-extensions==4.12.2 diff --git a/spec/fixtures/hooks/bin/post_compile b/spec/fixtures/hooks/bin/post_compile index 373d33889..4bd8ed0e8 100644 --- a/spec/fixtures/hooks/bin/post_compile +++ b/spec/fixtures/hooks/bin/post_compile @@ -2,5 +2,6 @@ set -euo pipefail -echo 'post_compile ran with env vars:' -printenv | cut -d '=' -f 1 | sort +echo '~ post_compile ran with env vars:' +bin/print-env-vars.sh +echo '~ post_compile complete' diff --git a/spec/fixtures/hooks/bin/pre_compile b/spec/fixtures/hooks/bin/pre_compile index 40d1a402e..9f3737bef 100644 --- a/spec/fixtures/hooks/bin/pre_compile +++ b/spec/fixtures/hooks/bin/pre_compile @@ -2,5 +2,6 @@ set -euo pipefail -echo 'pre_compile ran with env vars:' -printenv | cut -d '=' -f 1 | sort +echo '~ pre_compile ran with env vars:' +bin/print-env-vars.sh +echo '~ pre_compile complete' diff --git a/spec/fixtures/hooks/bin/print-env-vars.sh b/spec/fixtures/hooks/bin/print-env-vars.sh new file mode 100755 index 000000000..dbd598a54 --- /dev/null +++ b/spec/fixtures/hooks/bin/print-env-vars.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -euo pipefail + +printenv | sort \ + | grep -vE '^(_|BUILDPACK_LOG_FILE|DYNO|OLDPWD|REQUEST_ID|SHLVL)=' \ + | sed --regexp-extended \ + --expression 's#(=/tmp/build_)[^:/]+#\1#' \ + --expression 's#^(ENV_DIR=/tmp/).*#\1...#' \ + --expression 's#^(SOURCE_VERSION=).*#\1...#' diff --git a/spec/fixtures/pipenv_editable/bin/compile b/spec/fixtures/pipenv_editable/bin/compile index c506ad6b7..df17e9401 100755 --- a/spec/fixtures/pipenv_editable/bin/compile +++ b/spec/fixtures/pipenv_editable/bin/compile @@ -9,4 +9,4 @@ BUILD_DIR="${1}" cd "${BUILD_DIR}" -exec bin/test-entrypoints +exec bin/test-entrypoints.sh diff --git a/spec/fixtures/pipenv_editable/bin/post_compile b/spec/fixtures/pipenv_editable/bin/post_compile index 460dc2de7..6e77d159a 100755 --- a/spec/fixtures/pipenv_editable/bin/post_compile +++ b/spec/fixtures/pipenv_editable/bin/post_compile @@ -2,4 +2,4 @@ set -euo pipefail -exec bin/test-entrypoints +exec bin/test-entrypoints.sh diff --git a/spec/fixtures/pipenv_editable/bin/test-entrypoints b/spec/fixtures/pipenv_editable/bin/test-entrypoints.sh similarity index 100% rename from spec/fixtures/pipenv_editable/bin/test-entrypoints rename to spec/fixtures/pipenv_editable/bin/test-entrypoints.sh diff --git a/spec/fixtures/pipenv_python_version_unspecified/Pipfile b/spec/fixtures/pipenv_python_version_unspecified/Pipfile index 67803f45b..496fa1558 100644 --- a/spec/fixtures/pipenv_python_version_unspecified/Pipfile +++ b/spec/fixtures/pipenv_python_version_unspecified/Pipfile @@ -4,6 +4,6 @@ verify_ssl = true name = "pypi" [packages] -urllib3 = "*" +typing-extensions = "*" [dev-packages] diff --git a/spec/fixtures/pipenv_python_version_unspecified/Pipfile.lock b/spec/fixtures/pipenv_python_version_unspecified/Pipfile.lock index 4c426c2ae..e744fd450 100644 --- a/spec/fixtures/pipenv_python_version_unspecified/Pipfile.lock +++ b/spec/fixtures/pipenv_python_version_unspecified/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a95c318e4395270fbd1e0f52dd8f12185db4c3258d4e03dd80de54b7c7aad8b1" + "sha256": "beb76460a63ef2f29eec7b281a3c7114d442db105096d7472b4b72a7504df8fe" }, "pipfile-spec": 6, "requires": {}, @@ -14,14 +14,14 @@ ] }, "default": { - "urllib3": { + "typing-extensions": { "hashes": [ - "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", - "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.2.1" + "version": "==4.12.2" } }, "develop": {} diff --git a/spec/fixtures/pipenv_python_version_unspecified/bin/compile b/spec/fixtures/pipenv_python_version_unspecified/bin/compile new file mode 100755 index 000000000..db00975d6 --- /dev/null +++ b/spec/fixtures/pipenv_python_version_unspecified/bin/compile @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# This file is run by the inline buildpack, and tests that the environment is +# configured as expected for buildpacks that run after the Python buildpack. + +set -euo pipefail + +printenv | sort | grep -vE '^(_|BUILDPACK_LOG_FILE|DYNO|HOME|PWD|REQUEST_ID|SHLVL|SOURCE_VERSION|STACK)=' +echo + +python -c 'import pprint, sys; pprint.pp(sys.path)' +echo + +# TODO: Investigate why 'pipenv graph' doesn't work here. +# TODO: Remove --disable-pip-version-check in favour of exporting PIP_DISABLE_PIP_VERSION_CHECK +pip list --disable-pip-version-check +echo + +python -c 'import typing_extensions; print(typing_extensions)' diff --git a/spec/fixtures/pipenv_python_version_unspecified/bin/detect b/spec/fixtures/pipenv_python_version_unspecified/bin/detect new file mode 100755 index 000000000..68cdcc4a2 --- /dev/null +++ b/spec/fixtures/pipenv_python_version_unspecified/bin/detect @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# This file is run by the inline buildpack. + +set -euo pipefail + +echo "Inline" diff --git a/spec/fixtures/pipenv_python_version_unspecified/setup.py b/spec/fixtures/pipenv_python_version_unspecified/setup.py new file mode 100644 index 000000000..de90e907e --- /dev/null +++ b/spec/fixtures/pipenv_python_version_unspecified/setup.py @@ -0,0 +1,9 @@ +# This file is here to confirm we don't try and create the fallback requirements +# file containing '-e .' when using Pipenv. + +from setuptools import setup + +setup( + name='test', + install_requires=['six'], +) diff --git a/spec/fixtures/python_3.10_outdated/runtime.txt b/spec/fixtures/python_3.10_outdated/runtime.txt index a5da7cc4d..fadb07024 100644 --- a/spec/fixtures/python_3.10_outdated/runtime.txt +++ b/spec/fixtures/python_3.10_outdated/runtime.txt @@ -1 +1 @@ -python-3.10.5 +python-3.10.0 diff --git a/spec/fixtures/python_3.8_outdated/runtime.txt b/spec/fixtures/python_3.8_outdated/runtime.txt index d9a16ae1e..73b1cf81d 100644 --- a/spec/fixtures/python_3.8_outdated/runtime.txt +++ b/spec/fixtures/python_3.8_outdated/runtime.txt @@ -1 +1 @@ -python-3.8.12 +python-3.8.0 diff --git a/spec/fixtures/python_3.9_outdated/runtime.txt b/spec/fixtures/python_3.9_outdated/runtime.txt index 540296197..f72c5111f 100644 --- a/spec/fixtures/python_3.9_outdated/runtime.txt +++ b/spec/fixtures/python_3.9_outdated/runtime.txt @@ -1 +1 @@ -python-3.9.12 +python-3.9.0 diff --git a/spec/fixtures/requirements_basic/bin/compile b/spec/fixtures/requirements_basic/bin/compile new file mode 100755 index 000000000..68d243e7b --- /dev/null +++ b/spec/fixtures/requirements_basic/bin/compile @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# This file is run by the inline buildpack, and tests that the environment is +# configured as expected for buildpacks that run after the Python buildpack. + +set -euo pipefail + +printenv | sort | grep -vE '^(_|BUILDPACK_LOG_FILE|DYNO|HOME|PWD|REQUEST_ID|SHLVL|SOURCE_VERSION|STACK)=' +echo + +python -c 'import pprint, sys; pprint.pp(sys.path)' +echo + +# TODO: Remove --disable-pip-version-check in favour of exporting PIP_DISABLE_PIP_VERSION_CHECK +pip list --disable-pip-version-check +echo + +python -c 'import typing_extensions; print(typing_extensions)' diff --git a/spec/fixtures/requirements_basic/bin/detect b/spec/fixtures/requirements_basic/bin/detect new file mode 100755 index 000000000..68cdcc4a2 --- /dev/null +++ b/spec/fixtures/requirements_basic/bin/detect @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# This file is run by the inline buildpack. + +set -euo pipefail + +echo "Inline" diff --git a/spec/fixtures/requirements_basic/requirements.txt b/spec/fixtures/requirements_basic/requirements.txt new file mode 100644 index 000000000..eec3a2223 --- /dev/null +++ b/spec/fixtures/requirements_basic/requirements.txt @@ -0,0 +1,2 @@ +# This package has been picked since it has no dependencies and is small/fast to install. +typing-extensions==4.12.2 diff --git a/spec/fixtures/requirements_editable/bin/compile b/spec/fixtures/requirements_editable/bin/compile index c506ad6b7..df17e9401 100755 --- a/spec/fixtures/requirements_editable/bin/compile +++ b/spec/fixtures/requirements_editable/bin/compile @@ -9,4 +9,4 @@ BUILD_DIR="${1}" cd "${BUILD_DIR}" -exec bin/test-entrypoints +exec bin/test-entrypoints.sh diff --git a/spec/fixtures/requirements_editable/bin/post_compile b/spec/fixtures/requirements_editable/bin/post_compile index 460dc2de7..6e77d159a 100755 --- a/spec/fixtures/requirements_editable/bin/post_compile +++ b/spec/fixtures/requirements_editable/bin/post_compile @@ -2,4 +2,4 @@ set -euo pipefail -exec bin/test-entrypoints +exec bin/test-entrypoints.sh diff --git a/spec/fixtures/requirements_editable/bin/test-entrypoints b/spec/fixtures/requirements_editable/bin/test-entrypoints.sh similarity index 100% rename from spec/fixtures/requirements_editable/bin/test-entrypoints rename to spec/fixtures/requirements_editable/bin/test-entrypoints.sh diff --git a/spec/fixtures/requirements_git/requirements.txt b/spec/fixtures/requirements_git/requirements.txt deleted file mode 100644 index 81e9dbfe0..000000000 --- a/spec/fixtures/requirements_git/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# This relies upon the VCS binaries from the stack image. -git+https://github.com/certifi/python-certifi diff --git a/spec/fixtures/requirements_invalid/requirements.txt b/spec/fixtures/requirements_invalid/requirements.txt new file mode 100644 index 000000000..db42b7ee6 --- /dev/null +++ b/spec/fixtures/requirements_invalid/requirements.txt @@ -0,0 +1 @@ +an-invalid-requirement! diff --git a/spec/hatchet/ci_spec.rb b/spec/hatchet/ci_spec.rb index eb0b2c5db..ad8053abf 100644 --- a/spec/hatchet/ci_spec.rb +++ b/spec/hatchet/ci_spec.rb @@ -3,46 +3,163 @@ require_relative '../spec_helper' RSpec.describe 'Heroku CI' do - it 'installs both normal and test dependencies and uses cache on subsequent runs' do - Hatchet::Runner.new('spec/fixtures/ci_requirements', allow_failure: true).run_ci do |test_run| - expect(test_run.output).to match(Regexp.new(<<~REGEX, Regexp::MULTILINE)) - -----> Python app detected - -----> No Python version was specified. Using the buildpack default: python-#{DEFAULT_PYTHON_VERSION} - To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes - -----> Installing python-#{DEFAULT_PYTHON_VERSION} - -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} - -----> Installing SQLite3 - -----> Installing requirements with pip - .* - Successfully installed urllib3-.* - -----> Installing test dependencies... - .* - Successfully installed .* pytest-.* - -----> Skipping Django collectstatic since the env var DISABLE_COLLECTSTATIC is set. - -----> No test-setup command provided. Skipping. - -----> Running test command `pytest --version`... - pytest .* - -----> test command `pytest --version` completed successfully - REGEX - - test_run.run_again - - expect(test_run.output).to match(Regexp.new(<<~REGEX)) - -----> Python app detected - -----> No Python version was specified. Using the same version as the last build: python-#{DEFAULT_PYTHON_VERSION} - To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes - -----> No change in requirements detected, installing from cache - -----> Using cached install of python-#{DEFAULT_PYTHON_VERSION} - -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} - -----> Installing SQLite3 - -----> Installing requirements with pip - -----> Installing test dependencies... - -----> Skipping Django collectstatic since the env var DISABLE_COLLECTSTATIC is set. - -----> No test-setup command provided. Skipping. - -----> Running test command `pytest --version`... - pytest .* - -----> test command `pytest --version` completed successfully - REGEX + let(:buildpacks) { [:default, 'heroku-community/inline'] } + + context 'when using pip' do + let(:app) { Hatchet::Runner.new('spec/fixtures/ci_requirements', buildpacks:) } + + it 'installs both normal and test dependencies and uses cache on subsequent runs' do + app.run_ci do |test_run| + expect(test_run.output).to match(Regexp.new(<<~REGEX, Regexp::MULTILINE)) + -----> Python app detected + -----> No Python version was specified. Using the buildpack default: python-#{DEFAULT_PYTHON_VERSION} + To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes + -----> Installing python-#{DEFAULT_PYTHON_VERSION} + -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} + -----> Installing SQLite3 + -----> Installing requirements with pip + .* + Successfully installed typing-extensions-4.12.2 + -----> Installing test dependencies... + .* + Successfully installed .* pytest-8.3.3 + -----> Skipping Django collectstatic since the env var DISABLE_COLLECTSTATIC is set. + -----> Running post-compile hook + CI=true + CPLUS_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include: + C_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include: + DISABLE_COLLECTSTATIC=1 + INSTALL_TEST=1 + LANG=en_US.UTF-8 + LC_ALL=C.UTF-8 + LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + PATH=/app/.heroku/python/bin:/app/.heroku/vendor/bin::/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/app/.sprettur/bin/ + PIP_NO_PYTHON_VERSION_WARNING=1 + PKG_CONFIG_PATH=/app/.heroku/vendor/lib/pkg-config:/app/.heroku/python/lib/pkg-config: + PYTHONUNBUFFERED=1 + -----> Inline app detected + LANG=en_US.UTF-8 + LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + PATH=/app/.heroku/python/bin:/usr/local/bin:/usr/bin:/bin:/app/.sprettur/bin/ + PYTHONHASHSEED=random + PYTHONHOME=/app/.heroku/python + PYTHONPATH=/app + PYTHONUNBUFFERED=true + -----> No test-setup command provided. Skipping. + -----> Running test command `./bin/print-env-vars.sh && pytest --version`... + CI=true + DYNO_RAM=2560 + FORWARDED_ALLOW_IPS=\\* + GUNICORN_CMD_ARGS=--access-logfile - + LANG=en_US.UTF-8 + LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + PATH=/app/.heroku/python/bin:/usr/local/bin:/usr/bin:/bin:/app/.sprettur/bin/:/app/.sprettur/bin/ + PYTHONHASHSEED=random + PYTHONHOME=/app/.heroku/python + PYTHONPATH=/app + PYTHONUNBUFFERED=true + WEB_CONCURRENCY=5 + pytest 8.3.3 + -----> test command `./bin/print-env-vars.sh && pytest --version` completed successfully + REGEX + + test_run.run_again + + expect(test_run.output).to include(<<~OUTPUT) + -----> Python app detected + -----> No Python version was specified. Using the same version as the last build: python-#{DEFAULT_PYTHON_VERSION} + To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes + -----> No change in requirements detected, installing from cache + -----> Using cached install of python-#{DEFAULT_PYTHON_VERSION} + -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} + -----> Installing SQLite3 + -----> Installing requirements with pip + -----> Installing test dependencies... + -----> Skipping Django collectstatic since the env var DISABLE_COLLECTSTATIC is set. + -----> Running post-compile hook + OUTPUT + end + end + end + + context 'when using Pipenv' do + let(:app) { Hatchet::Runner.new('spec/fixtures/ci_pipenv', buildpacks:) } + + it 'installs both normal and test dependencies and uses cache on subsequent runs' do + app.run_ci do |test_run| + expect(test_run.output).to match(Regexp.new(<<~REGEX)) + -----> Python app detected + -----> No Python version was specified. Using the buildpack default: python-#{DEFAULT_PYTHON_VERSION} + To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes + -----> Installing python-#{DEFAULT_PYTHON_VERSION} + -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} + -----> Installing test dependencies + Installing dependencies from Pipfile.lock \\(.+\\)... + Installing dependencies from Pipfile.lock \\(.+\\)... + -----> Installing SQLite3 + -----> Skipping Django collectstatic since the env var DISABLE_COLLECTSTATIC is set. + -----> Running post-compile hook + CI=true + CPLUS_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include: + C_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include: + DISABLE_COLLECTSTATIC=1 + INSTALL_TEST=1 + LANG=en_US.UTF-8 + LC_ALL=C.UTF-8 + LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + PATH=/app/.heroku/python/bin:/app/.heroku/vendor/bin::/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/app/.sprettur/bin/ + PIP_NO_PYTHON_VERSION_WARNING=1 + PKG_CONFIG_PATH=/app/.heroku/vendor/lib/pkg-config:/app/.heroku/python/lib/pkg-config: + PYTHONUNBUFFERED=1 + SKIP_PIP_INSTALL=1 + -----> Inline app detected + LANG=en_US.UTF-8 + LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + PATH=/app/.heroku/python/bin:/usr/local/bin:/usr/bin:/bin:/app/.sprettur/bin/ + PYTHONHASHSEED=random + PYTHONHOME=/app/.heroku/python + PYTHONPATH=/app + PYTHONUNBUFFERED=true + -----> No test-setup command provided. Skipping. + -----> Running test command `./bin/print-env-vars.sh && pytest --version`... + CI=true + DYNO_RAM=2560 + FORWARDED_ALLOW_IPS=\\* + GUNICORN_CMD_ARGS=--access-logfile - + LANG=en_US.UTF-8 + LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + PATH=/app/.heroku/python/bin:/usr/local/bin:/usr/bin:/bin:/app/.sprettur/bin/:/app/.sprettur/bin/ + PYTHONHASHSEED=random + PYTHONHOME=/app/.heroku/python + PYTHONPATH=/app + PYTHONUNBUFFERED=true + WEB_CONCURRENCY=5 + pytest 8.3.3 + -----> test command `./bin/print-env-vars.sh && pytest --version` completed successfully + REGEX + + test_run.run_again + + expect(test_run.output).to match(Regexp.new(<<~REGEX)) + -----> Python app detected + -----> No Python version was specified. Using the same version as the last build: python-#{DEFAULT_PYTHON_VERSION} + To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes + -----> Using cached install of python-#{DEFAULT_PYTHON_VERSION} + -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} + -----> Installing test dependencies + Installing dependencies from Pipfile.lock \\(.+\\)... + Installing dependencies from Pipfile.lock \\(.+\\)... + -----> Installing SQLite3 + -----> Skipping Django collectstatic since the env var DISABLE_COLLECTSTATIC is set. + -----> Running post-compile hook + REGEX + end end end end diff --git a/spec/hatchet/django_spec.rb b/spec/hatchet/django_spec.rb index 865d5b833..bd7feb1a9 100644 --- a/spec/hatchet/django_spec.rb +++ b/spec/hatchet/django_spec.rb @@ -97,10 +97,19 @@ expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX, Regexp::MULTILINE)) remote: -----> \\$ python manage.py collectstatic --noinput remote: Traceback \\(most recent call last\\): - remote: .* + remote: .+ remote: ModuleNotFoundError: No module named 'gettingstarted' remote: remote: ! Error while running '\\$ python manage.py collectstatic --noinput'. + remote: See traceback above for details. + remote: + remote: You may need to update application code to resolve this error. + remote: Or, you can disable collectstatic for this application: + remote: + remote: \\$ heroku config:set DISABLE_COLLECTSTATIC=1 + remote: + remote: https://devcenter.heroku.com/articles/django-assets + remote: ! Push rejected, failed to compile Python app. REGEX end end diff --git a/spec/hatchet/hooks_spec.rb b/spec/hatchet/hooks_spec.rb index dd114a76b..6d66adf54 100644 --- a/spec/hatchet/hooks_spec.rb +++ b/spec/hatchet/hooks_spec.rb @@ -3,49 +3,62 @@ require_relative '../spec_helper' RSpec.describe 'Compile hooks' do - context 'when an app has bin/pre_compile and bin/post_compile scripts' do + # TODO: Run this on Heroku-22 too, once it has also migrated to the new build infrastructure. + # (Currently the test fails on the old infrastructure due to subtle differences in system PATH elements.) + context 'when an app has bin/pre_compile and bin/post_compile scripts', stacks: %w[heroku-20 heroku-24] do let(:app) { Hatchet::Runner.new('spec/fixtures/hooks', config: { 'SOME_APP_CONFIG_VAR' => '1' }) } it 'runs the hooks with the correct environment' do - expected_env_vars = %w[ - _ - BUILD_DIR - BUILDPACK_LOG_FILE - CACHE_DIR - C_INCLUDE_PATH - CPLUS_INCLUDE_PATH - DYNO - ENV_DIR - HOME - LANG - LD_LIBRARY_PATH - LIBRARY_PATH - OLDPWD - PATH - PIP_NO_PYTHON_VERSION_WARNING - PKG_CONFIG_PATH - PWD - PYTHONUNBUFFERED - REQUEST_ID - SHLVL - SOME_APP_CONFIG_VAR - SOURCE_VERSION - STACK - ] - app.deploy do |app| - expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX, Regexp::MULTILINE)) + output = clean_output(app.output) + + expect(output).to include(<<~OUTPUT) remote: -----> Python app detected remote: -----> Running pre-compile hook - remote: pre_compile ran with env vars: - remote: #{expected_env_vars.join("\nremote: ")} - remote: -----> No Python version was specified. Using the buildpack default: python-#{DEFAULT_PYTHON_VERSION} - remote: .* + remote: ~ pre_compile ran with env vars: + remote: BUILD_DIR=/tmp/build_ + remote: CACHE_DIR=/tmp/codon/tmp/cache + remote: C_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include: + remote: CPLUS_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include: + remote: ENV_DIR=/tmp/... + remote: HOME=/app + remote: LANG=en_US.UTF-8 + remote: LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + remote: LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + remote: PATH=/app/.heroku/python/bin:/app/.heroku/vendor/bin::/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + remote: PIP_NO_PYTHON_VERSION_WARNING=1 + remote: PKG_CONFIG_PATH=/app/.heroku/vendor/lib/pkg-config:/app/.heroku/python/lib/pkg-config: + remote: PWD=/tmp/build_ + remote: PYTHONUNBUFFERED=1 + remote: SOME_APP_CONFIG_VAR=1 + remote: SOURCE_VERSION=... + remote: STACK=#{app.stack} + remote: ~ pre_compile complete + OUTPUT + + expect(output).to include(<<~OUTPUT) remote: -----> Installing requirements with pip remote: -----> Running post-compile hook - remote: post_compile ran with env vars: - remote: #{expected_env_vars.join("\nremote: ")} - REGEX + remote: ~ post_compile ran with env vars: + remote: BUILD_DIR=/tmp/build_ + remote: CACHE_DIR=/tmp/codon/tmp/cache + remote: C_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include: + remote: CPLUS_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include: + remote: ENV_DIR=/tmp/... + remote: HOME=/app + remote: LANG=en_US.UTF-8 + remote: LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + remote: LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + remote: PATH=/app/.heroku/python/bin:/app/.heroku/vendor/bin::/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + remote: PIP_NO_PYTHON_VERSION_WARNING=1 + remote: PKG_CONFIG_PATH=/app/.heroku/vendor/lib/pkg-config:/app/.heroku/python/lib/pkg-config: + remote: PWD=/tmp/build_ + remote: PYTHONUNBUFFERED=1 + remote: SOME_APP_CONFIG_VAR=1 + remote: SOURCE_VERSION=... + remote: STACK=#{app.stack} + remote: ~ post_compile complete + OUTPUT end end end diff --git a/spec/hatchet/package_manager_spec.rb b/spec/hatchet/package_manager_spec.rb index 10cff59b4..d247e1deb 100644 --- a/spec/hatchet/package_manager_spec.rb +++ b/spec/hatchet/package_manager_spec.rb @@ -35,6 +35,7 @@ remote: ! https://devcenter.heroku.com/articles/getting-started-with-python remote: ! https://devcenter.heroku.com/articles/python-support remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end diff --git a/spec/hatchet/pip_spec.rb b/spec/hatchet/pip_spec.rb index 704a65c4b..5351975e0 100644 --- a/spec/hatchet/pip_spec.rb +++ b/spec/hatchet/pip_spec.rb @@ -12,12 +12,15 @@ end RSpec.describe 'Pip support' do - context 'when requirements.txt is unchanged since the last build' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_version_unspecified') } + # TODO: Run this on Heroku-22 too, once it has also migrated to the new build infrastructure. + # (Currently the test fails on the old infrastructure due to subtle differences in system PATH elements.) + context 'when requirements.txt is unchanged since the last build', stacks: %w[heroku-20 heroku-24] do + let(:buildpacks) { [:default, 'heroku-community/inline'] } + let(:app) { Hatchet::Runner.new('spec/fixtures/requirements_basic', buildpacks:) } it 're-uses packages from the cache' do app.deploy do |app| - expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) + expect(clean_output(app.output)).to include(<<~OUTPUT) remote: -----> Python app detected remote: -----> No Python version was specified. Using the buildpack default: python-#{DEFAULT_PYTHON_VERSION} remote: To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes @@ -25,12 +28,37 @@ remote: -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} remote: -----> Installing SQLite3 remote: -----> Installing requirements with pip - remote: Collecting urllib3 \\(from -r requirements.txt \\(line 1\\)\\) - remote: Downloading urllib3-.* - remote: Downloading urllib3-.* - remote: Installing collected packages: urllib3 - remote: Successfully installed urllib3-.* - REGEX + remote: Collecting typing-extensions==4.12.2 (from -r requirements.txt (line 2)) + remote: Downloading typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB) + remote: Downloading typing_extensions-4.12.2-py3-none-any.whl (37 kB) + remote: Installing collected packages: typing-extensions + remote: Successfully installed typing-extensions-4.12.2 + remote: -----> Inline app detected + remote: LANG=en_US.UTF-8 + remote: LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + remote: LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + remote: PATH=/app/.heroku/python/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + remote: PYTHONHASHSEED=random + remote: PYTHONHOME=/app/.heroku/python + remote: PYTHONPATH=/app + remote: PYTHONUNBUFFERED=true + remote: + remote: ['', + remote: '/app', + remote: '/app/.heroku/python/lib/python312.zip', + remote: '/app/.heroku/python/lib/python3.12', + remote: '/app/.heroku/python/lib/python3.12/lib-dynload', + remote: '/app/.heroku/python/lib/python3.12/site-packages'] + remote: + remote: Package Version + remote: ----------------- ------- + remote: pip #{PIP_VERSION} + remote: setuptools #{SETUPTOOLS_VERSION} + remote: typing_extensions 4.12.2 + remote: wheel #{WHEEL_VERSION} + remote: + remote: + OUTPUT app.commit! app.push! expect(clean_output(app.output)).to include(<<~OUTPUT) @@ -42,21 +70,21 @@ remote: -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} remote: -----> Installing SQLite3 remote: -----> Installing requirements with pip - remote: -----> Discovering process types + remote: -----> Inline app detected OUTPUT end end end context 'when requirements.txt has changed since the last build' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_version_unspecified') } + let(:app) { Hatchet::Runner.new('spec/fixtures/requirements_basic') } it 'clears the cache before installing the packages again' do app.deploy do |app| File.write('requirements.txt', 'six', mode: 'a') app.commit! app.push! - expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) + expect(clean_output(app.output)).to include(<<~OUTPUT) remote: -----> Python app detected remote: -----> No Python version was specified. Using the same version as the last build: python-#{DEFAULT_PYTHON_VERSION} remote: To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes @@ -65,32 +93,50 @@ remote: -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} remote: -----> Installing SQLite3 remote: -----> Installing requirements with pip - remote: Collecting urllib3 \\(from -r requirements.txt \\(line 1\\)\\) - remote: Downloading urllib3-.* - remote: Collecting six \\(from -r requirements.txt \\(line 2\\)\\) - remote: Downloading six-.* - remote: Downloading urllib3-.* - remote: Downloading six-.* - remote: Installing collected packages: urllib3, six - remote: Successfully installed six-.* urllib3-.* - REGEX + remote: Collecting typing-extensions==4.12.2 (from -r requirements.txt (line 2)) + remote: Downloading typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB) + remote: Collecting six (from -r requirements.txt (line 3)) + remote: Downloading six-1.16.0-py2.py3-none-any.whl.metadata (1.8 kB) + remote: Downloading typing_extensions-4.12.2-py3-none-any.whl (37 kB) + remote: Downloading six-1.16.0-py2.py3-none-any.whl (11 kB) + remote: Installing collected packages: typing-extensions, six + remote: Successfully installed six-1.16.0 typing-extensions-4.12.2 + OUTPUT end end end - context 'when requirements.txt contains popular compiled packages' do - let(:app) { Hatchet::Runner.new('spec/fixtures/requirements_compiled') } + context 'when the package manager has changed from Pipenv to pip since the last build' do + let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_version_unspecified') } - include_examples 'installs successfully using pip' + # TODO: Fix this case so the cache is actually cleared. + it 'clears the cache before installing with pip' do + app.deploy do |app| + FileUtils.rm(['Pipfile', 'Pipfile.lock']) + FileUtils.cp(FIXTURE_DIR.join('requirements_basic/requirements.txt'), '.') + app.commit! + app.push! + expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) + remote: -----> Python app detected + remote: -----> No Python version was specified. Using the same version as the last build: python-#{DEFAULT_PYTHON_VERSION} + remote: To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes + remote: -----> Using cached install of python-#{DEFAULT_PYTHON_VERSION} + remote: -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} + remote: -----> Installing SQLite3 + remote: -----> Installing requirements with pip + remote: -----> Discovering process types + REGEX + end + end end - context 'when requirements.txt contains Git requirements URLs' do - let(:app) { Hatchet::Runner.new('spec/fixtures/requirements_git') } + context 'when requirements.txt contains popular compiled packages' do + let(:app) { Hatchet::Runner.new('spec/fixtures/requirements_compiled') } include_examples 'installs successfully using pip' end - context 'when requirements.txt contains editable requirements' do + context 'when requirements.txt contains editable requirements (both VCS and local package)' do let(:buildpacks) { [:default, 'heroku-community/inline'] } let(:app) { Hatchet::Runner.new('spec/fixtures/requirements_editable', buildpacks:) } @@ -120,7 +166,7 @@ REGEX # Test rewritten paths work at runtime. - expect(app.run('bin/test-entrypoints')).to include(<<~OUTPUT) + expect(app.run('bin/test-entrypoints.sh')).to include(<<~OUTPUT) easy-install.pth:/app/.heroku/src/gunicorn easy-install.pth:/app/packages/local_package_setup_py __editable___local_package_pyproject_toml_0_0_1_finder.py:/app/packages/local_package_pyproject_toml/local_package_pyproject_toml'} @@ -166,8 +212,21 @@ it 'installs packages from setup.py' do app.deploy do |app| - expect(app.output).to include('Running setup.py develop for test') - expect(app.output).to include('Successfully installed six') + expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX, Regexp::MULTILINE)) + remote: -----> Python app detected + remote: -----> No Python version was specified. Using the buildpack default: python-#{DEFAULT_PYTHON_VERSION} + remote: To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes + remote: -----> Installing python-#{DEFAULT_PYTHON_VERSION} + remote: -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} + remote: -----> Installing SQLite3 + remote: -----> Installing requirements with pip + remote: Obtaining file:///tmp/build_.* \\(from -r requirements.txt \\(line 1\\)\\) + remote: Preparing metadata \\(setup.py\\): started + remote: Preparing metadata \\(setup.py\\): finished with status 'done' + remote: .+ + remote: Installing collected packages: six, test + remote: Running setup.py develop for test + REGEX end end end @@ -186,6 +245,20 @@ end end + context 'when requirements.txt contains an invalid requirement' do + let(:app) { Hatchet::Runner.new('spec/fixtures/requirements_invalid', allow_failure: true) } + + it 'aborts the build and displays the pip error' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: -----> Installing requirements with pip + remote: ERROR: Invalid requirement: 'an-invalid-requirement!' (from line 1 of requirements.txt) + remote: ! Push rejected, failed to compile Python app. + OUTPUT + end + end + end + context 'when requirements.txt contains GDAL but the GDAL C++ library is missing' do let(:app) { Hatchet::Runner.new('spec/fixtures/requirements_gdal', allow_failure: true) } @@ -195,6 +268,7 @@ remote: ! Hello! Package installation failed since the GDAL library was not found. remote: ! For GDAL, GEOS and PROJ support, use the Geo buildpack alongside the Python buildpack: remote: ! https://github.com/heroku/heroku-geo-buildpack + remote: ! Push rejected, failed to compile Python app. OUTPUT end end diff --git a/spec/hatchet/pipenv_spec.rb b/spec/hatchet/pipenv_spec.rb index ff5b5ba1c..6c0e2fa97 100644 --- a/spec/hatchet/pipenv_spec.rb +++ b/spec/hatchet/pipenv_spec.rb @@ -30,6 +30,7 @@ remote: ! For a list of the supported Python versions, see: remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end @@ -63,10 +64,14 @@ end context 'with a Pipfile.lock but no Python version specified' do - let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_version_unspecified') } + let(:buildpacks) { [:default, 'heroku-community/inline'] } + let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_version_unspecified', buildpacks:) } - it 'builds with the default Python version' do + # TODO: Run this on Heroku-22 too, once it has also migrated to the new build infrastructure. + # (Currently the test fails on the old infrastructure due to subtle differences in system PATH elements.) + it 'builds with the default Python version', stacks: %w[heroku-20 heroku-24] do app.deploy do |app| + # TODO: We should not be leaking the Pipenv installation into the app environment. expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) remote: -----> Python app detected remote: -----> No Python version was specified. Using the buildpack default: python-#{DEFAULT_PYTHON_VERSION} @@ -76,6 +81,37 @@ remote: -----> Installing dependencies with Pipenv #{PIPENV_VERSION} remote: Installing dependencies from Pipfile.lock \\(.+\\)... remote: -----> Installing SQLite3 + remote: -----> Inline app detected + remote: LANG=en_US.UTF-8 + remote: LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + remote: LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib: + remote: PATH=/app/.heroku/python/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + remote: PYTHONHASHSEED=random + remote: PYTHONHOME=/app/.heroku/python + remote: PYTHONPATH=/app + remote: PYTHONUNBUFFERED=true + remote: + remote: \\['', + remote: '/app', + remote: '/app/.heroku/python/lib/python312.zip', + remote: '/app/.heroku/python/lib/python3.12', + remote: '/app/.heroku/python/lib/python3.12/lib-dynload', + remote: '/app/.heroku/python/lib/python3.12/site-packages'\\] + remote: + remote: Package Version + remote: ----------------- --------- + remote: certifi .+ + remote: distlib .+ + remote: filelock .+ + remote: pip #{PIP_VERSION} + remote: pipenv #{PIPENV_VERSION} + remote: platformdirs .+ + remote: setuptools #{SETUPTOOLS_VERSION} + remote: typing_extensions 4.12.2 + remote: virtualenv .+ + remote: wheel #{WHEEL_VERSION} + remote: + remote: \\ REGEX end end @@ -101,6 +137,7 @@ remote: ! For a list of the supported Python versions, see: remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end @@ -126,6 +163,7 @@ remote: ! For a list of the supported Python versions, see: remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end @@ -207,7 +245,7 @@ let(:allow_failure) { false } let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_full_version', allow_failure:) } - it 'builds with the outdated Python version specified' do + it 'builds with the outdated Python version specified and displays a deprecation warning' do app.deploy do |app| expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) remote: -----> Python app detected @@ -241,6 +279,7 @@ remote: ! For a list of the supported Python versions, see: remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end @@ -260,6 +299,7 @@ remote: ! For a list of the supported Python versions, see: remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end @@ -305,6 +345,32 @@ end end + context 'when the package manager has changed from pip to Pipenv since the last build' do + let(:app) { Hatchet::Runner.new('spec/fixtures/python_version_unspecified') } + + # TODO: Fix this case so the cache is actually cleared. + it 'clears the cache before installing with Pipenv' do + app.deploy do |app| + FileUtils.rm('requirements.txt') + FileUtils.cp(FIXTURE_DIR.join('pipenv_python_version_unspecified/Pipfile'), '.') + FileUtils.cp(FIXTURE_DIR.join('pipenv_python_version_unspecified/Pipfile.lock'), '.') + app.commit! + app.push! + expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) + remote: -----> Python app detected + remote: -----> No Python version was specified. Using the same version as the last build: python-#{DEFAULT_PYTHON_VERSION} + remote: To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes + remote: -----> Using cached install of python-#{DEFAULT_PYTHON_VERSION} + remote: -----> Installing pip #{PIP_VERSION}, setuptools #{SETUPTOOLS_VERSION} and wheel #{WHEEL_VERSION} + remote: -----> Installing dependencies with Pipenv #{PIPENV_VERSION} + remote: Installing dependencies from Pipfile.lock \\(.+\\)... + remote: -----> Installing SQLite3 + remote: -----> Discovering process types + REGEX + end + end + end + context 'when there is both a Pipfile.lock and a requirements.txt' do let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_and_requirements_txt') } @@ -338,6 +404,7 @@ remote: Your Pipfile.lock \\(.+\\) is out of date. Expected: \\(.+\\). remote: .+ remote: ERROR:: Aborting deploy + remote: ! Push rejected, failed to compile Python app. REGEX end end @@ -373,7 +440,7 @@ REGEX # Test rewritten paths work at runtime. - expect(app.run('bin/test-entrypoints')).to include(<<~OUTPUT) + expect(app.run('bin/test-entrypoints.sh')).to include(<<~OUTPUT) easy-install.pth:/app/.heroku/src/gunicorn easy-install.pth:/app/packages/local_package_setup_py __editable___local_package_pyproject_toml_0_0_1_finder.py:/app/packages/local_package_pyproject_toml/local_package_pyproject_toml'} diff --git a/spec/hatchet/python_update_warning_spec.rb b/spec/hatchet/python_update_warning_spec.rb index 6c7f66d62..df909ee73 100644 --- a/spec/hatchet/python_update_warning_spec.rb +++ b/spec/hatchet/python_update_warning_spec.rb @@ -30,13 +30,17 @@ remote: ! For a list of the supported Python versions, see: remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end end +# NOTE: We use the oldest patch releases (ie the '.0' releases) since we also want to test against +# the oldest Python versions available to users. This is particularly important given that older +# patch releases will bundle older pip, and the buildpack uses that pip during bootstrapping. RSpec.describe 'Python update warnings' do - context 'with a runtime.txt containing python-3.8.12' do + context 'with a runtime.txt containing python-3.8.0' do let(:allow_failure) { false } let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.8_outdated', allow_failure:) } @@ -60,7 +64,7 @@ remote: ! A Python security update is available! Upgrade as soon as possible to: python-#{LATEST_PYTHON_3_8} remote: ! See: https://devcenter.heroku.com/articles/python-runtimes remote: ! - remote: -----> Installing python-3.8.12 + remote: -----> Installing python-3.8.0 REGEX end end @@ -70,31 +74,31 @@ let(:allow_failure) { true } # We only support Python 3.8 on Heroku-20 and older. - include_examples 'aborts the build without showing an update warning', '3.8.12' + include_examples 'aborts the build without showing an update warning', '3.8.0' end end - context 'with a runtime.txt containing python-3.9.12' do + context 'with a runtime.txt containing python-3.9.0' do let(:allow_failure) { false } let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.9_outdated', allow_failure:) } context 'when using Heroku-22 or older', stacks: %w[heroku-20 heroku-22] do - include_examples 'warns there is a Python update available', '3.9.12', LATEST_PYTHON_3_9 + include_examples 'warns there is a Python update available', '3.9.0', LATEST_PYTHON_3_9 end context 'when using Heroku-24', stacks: %w[heroku-24] do let(:allow_failure) { true } # We only support Python 3.9 on Heroku-22 and older. - include_examples 'aborts the build without showing an update warning', '3.9.12' + include_examples 'aborts the build without showing an update warning', '3.9.0' end end - context 'with a runtime.txt containing python-3.10.5' do + context 'with a runtime.txt containing python-3.10.0' do let(:allow_failure) { false } let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.10_outdated', allow_failure:) } - include_examples 'warns there is a Python update available', '3.10.5', LATEST_PYTHON_3_10 + include_examples 'warns there is a Python update available', '3.10.0', LATEST_PYTHON_3_10 end context 'with a runtime.txt containing python-3.11.0' do diff --git a/spec/hatchet/python_version_spec.rb b/spec/hatchet/python_version_spec.rb index f4d96c376..a324089ba 100644 --- a/spec/hatchet/python_version_spec.rb +++ b/spec/hatchet/python_version_spec.rb @@ -31,6 +31,7 @@ remote: ! For a list of the supported Python versions, see: remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end @@ -102,6 +103,7 @@ remote: ! For a list of the supported Python versions, see: remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end @@ -127,6 +129,7 @@ remote: ! For a list of the supported Python versions, see: remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes remote: ! + remote: ! Push rejected, failed to compile Python app. OUTPUT end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6d72bc8ee..93e3b2bdb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,8 @@ require 'rspec/retry' require 'hatchet' +FIXTURE_DIR = Pathname.new(__FILE__).parent.join('fixtures') + LATEST_PYTHON_3_8 = '3.8.20' LATEST_PYTHON_3_9 = '3.9.20' LATEST_PYTHON_3_10 = '3.10.15'