Avoid using 3.8 in tests when feasible (#13680)

See https://github.com/astral-sh/uv/issues/13676

Python 3.8 is EOL, and we should avoid using it for tests unless we're
covering 3.8-specific behaviors.

In some cases, the Python version we require for the test is arbitrary,
for which I've added a new constant (set to 3.12). In other cases, we
require an older Python version for compatibility with various packages,
like `setuptools`. For those, we can _probably_ switch to a newer
version but it'd be more invasive as we'd need to change our constraints
or `exclude-newer`.

Adds a feature flag for cases where we still need to use the EOL
version.

There's a lot of usage in packse scenarios too, I'll update those
separately because the diff will be large.
This commit is contained in:
Zanie Blue 2025-05-27 13:34:49 -05:00 committed by GitHub
parent df00189ec5
commit 32b949c3a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 150 additions and 138 deletions

View File

@ -156,6 +156,7 @@ default-tests = [
"pypi",
"python",
"python-managed",
"python-eol",
"slow-tests",
"test-ecosystem"
]
@ -169,6 +170,8 @@ pypi = []
python = []
# Introduces a testing dependency on a local Python installation with specific patch versions.
python-patch = []
# Introduces a testing dependency on a local Python installation with an EOL version.
python-eol = []
# Introduces a testing dependency on managed Python installations.
python-managed = []
# Include "slow" test cases.

View File

@ -1,4 +1,4 @@
use crate::common::{TestContext, uv_snapshot};
use crate::common::{DEFAULT_PYTHON_VERSION, TestContext, uv_snapshot};
use anyhow::Result;
use assert_cmd::assert::OutputAssertExt;
use assert_fs::prelude::*;
@ -898,7 +898,7 @@ fn build_constraints() -> Result<()> {
#[test]
fn build_sha() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let filters = context
.filters()
.into_iter()

View File

@ -33,6 +33,7 @@ use uv_static::EnvVars;
static EXCLUDE_NEWER: &str = "2024-03-25T00:00:00Z";
pub const PACKSE_VERSION: &str = "0.3.46";
pub const DEFAULT_PYTHON_VERSION: &str = "3.12";
/// Using a find links url allows using `--index-url` instead of `--extra-index-url` in tests
/// to prevent dependency confusion attacks against our test suite.

View File

@ -10982,7 +10982,7 @@ fn repeated_index_cli_reversed() -> Result<()> {
#[test]
fn add_with_build_constraints() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"

View File

@ -2365,7 +2365,7 @@ fn init_requires_python_version() -> Result<()> {
})?;
let child = context.temp_dir.join("foo");
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg(&child).arg("--python").arg("3.8"), @r###"
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg(&child).arg("--python").arg("3.9"), @r###"
success: true
exit_code: 0
----- stdout -----
@ -2380,15 +2380,15 @@ fn init_requires_python_version() -> Result<()> {
filters => context.filters(),
}, {
assert_snapshot!(
pyproject_toml, @r###"
pyproject_toml, @r#"
[project]
name = "foo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = []
"###
"#
);
});
@ -2397,7 +2397,7 @@ fn init_requires_python_version() -> Result<()> {
filters => context.filters(),
}, {
assert_snapshot!(
python_version, @"3.8"
python_version, @"3.9"
);
});
@ -2466,9 +2466,9 @@ fn init_requires_python_specifiers() -> Result<()> {
/// Run `uv init`, inferring the `requires-python` from the `.python-version` file.
#[test]
fn init_requires_python_version_file() -> Result<()> {
let context = TestContext::new_with_versions(&["3.8", "3.12"]);
let context = TestContext::new_with_versions(&["3.9", "3.12"]);
context.temp_dir.child(".python-version").write_str("3.8")?;
context.temp_dir.child(".python-version").write_str("3.9")?;
let child = context.temp_dir.join("foo");
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg(&child), @r###"
@ -2485,15 +2485,15 @@ fn init_requires_python_version_file() -> Result<()> {
filters => context.filters(),
}, {
assert_snapshot!(
pyproject_toml, @r###"
pyproject_toml, @r#"
[project]
name = "foo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = []
"###
"#
);
});

View File

@ -2220,6 +2220,7 @@ fn lock_dependency_extra() -> Result<()> {
}
/// Lock a project with a dependency that has a conditional extra.
#[cfg(feature = "python-eol")]
#[test]
fn lock_conditional_dependency_extra() -> Result<()> {
let context = TestContext::new("3.12");

View File

@ -17,7 +17,9 @@ use wiremock::{Mock, MockServer, ResponseTemplate};
use uv_fs::Simplified;
use uv_static::EnvVars;
use crate::common::{TestContext, download_to_disk, packse_index_url, uv_snapshot};
use crate::common::{
DEFAULT_PYTHON_VERSION, TestContext, download_to_disk, packse_index_url, uv_snapshot,
};
#[test]
fn compile_requirements_in() -> Result<()> {
@ -2105,6 +2107,7 @@ fn omit_non_matching_annotation() -> Result<()> {
/// Test that we select the last 3.8 compatible numpy version instead of trying to compile an
/// incompatible sdist <https://github.com/astral-sh/uv/issues/388>
#[cfg(feature = "python-eol")]
#[test]
fn compile_numpy_py38() -> Result<()> {
let context = TestContext::new("3.8");
@ -5563,6 +5566,7 @@ coverage = ["example[test]", "extras>=0.0.1,<=0.0.2"]
///
/// `voluptuous==0.15.1` requires Python 3.9 or later, so we should resolve to an earlier version
/// and avoiding building 0.15.1 at all.
#[cfg(feature = "python-eol")]
#[test]
fn requires_python_prefetch() -> Result<()> {
let context = TestContext::new("3.8").with_exclude_newer("2025-01-01T00:00:00Z");
@ -13551,7 +13555,7 @@ fn ignore_invalid_constraint() -> Result<()> {
/// Include a `build_constraints.txt` file with an incompatible constraint.
#[test]
fn incompatible_build_constraint() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==1.2")?;
@ -13580,7 +13584,7 @@ fn incompatible_build_constraint() -> Result<()> {
/// Include a `build_constraints.txt` file with a compatible constraint.
#[test]
fn compatible_build_constraint() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==1.2")?;
@ -13590,7 +13594,7 @@ fn compatible_build_constraint() -> Result<()> {
uv_snapshot!(context.pip_compile()
.arg("requirements.txt")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r###"
.arg("build_constraints.txt"), @r"
success: true
exit_code: 0
----- stdout -----
@ -13601,7 +13605,7 @@ fn compatible_build_constraint() -> Result<()> {
----- stderr -----
Resolved 1 package in [TIME]
"###
"
);
Ok(())
@ -13610,7 +13614,7 @@ fn compatible_build_constraint() -> Result<()> {
/// Include `build-constraint-dependencies` in pyproject.toml with an incompatible constraint.
#[test]
fn incompatible_build_constraint_in_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[build-system]
@ -13648,6 +13652,7 @@ build-constraint-dependencies = [
}
/// Include `build-constraint-dependencies` in pyproject.toml with a compatible constraint.
#[cfg(feature = "python-eol")]
#[test]
fn compatible_build_constraint_in_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
@ -13691,7 +13696,7 @@ build-constraint-dependencies = [
/// Merge `build_constraints.txt` with `build-constraint-dependencies` in pyproject.toml with an incompatible constraint.
#[test]
fn incompatible_build_constraint_merged_with_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
// incompatible setuptools version in pyproject.toml, compatible in build_constraints.txt
let constraints_txt = context.temp_dir.child("build_constraints.txt");
@ -13776,7 +13781,7 @@ build-constraint-dependencies = [
/// Merge CLI args `build_constraints.txt` with `build-constraint-dependencies` in pyproject.toml with a compatible constraint.
#[test]
fn compatible_build_constraint_merged_with_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
// incompatible setuptools version in pyproject.toml, compatible in build_constraints.txt
let constraints_txt = context.temp_dir.child("build_constraints.txt");
@ -13942,7 +13947,7 @@ fn invalid_extra() -> Result<()> {
#[test]
#[cfg(not(windows))]
fn symlink() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio")?;
@ -13958,7 +13963,7 @@ fn symlink() -> Result<()> {
uv_snapshot!(context.pip_compile()
.arg("requirements.in")
.arg("--output-file")
.arg("requirements-symlink.txt"), @r###"
.arg("requirements-symlink.txt"), @r"
success: true
exit_code: 0
----- stdout -----
@ -13966,18 +13971,14 @@ fn symlink() -> Result<()> {
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --output-file requirements-symlink.txt
anyio==4.3.0
# via -r requirements.in
exceptiongroup==1.2.0
# via anyio
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
typing-extensions==4.10.0
# via anyio
----- stderr -----
Resolved 5 packages in [TIME]
"###
Resolved 3 packages in [TIME]
"
);
// The symlink should still be a symlink.
@ -14486,6 +14487,7 @@ fn unsupported_requires_python_static_metadata() -> Result<()> {
/// dynamic metadata.
///
/// See: <https://github.com/astral-sh/uv/issues/8767>
#[cfg(feature = "python-eol")]
#[test]
fn unsupported_requires_python_dynamic_metadata() -> Result<()> {
let context = TestContext::new("3.8").with_exclude_newer("2024-11-04T00:00:00Z");
@ -14986,6 +14988,7 @@ fn compile_lowest_extra_unpinned_warning() -> Result<()> {
Ok(())
}
#[cfg(feature = "python-eol")]
#[test]
fn disjoint_requires_python() -> Result<()> {
let context = TestContext::new("3.8").with_exclude_newer("2025-01-29T00:00:00Z");
@ -15086,6 +15089,7 @@ fn dynamic_version_source_dist() -> Result<()> {
Ok(())
}
#[cfg(feature = "python-eol")]
#[test]
fn max_python_requirement() -> Result<()> {
let context = TestContext::new("3.8").with_exclude_newer("2024-12-18T00:00:00Z");
@ -15994,6 +15998,7 @@ fn directory_and_group() -> Result<()> {
}
/// See: <https://github.com/astral-sh/uv/issues/10957>
#[cfg(feature = "python-eol")]
#[test]
fn compile_preserve_requires_python_split() -> Result<()> {
let context = TestContext::new("3.8").with_exclude_newer("2025-01-01T00:00:00Z");

View File

@ -18,8 +18,8 @@ use wiremock::{
#[cfg(feature = "git")]
use crate::common::{self, decode_token};
use crate::common::{
TestContext, build_vendor_links_url, download_to_disk, get_bin, uv_snapshot, venv_bin_path,
venv_to_interpreter,
DEFAULT_PYTHON_VERSION, TestContext, build_vendor_links_url, download_to_disk, get_bin,
uv_snapshot, venv_bin_path, venv_to_interpreter,
};
use uv_fs::Simplified;
use uv_static::EnvVars;
@ -1949,7 +1949,7 @@ async fn install_deduplicated_indices() {
#[test]
#[cfg(feature = "git")]
fn install_git_public_https() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
uv_snapshot!(
context
@ -1974,7 +1974,7 @@ fn install_git_public_https() {
#[test]
#[cfg(feature = "git")]
fn install_implicit_git_public_https() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
uv_snapshot!(
context
@ -1999,7 +1999,7 @@ fn install_implicit_git_public_https() {
#[test]
#[cfg(feature = "git")]
fn update_ref_git_public_https() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
uv_snapshot!(
context
@ -2046,7 +2046,7 @@ fn update_ref_git_public_https() {
#[test]
#[cfg(feature = "git")]
fn install_git_public_https_missing_branch_or_tag() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let mut filters = context.filters();
// Windows does not style the command the same as Unix, so we must omit it from the snapshot
@ -2075,7 +2075,7 @@ fn install_git_public_https_missing_branch_or_tag() {
#[test]
#[cfg(feature = "git")]
fn install_git_public_https_missing_commit() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let mut filters = context.filters();
// Windows does not style the command the same as Unix, so we must omit it from the snapshot
@ -2117,7 +2117,7 @@ fn install_git_public_https_missing_commit() {
fn install_git_private_https_pat() {
use crate::common::decode_token;
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
let package = format!(
"uv-private-pypackage@ git+https://{token}@github.com/astral-test/uv-private-pypackage"
@ -2144,7 +2144,7 @@ fn install_git_private_https_pat() {
#[test]
#[cfg(all(not(windows), feature = "git"))]
fn install_git_private_https_pat_mixed_with_public() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
let package = format!(
@ -2172,7 +2172,7 @@ fn install_git_private_https_pat_mixed_with_public() {
#[test]
#[cfg(all(not(windows), feature = "git"))]
fn install_git_private_https_multiple_pat() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let token_1 = decode_token(common::READ_ONLY_GITHUB_TOKEN);
let token_2 = decode_token(common::READ_ONLY_GITHUB_TOKEN_2);
@ -2204,7 +2204,7 @@ fn install_git_private_https_multiple_pat() {
#[test]
#[cfg(feature = "git")]
fn install_git_private_https_pat_at_ref() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
let mut filters = context.filters();
@ -2246,7 +2246,7 @@ fn install_git_private_https_pat_at_ref() {
#[cfg(feature = "git")]
#[ignore]
fn install_git_private_https_pat_and_username() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
let user = "astral-test-bot";
@ -2270,7 +2270,7 @@ fn install_git_private_https_pat_and_username() {
#[test]
#[cfg(all(not(windows), feature = "git"))]
fn install_git_private_https_pat_not_authorized() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
// A revoked token
let token = "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";
@ -2309,7 +2309,7 @@ fn install_git_private_https_pat_not_authorized() {
#[test]
#[cfg(not(windows))]
fn install_github_artifact_private_https_pat_mixed_with_public() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
let private_package = format!(
@ -2339,7 +2339,7 @@ fn install_github_artifact_private_https_pat_mixed_with_public() {
#[test]
#[cfg(not(windows))]
fn install_github_artifact_private_https_multiple_pat() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let token_1 = decode_token(common::READ_ONLY_GITHUB_TOKEN);
let token_2 = decode_token(common::READ_ONLY_GITHUB_TOKEN_2);
@ -2373,7 +2373,7 @@ fn install_github_artifact_private_https_multiple_pat() {
#[test]
#[cfg(feature = "git")]
fn install_git_private_https_interactive() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let package = "uv-private-pypackage@ git+https://github.com/astral-test/uv-private-pypackage";
@ -6343,7 +6343,7 @@ fn already_installed_multiple_versions() -> Result<()> {
#[test]
#[cfg(feature = "git")]
fn already_installed_remote_url() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
// First, install from the remote URL
uv_snapshot!(context.filters(), context.pip_install().arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage"), @r###"
@ -8262,7 +8262,7 @@ fn install_unsupported_environment_yml() -> Result<()> {
/// Include a `build_constraints.txt` file with an incompatible constraint.
#[test]
fn incompatible_build_constraint() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools==1")?;
@ -8289,7 +8289,7 @@ fn incompatible_build_constraint() -> Result<()> {
/// Include a `build_constraints.txt` file with a compatible constraint.
#[test]
fn compatible_build_constraint() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools>=40")?;
@ -8297,7 +8297,7 @@ fn compatible_build_constraint() -> Result<()> {
uv_snapshot!(context.pip_install()
.arg("requests==1.2")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r###"
.arg("build_constraints.txt"), @r"
success: true
exit_code: 0
----- stdout -----
@ -8307,7 +8307,7 @@ fn compatible_build_constraint() -> Result<()> {
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"###
"
);
Ok(())
@ -8316,7 +8316,7 @@ fn compatible_build_constraint() -> Result<()> {
/// Include `build-constraint-dependencies` in pyproject.toml with an incompatible constraint.
#[test]
fn incompatible_build_constraint_in_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
@ -8345,6 +8345,7 @@ build-constraint-dependencies = [
}
/// Include a `build_constraints.txt` file with a compatible constraint.
#[cfg(feature = "python-eol")]
#[test]
fn compatible_build_constraint_in_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
@ -8359,7 +8360,7 @@ build-constraint-dependencies = [
)?;
uv_snapshot!(context.pip_install()
.arg("requests==1.2"), @r###"
.arg("requests==1.2"), @r"
success: true
exit_code: 0
----- stdout -----
@ -8369,7 +8370,7 @@ build-constraint-dependencies = [
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"###
"
);
Ok(())
@ -8378,7 +8379,7 @@ build-constraint-dependencies = [
/// Merge `build_constraints.txt` with `build-constraint-dependencies` in pyproject.toml with an incompatible constraint.
#[test]
fn incompatible_build_constraint_merged_with_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
// Incompatible setuptools version in pyproject.toml, compatible in build_constraints.txt.
let constraints_txt = context.temp_dir.child("build_constraints.txt");
@ -8442,7 +8443,7 @@ build-constraint-dependencies = [
/// Merge `build_constraints.txt` with `build-constraint-dependencies` in pyproject.toml with a compatible constraint.
#[test]
fn compatible_build_constraint_merged_with_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools>=40")?;
@ -8458,7 +8459,7 @@ build-constraint-dependencies = [
uv_snapshot!(context.pip_install()
.arg("requests==1.2")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r###"
.arg("build_constraints.txt"), @r"
success: true
exit_code: 0
----- stdout -----
@ -8468,7 +8469,7 @@ build-constraint-dependencies = [
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"###
"
);
Ok(())
}
@ -8569,7 +8570,7 @@ fn install_build_isolation_package() -> Result<()> {
/// Install a package with an unsupported extension.
#[test]
fn invalid_extension() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
uv_snapshot!(context.filters(), context.pip_install()
.arg("ruff @ https://files.pythonhosted.org/packages/f7/69/96766da2cdb5605e6a31ef2734aff0be17901cefb385b885c2ab88896d76/ruff-0.5.6.tar.baz")
@ -8589,7 +8590,7 @@ fn invalid_extension() {
/// Install a package without unsupported extension.
#[test]
fn no_extension() {
let context = TestContext::new("3.8");
let context = TestContext::new(DEFAULT_PYTHON_VERSION);
uv_snapshot!(context.filters(), context.pip_install()
.arg("ruff @ https://files.pythonhosted.org/packages/f7/69/96766da2cdb5605e6a31ef2734aff0be17901cefb385b885c2ab88896d76/ruff-0.5.6")

View File

@ -779,7 +779,7 @@ fn install_sdist_url() -> Result<()> {
/// Install a package with source archive format `.tar.bz2`.
#[test]
fn install_sdist_archive_type_bz2() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
@ -922,6 +922,7 @@ fn install_version_then_install_url() -> Result<()> {
/// Test that we select the last 3.8 compatible numpy version instead of trying to compile an
/// incompatible sdist <https://github.com/astral-sh/uv/issues/388>
#[cfg(feature = "python-eol")]
#[test]
fn install_numpy_py38() -> Result<()> {
let context = TestContext::new("3.8");
@ -5538,7 +5539,7 @@ fn preserve_markers() -> Result<()> {
/// Include a `build_constraints.txt` file with an incompatible constraint.
#[test]
fn incompatible_build_constraint() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==1.2")?;
@ -5568,7 +5569,7 @@ fn incompatible_build_constraint() -> Result<()> {
/// Include a `build_constraints.txt` file with a compatible constraint.
#[test]
fn compatible_build_constraint() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==1.2")?;
@ -5596,7 +5597,7 @@ fn compatible_build_constraint() -> Result<()> {
#[test]
fn sync_seed() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==1.2")?;
@ -5618,7 +5619,7 @@ fn sync_seed() -> Result<()> {
// Syncing should remove the seed packages.
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt"), @r###"
.arg("requirements.txt"), @r"
success: true
exit_code: 0
----- stdout -----
@ -5630,29 +5631,29 @@ fn sync_seed() -> Result<()> {
Installed 1 package in [TIME]
- pip==24.0
+ requests==1.2.0
"###
"
);
// Re-create the environment with seed packages.
uv_snapshot!(context.filters(), context.venv()
.arg("--seed"), @r###"
.arg("--seed"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.8.[X] interpreter at: [PYTHON-3.8]
Using CPython 3.9.[X] interpreter at: [PYTHON-3.9]
Creating virtual environment with seed packages at: .venv
+ pip==24.0
+ setuptools==69.2.0
+ wheel==0.43.0
Activate with: source .venv/[BIN]/activate
"###
"
);
// Syncing should retain the seed packages.
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt"), @r###"
.arg("requirements.txt"), @r"
success: true
exit_code: 0
----- stdout -----
@ -5661,7 +5662,7 @@ fn sync_seed() -> Result<()> {
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"###
"
);
Ok(())

View File

@ -750,24 +750,24 @@ fn python_required_python_major_minor() {
.unwrap();
// Find `python3.11`, which is `>=3.11.4`.
uv_snapshot!(context.filters(), context.python_find().arg(">=3.11.4, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @r###"
uv_snapshot!(context.filters(), context.python_find().arg(">=3.11.4, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/python3.11
----- stderr -----
"###);
");
// Find `python3.11`, which is `>3.11.4`.
uv_snapshot!(context.filters(), context.python_find().arg(">3.11.4, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @r###"
uv_snapshot!(context.filters(), context.python_find().arg(">3.11.4, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/python3.11
----- stderr -----
"###);
");
// Fail to find any matching Python interpreter.
uv_snapshot!(context.filters(), context.python_find().arg(">3.11.255, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @r###"

View File

@ -15,7 +15,7 @@ use crate::common::{TestContext, uv_snapshot};
#[test]
fn run_with_python_version() -> Result<()> {
let context = TestContext::new_with_versions(&["3.12", "3.11", "3.8"]);
let context = TestContext::new_with_versions(&["3.12", "3.11", "3.9"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
@ -116,25 +116,25 @@ fn run_with_python_version() -> Result<()> {
+ sniffio==1.3.1
"###);
// This time, we target Python 3.8 instead.
// This time, we target Python 3.9 instead.
let mut command = context.run();
let command_with_args = command
.arg("-p")
.arg("3.8")
.arg("3.9")
.arg("python")
.arg("-B")
.arg("main.py")
.env_remove(EnvVars::VIRTUAL_ENV);
uv_snapshot!(context.filters(), command_with_args, @r###"
uv_snapshot!(context.filters(), command_with_args, @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Using CPython 3.8.[X] interpreter at: [PYTHON-3.8]
error: The requested interpreter resolved to Python 3.8.[X], which is incompatible with the project's Python requirement: `>=3.11, <4`
"###);
Using CPython 3.9.[X] interpreter at: [PYTHON-3.9]
error: The requested interpreter resolved to Python 3.9.[X], which is incompatible with the project's Python requirement: `>=3.11, <4`
");
Ok(())
}
@ -145,7 +145,7 @@ fn run_args() -> Result<()> {
let mut filters = context.filters();
filters.push((r"Usage: (uv|\.exe) run \[OPTIONS\] (?s).*", "[UV RUN HELP]"));
filters.push((r"usage: (\[VENV\]|\[PYTHON-3.12\])(?s).*", "[PYTHON HELP]"));
filters.push((r"usage: .*(\n|.*)*", "usage: [PYTHON HELP]"));
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
@ -176,7 +176,7 @@ fn run_args() -> Result<()> {
success: true
exit_code: 0
----- stdout -----
[PYTHON HELP]
usage: [PYTHON HELP]
");
// Can use `--` to separate uv arguments from the command arguments.
@ -475,11 +475,11 @@ fn run_pep723_script() -> Result<()> {
#[test]
fn run_pep723_script_requires_python() -> Result<()> {
let context = TestContext::new_with_versions(&["3.8", "3.11"]);
let context = TestContext::new_with_versions(&["3.9", "3.11"]);
// If we have a `.python-version` that's incompatible with the script, we should error.
let python_version = context.temp_dir.child(PYTHON_VERSION_FILENAME);
python_version.write_str("3.8")?;
python_version.write_str("3.9")?;
// If the script contains a PEP 723 tag, we should install its requirements.
let test_script = context.temp_dir.child("main.py");
@ -498,22 +498,22 @@ fn run_pep723_script_requires_python() -> Result<()> {
"#
})?;
uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###"
uv_snapshot!(context.filters(), context.run().arg("main.py"), @r#"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
warning: The Python request from `.python-version` resolved to Python 3.8.[X], which is incompatible with the script's Python requirement: `>=3.11`
warning: The Python request from `.python-version` resolved to Python 3.9.[X], which is incompatible with the script's Python requirement: `>=3.11`
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
Traceback (most recent call last):
File "main.py", line 10, in <module>
File "[TEMP_DIR]/main.py", line 10, in <module>
x: str | int = "hello"
TypeError: unsupported operand type(s) for |: 'type' and 'type'
"###);
"#);
// Delete the `.python-version` file to allow the script to run.
fs_err::remove_file(&python_version)?;
@ -765,14 +765,14 @@ fn run_pep723_script_overrides() -> Result<()> {
/// Run a PEP 723-compatible script with `tool.uv` build constraints.
#[test]
fn run_pep723_script_build_constraints() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let test_script = context.temp_dir.child("main.py");
// Incompatible build constraints.
test_script.write_str(indoc! { r#"
# /// script
# requires-python = ">=3.8"
# requires-python = ">=3.9"
# dependencies = [
# "anyio>=3",
# "requests==1.2"
@ -801,7 +801,7 @@ fn run_pep723_script_build_constraints() -> Result<()> {
// Compatible build constraints.
test_script.write_str(indoc! { r#"
# /// script
# requires-python = ">=3.8"
# requires-python = ">=3.9"
# dependencies = [
# "anyio>=3",
# "requests==1.2"
@ -1322,14 +1322,14 @@ fn run_with_pyvenv_cfg_file() -> Result<()> {
#[test]
fn run_with_build_constraints() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = ["anyio"]
[tool.uv]
@ -1370,7 +1370,7 @@ fn run_with_build_constraints() -> Result<()> {
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = ["anyio"]
[tool.uv]
@ -3107,7 +3107,7 @@ fn run_project_toml_error() -> Result<()> {
#[test]
fn run_isolated_incompatible_python() -> Result<()> {
let context = TestContext::new_with_versions(&["3.8", "3.11"]);
let context = TestContext::new_with_versions(&["3.9", "3.11"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
@ -3124,7 +3124,7 @@ fn run_isolated_incompatible_python() -> Result<()> {
})?;
let python_version = context.temp_dir.child(PYTHON_VERSION_FILENAME);
python_version.write_str("3.8")?;
python_version.write_str("3.9")?;
let test_script = context.temp_dir.child("main.py");
test_script.write_str(indoc! { r#"
@ -3135,15 +3135,15 @@ fn run_isolated_incompatible_python() -> Result<()> {
"#
})?;
// We should reject Python 3.8...
// We should reject Python 3.9...
uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Using CPython 3.8.[X] interpreter at: [PYTHON-3.8]
error: The Python request from `.python-version` resolved to Python 3.8.[X], which is incompatible with the project's Python requirement: `>=3.12`. Use `uv python pin` to update the `.python-version` file to a compatible version.
Using CPython 3.9.[X] interpreter at: [PYTHON-3.9]
error: The Python request from `.python-version` resolved to Python 3.9.[X], which is incompatible with the project's Python requirement: `>=3.12`. Use `uv python pin` to update the `.python-version` file to a compatible version.
"###);
// ...even if `--isolated` is provided.
@ -3153,7 +3153,7 @@ fn run_isolated_incompatible_python() -> Result<()> {
----- stdout -----
----- stderr -----
error: The Python request from `.python-version` resolved to Python 3.8.[X], which is incompatible with the project's Python requirement: `>=3.12`. Use `uv python pin` to update the `.python-version` file to a compatible version.
error: The Python request from `.python-version` resolved to Python 3.9.[X], which is incompatible with the project's Python requirement: `>=3.12`. Use `uv python pin` to update the `.python-version` file to a compatible version.
"###);
Ok(())
@ -3685,7 +3685,7 @@ fn run_linked_environment_path() -> Result<()> {
uv_snapshot!(context.filters(), context.run()
.env_remove("VIRTUAL_ENV") // Ignore the test context's active virtual environment
.env(EnvVars::UV_PROJECT_ENVIRONMENT, "target")
.arg("python").arg("-c").arg("import sys; print(sys.prefix); print(sys.executable)"), @r###"
.arg("python").arg("-c").arg("import sys; print(sys.prefix); print(sys.executable)"), @r"
success: true
exit_code: 0
----- stdout -----
@ -3695,7 +3695,7 @@ fn run_linked_environment_path() -> Result<()> {
----- stderr -----
Resolved 8 packages in [TIME]
Audited 6 packages in [TIME]
"###);
");
// And, similarly, the entrypoint should use `target`
let black_entrypoint = context.read("target/bin/black");
@ -3703,7 +3703,7 @@ fn run_linked_environment_path() -> Result<()> {
filters => context.filters(),
}, {
assert_snapshot!(
black_entrypoint, @r###"
black_entrypoint, @r##"
#![TEMP_DIR]/target/[BIN]/python
# -*- coding: utf-8 -*-
import sys
@ -3714,7 +3714,7 @@ fn run_linked_environment_path() -> Result<()> {
elif sys.argv[0].endswith(".exe"):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(patched_main())
"###
"##
);
});
@ -5193,7 +5193,7 @@ fn run_pep723_script_with_constraints() -> Result<()> {
#[test]
fn run_no_sync_incompatible_python() -> Result<()> {
let context = TestContext::new_with_versions(&["3.12", "3.11", "3.8"]);
let context = TestContext::new_with_versions(&["3.12", "3.11", "3.9"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
@ -5229,14 +5229,14 @@ fn run_no_sync_incompatible_python() -> Result<()> {
+ iniconfig==2.0.0
");
uv_snapshot!(context.filters(), context.run().arg("--no-sync").arg("--python").arg("3.8").arg("main.py"), @r"
uv_snapshot!(context.filters(), context.run().arg("--no-sync").arg("--python").arg("3.9").arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
Hello, world!
----- stderr -----
warning: Using incompatible environment (`.venv`) due to `--no-sync` (The project environment's Python version does not satisfy the request: `Python 3.8`)
warning: Using incompatible environment (`.venv`) due to `--no-sync` (The project environment's Python version does not satisfy the request: `Python 3.9`)
");
Ok(())

View File

@ -270,7 +270,7 @@ fn package() -> Result<()> {
/// Ensure that we use the maximum Python version when a workspace contains mixed requirements.
#[test]
fn mixed_requires_python() -> Result<()> {
let context = TestContext::new_with_versions(&["3.8", "3.12"]);
let context = TestContext::new_with_versions(&["3.9", "3.12"]);
// Create a workspace root with a minimum Python requirement of Python 3.12.
let pyproject_toml = context.temp_dir.child("pyproject.toml");
@ -296,7 +296,7 @@ fn mixed_requires_python() -> Result<()> {
let init = src.child("__init__.py");
init.touch()?;
// Create a child with a minimum Python requirement of Python 3.8.
// Create a child with a minimum Python requirement of Python 3.9.
let child = context.temp_dir.child("packages").child("bird-feeder");
child.create_dir_all()?;
@ -312,7 +312,7 @@ fn mixed_requires_python() -> Result<()> {
[project]
name = "bird-feeder"
version = "0.1.0"
requires-python = ">=3.8"
requires-python = ">=3.9"
[build-system]
requires = ["setuptools>=42"]
@ -339,15 +339,15 @@ fn mixed_requires_python() -> Result<()> {
"###);
// Running `uv sync` again should fail.
uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.8"), @r###"
uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.9"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Using CPython 3.8.[X] interpreter at: [PYTHON-3.8]
error: The requested interpreter resolved to Python 3.8.[X], which is incompatible with the project's Python requirement: `>=3.12`. However, a workspace member (`bird-feeder`) supports Python >=3.8. To install the workspace member on its own, navigate to `packages/bird-feeder`, then run `uv venv --python 3.8.[X]` followed by `uv pip install -e .`.
"###);
Using CPython 3.9.[X] interpreter at: [PYTHON-3.9]
error: The requested interpreter resolved to Python 3.9.[X], which is incompatible with the project's Python requirement: `>=3.12`. However, a workspace member (`bird-feeder`) supports Python >=3.9. To install the workspace member on its own, navigate to `packages/bird-feeder`, then run `uv venv --python 3.9.[X]` followed by `uv pip install -e .`.
");
Ok(())
}
@ -8401,14 +8401,14 @@ fn sync_locked_script() -> Result<()> {
#[test]
fn sync_script_with_compatible_build_constraints() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let test_script = context.temp_dir.child("script.py");
// Compatible build constraints.
test_script.write_str(indoc! { r#"
# /// script
# requires-python = ">=3.8"
# requires-python = ">=3.9"
# dependencies = [
# "anyio>=3",
# "requests==1.2"
@ -8454,7 +8454,7 @@ fn sync_script_with_compatible_build_constraints() -> Result<()> {
#[test]
fn sync_script_with_incompatible_build_constraints() -> Result<()> {
let context = TestContext::new("3.8");
let context = TestContext::new("3.9");
let test_script = context.temp_dir.child("script.py");
let filters = context
@ -8469,7 +8469,7 @@ fn sync_script_with_incompatible_build_constraints() -> Result<()> {
// Incompatible build constraints.
test_script.write_str(indoc! { r#"
# /// script
# requires-python = ">=3.8"
# requires-python = ">=3.9"
# dependencies = [
# "anyio>=3",
# "requests==1.2"

View File

@ -326,7 +326,7 @@ fn tool_install_with_editable() -> Result<()> {
#[test]
fn tool_install_with_compatible_build_constraints() -> Result<()> {
let context = TestContext::new("3.8")
let context = TestContext::new("3.9")
.with_exclude_newer("2024-05-04T00:00:00Z")
.with_filtered_counts()
.with_filtered_exe_suffix();
@ -344,7 +344,7 @@ fn tool_install_with_compatible_build_constraints() -> Result<()> {
.arg("build_constraints.txt")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
success: true
exit_code: 0
----- stdout -----
@ -363,7 +363,7 @@ fn tool_install_with_compatible_build_constraints() -> Result<()> {
+ tomli==2.0.1
+ typing-extensions==4.11.0
Installed 2 executables: black, blackd
"###);
");
tool_dir
.child("black")
@ -396,7 +396,7 @@ fn tool_install_with_compatible_build_constraints() -> Result<()> {
#[test]
fn tool_install_with_incompatible_build_constraints() -> Result<()> {
let context = TestContext::new("3.8")
let context = TestContext::new("3.9")
.with_exclude_newer("2024-05-04T00:00:00Z")
.with_filtered_counts()
.with_filtered_exe_suffix();
@ -414,7 +414,7 @@ fn tool_install_with_incompatible_build_constraints() -> Result<()> {
.arg("build_constraints.txt")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
success: false
exit_code: 1
----- stdout -----
@ -425,7 +425,7 @@ fn tool_install_with_incompatible_build_constraints() -> Result<()> {
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools>=40.8.0 and setuptools==2, we can conclude that your requirements are unsatisfiable.
"###);
");
tool_dir
.child("black")

View File

@ -2348,7 +2348,7 @@ fn tool_run_with_script_and_from_script() {
#[test]
fn tool_run_with_compatible_build_constraints() -> Result<()> {
let context = TestContext::new("3.8")
let context = TestContext::new("3.9")
.with_exclude_newer("2024-05-04T00:00:00Z")
.with_filtered_counts()
.with_filtered_exe_suffix();
@ -2361,7 +2361,7 @@ fn tool_run_with_compatible_build_constraints() -> Result<()> {
.arg("--build-constraints")
.arg("build_constraints.txt")
.arg("pytest")
.arg("--version"), @r###"
.arg("--version"), @r"
success: true
exit_code: 0
----- stdout -----
@ -2378,14 +2378,14 @@ fn tool_run_with_compatible_build_constraints() -> Result<()> {
+ pytest==8.2.0
+ requests==1.2.0
+ tomli==2.0.1
"###);
");
Ok(())
}
#[test]
fn tool_run_with_incompatible_build_constraints() -> Result<()> {
let context = TestContext::new("3.8")
let context = TestContext::new("3.9")
.with_exclude_newer("2024-05-04T00:00:00Z")
.with_filtered_counts()
.with_filtered_exe_suffix();
@ -2404,7 +2404,7 @@ fn tool_run_with_incompatible_build_constraints() -> Result<()> {
.arg("--version")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
success: false
exit_code: 1
----- stdout -----
@ -2415,7 +2415,7 @@ fn tool_run_with_incompatible_build_constraints() -> Result<()> {
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools>=40.8.0 and setuptools==2, we can conclude that your requirements are unsatisfiable.
"###);
");
Ok(())
}

View File

@ -875,27 +875,27 @@ fn non_empty_dir_exists_allow_existing() -> Result<()> {
#[test]
#[cfg(windows)]
fn windows_shims() -> Result<()> {
let context = TestContext::new_with_versions(&["3.9", "3.8"]);
let context = TestContext::new_with_versions(&["3.10", "3.9"]);
let shim_path = context.temp_dir.child("shim");
let py38 = context
let py39 = context
.python_versions
.last()
.expect("python_path_with_versions to set up the python versions");
// We want 3.8 and the first version should be 3.9.
// We want 3.9 and the first version should be 3.10.
// Picking the last is necessary to prove that shims work because the python version selects
// the python version from the first path segment by default, so we take the last to prove it's not
// returning that version.
assert!(py38.0.to_string().contains("3.8"));
assert!(py39.0.to_string().contains("3.9"));
// Write the shim script that forwards the arguments to the python3.8 installation.
// Write the shim script that forwards the arguments to the python3.9 installation.
fs_err::create_dir(&shim_path)?;
fs_err::write(
shim_path.child("python.bat"),
format!(
"@echo off\r\n{}/python.exe %*",
py38.1.parent().unwrap().display()
py39.1.parent().unwrap().display()
),
)?;
@ -908,7 +908,7 @@ fn windows_shims() -> Result<()> {
----- stdout -----
----- stderr -----
Using CPython 3.8.[X] interpreter at: [PYTHON-3.8]
Using CPython 3.9.[X] interpreter at: [PYTHON-3.9]
Creating virtual environment at: .venv
Activate with: source .venv/[BIN]/activate
"###