Skip to content

Commit

Permalink
Improve pip package manager tests
Browse files Browse the repository at this point in the history
Whilst writing the tests for the upcoming Poetry support, I made a few
changes to the overall package manager testing strategy (such as using
a testing buildpack to verify that at build time the tools and env vars
are configured correctly) - which I've split out of the later PRs for
easier review.

In particular, the new testing buildpack added here is what helped me
debug and locate this upstream lifecycle build time env vars bug:
buildpacks/lifecycle#1393

GUS-W-16617242.
  • Loading branch information
edmorley committed Aug 29, 2024
1 parent e988580 commit 1737e8b
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 80 deletions.
2 changes: 1 addition & 1 deletion tests/fixtures/pip_basic/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# This package has been picked since it has no dependencies and is small/fast to install.
typing-extensions==4.7.1
typing-extensions==4.12.2
2 changes: 1 addition & 1 deletion tests/fixtures/pip_editable_git_compiled/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
# The URL to the package is specified via env var, to test that user-provided env vars
# are propagated to pip for use by its env var interpolation feature.

-e git+${WHEEL_PACKAGE_URL}@0.40.0#egg=extension.dist&subdirectory=tests/testdata/extension.dist
-e git+${WHEEL_PACKAGE_URL}@0.44.0#egg=extension.dist&subdirectory=tests/testdata/extension.dist
22 changes: 22 additions & 0 deletions tests/fixtures/testing_buildpack/bin/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

# Check that:
# - The correct env vars are set for later buildpacks.
# - Python's sys.path is correct.
# - The correct version of pip was installed, into its own layer.
# - Both the package manager and Python can find the typing-extensions package.
# - The typing-extensions package was installed into a separate dependencies layer.

set -euo pipefail

echo
echo "## Testing buildpack ##"

printenv | sort | grep -vE '^(_|CNB_.+|HOME|HOSTNAME|OLDPWD|PWD|SHLVL)='
echo
python -c 'import pprint, sys; pprint.pp(sys.path)'
echo
pip --version
pip list
pip install --dry-run typing-extensions
python -c 'import typing_extensions; print(typing_extensions)'
3 changes: 3 additions & 0 deletions tests/fixtures/testing_buildpack/bin/detect
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

exit 0
6 changes: 6 additions & 0 deletions tests/fixtures/testing_buildpack/buildpack.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
api = "0.11"

[buildpack]
id = "testing-buildpack"
version = "0.0.0"
clear-env = true
8 changes: 4 additions & 4 deletions tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
//! These tests are not run via automatic integration test discovery, but instead are
//! imported in main.rs so that they have access to private APIs (see comment in main.rs).

use libcnb_test::BuildConfig;
use std::env;
use std::path::Path;

mod detect_test;
mod django_test;
mod package_manager_test;
mod pip_test;
mod python_version_test;

use libcnb_test::BuildConfig;
use std::env;
use std::path::Path;

const LATEST_PYTHON_3_7: &str = "3.7.17";
const LATEST_PYTHON_3_8: &str = "3.8.19";
const LATEST_PYTHON_3_9: &str = "3.9.19";
Expand Down
172 changes: 98 additions & 74 deletions tests/pip_test.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use crate::packaging_tool_versions::PIP_VERSION;
use crate::tests::{default_build_config, DEFAULT_PYTHON_VERSION};
use crate::tests::{default_build_config, DEFAULT_PYTHON_VERSION, LATEST_PYTHON_3_11};
use indoc::{formatdoc, indoc};
use libcnb_test::{assert_contains, assert_empty, BuildpackReference, PackResult, TestRunner};

#[test]
#[ignore = "integration test"]
#[allow(clippy::too_many_lines)]
fn pip_basic_install_and_cache_reuse() {
let config = default_build_config("tests/fixtures/pip_basic");
let mut config = default_build_config("tests/fixtures/pip_basic");
config.buildpacks(vec![
BuildpackReference::CurrentCrate,
BuildpackReference::Other("file://tests/fixtures/testing_buildpack".to_string()),
]);

TestRunner::default().build(&config, |context| {
assert_empty!(context.pack_stderr);
Expand All @@ -23,36 +28,61 @@ fn pip_basic_install_and_cache_reuse() {
[Installing dependencies using pip]
Running pip install
Collecting typing-extensions==4.7.1 (from -r requirements.txt (line 2))
Downloading typing_extensions-4.7.1-py3-none-any.whl.metadata (3.1 kB)
Downloading typing_extensions-4.7.1-py3-none-any.whl (33 kB)
Collecting typing-extensions==4.12.2 (from -r requirements.txt (line 2))
Downloading typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB)
Downloading typing_extensions-4.12.2-py3-none-any.whl (37 kB)
Installing collected packages: typing-extensions
Successfully installed typing-extensions-4.7.1
Successfully installed typing-extensions-4.12.2
## Testing buildpack ##
CPATH=/layers/heroku_python/python/include/python3.12:/layers/heroku_python/python/include
LANG=C.UTF-8
LD_LIBRARY_PATH=/layers/heroku_python/python/lib:/layers/heroku_python/dependencies/lib
LIBRARY_PATH=/layers/heroku_python/python/lib:/layers/heroku_python/dependencies/lib
PATH=/layers/heroku_python/python/bin:/layers/heroku_python/dependencies/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PIP_CACHE_DIR=/layers/heroku_python/pip-cache
PIP_DISABLE_PIP_VERSION_CHECK=1
PKG_CONFIG_PATH=/layers/heroku_python/python/lib/pkgconfig
PYTHONHOME=/layers/heroku_python/python
PYTHONUNBUFFERED=1
PYTHONUSERBASE=/layers/heroku_python/dependencies
SOURCE_DATE_EPOCH=315532801
['',
'/layers/heroku_python/python/lib/python312.zip',
'/layers/heroku_python/python/lib/python3.12',
'/layers/heroku_python/python/lib/python3.12/lib-dynload',
'/layers/heroku_python/dependencies/lib/python3.12/site-packages',
'/layers/heroku_python/python/lib/python3.12/site-packages']
pip {PIP_VERSION} from /layers/heroku_python/python/lib/python3.12/site-packages/pip (python 3.12)
Package Version
----------------- -------
pip {PIP_VERSION}
typing_extensions 4.12.2
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: typing-extensions in /layers/heroku_python/dependencies/lib/python3.12/site-packages (4.12.2)
<module 'typing_extensions' from '/layers/heroku_python/dependencies/lib/python3.12/site-packages/typing_extensions.py'>
"}
);

// Check that:
// - The correct env vars are set at run-time.
// - pip is available at run-time too (and not just during the build).
// - The correct version of pip was installed.
// - pip uses (via 'PYTHONUSERBASE') the user site-packages in the dependencies
// layer, and so can find the typing-extensions package installed there.
// - The "pip update available" warning is not shown (since it should be suppressed).
// - The system site-packages directory is protected against running 'pip install'
// without having passed '--user'.
// Check that at run-time:
// - The correct env vars are set.
// - pip is available (rather than just during the build).
// - Both pip and Python can find the typing-extensions package.
let command_output = context.run_shell_command(
indoc! {"
set -euo pipefail
printenv | sort | grep -vE '^(_|HOME|HOSTNAME|OLDPWD|PWD|SHLVL)='
echo
pip list
pip install --dry-run typing-extensions
python -c 'import typing_extensions'
"}
);
assert_empty!(command_output.stderr);
assert_contains!(
assert_eq!(
command_output.stdout,
&formatdoc! {"
formatdoc! {"
LANG=C.UTF-8
LD_LIBRARY_PATH=/layers/heroku_python/python/lib:/layers/heroku_python/dependencies/lib
PATH=/layers/heroku_python/dependencies/bin:/layers/heroku_python/python/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Expand All @@ -64,10 +94,8 @@ fn pip_basic_install_and_cache_reuse() {
Package Version
----------------- -------
pip {PIP_VERSION}
typing_extensions 4.7.1
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: typing-extensions in /layers/heroku_python/dependencies/lib/"
}
typing_extensions 4.12.2
"}
);

context.rebuild(&config, |rebuild_context| {
Expand All @@ -85,72 +113,66 @@ fn pip_basic_install_and_cache_reuse() {
[Installing dependencies using pip]
Using cached pip download/wheel cache
Running pip install
Collecting typing-extensions==4.7.1 (from -r requirements.txt (line 2))
Using cached typing_extensions-4.7.1-py3-none-any.whl.metadata (3.1 kB)
Using cached typing_extensions-4.7.1-py3-none-any.whl (33 kB)
Collecting typing-extensions==4.12.2 (from -r requirements.txt (line 2))
Using cached typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB)
Using cached typing_extensions-4.12.2-py3-none-any.whl (37 kB)
Installing collected packages: typing-extensions
Successfully installed typing-extensions-4.7.1
Successfully installed typing-extensions-4.12.2
"}
);
});
});
}

// This tests that:
// - The cached layers are correctly invalidated when Python/other versions change.
// - The layer metadata written by older versions of the buildpack are still compatible.
#[test]
#[ignore = "integration test"]
fn pip_cache_invalidation_with_compatible_metadata() {
let config = default_build_config("tests/fixtures/pip_basic");

fn pip_cache_invalidation_python_version_changed() {
TestRunner::default().build(
config.clone().buildpacks([BuildpackReference::Other(
"docker://docker.io/heroku/buildpack-python:0.14.0".to_string(),
)]),
default_build_config("tests/fixtures/python_3.11"),
|context| {
context.rebuild(config, |rebuild_context| {
assert_empty!(rebuild_context.pack_stderr);
assert_contains!(
rebuild_context.pack_stdout,
&formatdoc! {"
[Determining Python version]
No Python version specified, using the current default of Python {DEFAULT_PYTHON_VERSION}.
To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes
[Installing Python and pip]
Discarding cache since:
- The Python version has changed from 3.12.4 to {DEFAULT_PYTHON_VERSION}
- The pip version has changed from 24.1.2 to {PIP_VERSION}
Installing Python {DEFAULT_PYTHON_VERSION}
Installing pip {PIP_VERSION}
[Installing dependencies using pip]
Discarding cached pip download/wheel cache
Running pip install
Collecting typing-extensions==4.7.1 (from -r requirements.txt (line 2))
Downloading typing_extensions-4.7.1-py3-none-any.whl.metadata (3.1 kB)
Downloading typing_extensions-4.7.1-py3-none-any.whl (33 kB)
Installing collected packages: typing-extensions
Successfully installed typing-extensions-4.7.1
"}
);
});
context.rebuild(
default_build_config("tests/fixtures/pip_basic"),
|rebuild_context| {
assert_empty!(rebuild_context.pack_stderr);
assert_contains!(
rebuild_context.pack_stdout,
&formatdoc! {"
[Determining Python version]
No Python version specified, using the current default of Python {DEFAULT_PYTHON_VERSION}.
To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes
[Installing Python and pip]
Discarding cache since:
- The Python version has changed from {LATEST_PYTHON_3_11} to {DEFAULT_PYTHON_VERSION}
Installing Python {DEFAULT_PYTHON_VERSION}
Installing pip {PIP_VERSION}
[Installing dependencies using pip]
Discarding cached pip download/wheel cache
Running pip install
Collecting typing-extensions==4.12.2 (from -r requirements.txt (line 2))
Downloading typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB)
Downloading typing_extensions-4.12.2-py3-none-any.whl (37 kB)
Installing collected packages: typing-extensions
Successfully installed typing-extensions-4.12.2
"}
);
},
);
},
);
}

// This tests that:
// - The cached layers are correctly invalidated when the layer metadata was incompatible.
// - That a suitable message was output explaining why.
// This tests that cached layers from a previous buildpack version are compatible, or if we've
// decided to break compatibility recently, that the layers are at least invalidated gracefully.
#[test]
#[ignore = "integration test"]
fn pip_cache_invalidation_with_incompatible_metadata() {
fn pip_cache_previous_buildpack_version() {
let config = default_build_config("tests/fixtures/pip_basic");

TestRunner::default().build(
config.clone().buildpacks([BuildpackReference::Other(
"docker://docker.io/heroku/buildpack-python:0.13.0".to_string(),
"docker://docker.io/heroku/buildpack-python:0.14.0".to_string(),
)]),
|context| {
context.rebuild(config, |rebuild_context| {
Expand All @@ -163,18 +185,20 @@ fn pip_cache_invalidation_with_incompatible_metadata() {
To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes
[Installing Python and pip]
Discarding cache since the buildpack cache format has changed
Discarding cache since:
- The Python version has changed from 3.12.4 to {DEFAULT_PYTHON_VERSION}
- The pip version has changed from 24.1.2 to {PIP_VERSION}
Installing Python {DEFAULT_PYTHON_VERSION}
Installing pip {PIP_VERSION}
[Installing dependencies using pip]
Discarding cached pip download/wheel cache
Running pip install
Collecting typing-extensions==4.7.1 (from -r requirements.txt (line 2))
Downloading typing_extensions-4.7.1-py3-none-any.whl.metadata (3.1 kB)
Downloading typing_extensions-4.7.1-py3-none-any.whl (33 kB)
Collecting typing-extensions==4.12.2 (from -r requirements.txt (line 2))
Downloading typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB)
Downloading typing_extensions-4.12.2-py3-none-any.whl (37 kB)
Installing collected packages: typing-extensions
Successfully installed typing-extensions-4.7.1
Successfully installed typing-extensions-4.12.2
"}
);
});
Expand All @@ -193,11 +217,11 @@ fn pip_cache_invalidation_with_incompatible_metadata() {
fn pip_editable_git_compiled() {
TestRunner::default().build(
default_build_config( "tests/fixtures/pip_editable_git_compiled")
.env("WHEEL_PACKAGE_URL", "https://github.com/pypa/wheel"),
.env("WHEEL_PACKAGE_URL", "https://github.com/pypa/wheel.git"),
|context| {
assert_contains!(
context.pack_stdout,
"Cloning https://github.com/pypa/wheel (to revision 0.40.0) to /layers/heroku_python/dependencies/src/extension-dist"
"Cloning https://github.com/pypa/wheel.git (to revision 0.44.0) to /layers/heroku_python/dependencies/src/extension-dist"
);
},
);
Expand Down

0 comments on commit 1737e8b

Please sign in to comment.