Add filtering of patch Python versions unless explicitly requested (#2930)

Elides Python patch versions from the test suite unless the test
specifically requests a patch version.

This reduces some toil when not using our bootstrapped Python versions.

Partially addresses https://github.com/astral-sh/uv/issues/2165 though
we'll need changes to the scenario tests to really support their case.
This commit is contained in:
Zanie Blue 2024-04-09 10:04:28 -05:00 committed by GitHub
parent d7ff8d93c0
commit 1cdadbdec8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 82 additions and 38 deletions

View file

@ -15,10 +15,11 @@ use std::env;
use std::ffi::OsString; use std::ffi::OsString;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Output; use std::process::Output;
use std::str::FromStr;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_cache::Cache; use uv_cache::Cache;
use uv_interpreter::find_requested_python; use uv_interpreter::{find_requested_python, PythonVersion};
// Exclude any packages uploaded after this date. // Exclude any packages uploaded after this date.
pub static EXCLUDE_NEWER: &str = "2024-03-25T00:00:00Z"; pub static EXCLUDE_NEWER: &str = "2024-03-25T00:00:00Z";
@ -69,6 +70,9 @@ impl TestContext {
let site_packages = site_packages_path(&venv, format!("python{python_version}")); let site_packages = site_packages_path(&venv, format!("python{python_version}"));
let python_version =
PythonVersion::from_str(python_version).expect("Tests must use valid Python versions");
let mut filters = Vec::new(); let mut filters = Vec::new();
filters.extend( filters.extend(
Self::path_patterns(&cache_dir) Self::path_patterns(&cache_dir)
@ -123,6 +127,18 @@ impl TestContext {
// Destroy any remaining UNC prefixes (Windows only) // Destroy any remaining UNC prefixes (Windows only)
filters.push((r"\\\\\?\\".to_string(), String::new())); filters.push((r"\\\\\?\\".to_string(), String::new()));
// Add Python patch version filtering unless explicitly requested to ensure
// snapshots are patch version agnostic when it is not a part of the test.
if python_version.patch().is_none() {
filters.push((
format!(
r"({})\.\d+",
regex::escape(python_version.to_string().as_str())
),
"$1.[X]".to_string(),
));
}
Self { Self {
temp_dir, temp_dir,
cache_dir, cache_dir,

View file

@ -5779,14 +5779,14 @@ requires-python = "<=3.8"
let requirements_in = context.temp_dir.child("requirements.in"); let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?; requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?;
uv_snapshot!(context.compile() uv_snapshot!(context.filters(), context.compile()
.arg("requirements.in"), @r###" .arg("requirements.in"), @r###"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
error: Editable `example` requires Python <=3.8, but resolution targets Python 3.12.1 error: Editable `example` requires Python <=3.8, but resolution targets Python 3.12.[X]
"### "###
); );
@ -6056,7 +6056,7 @@ requires-python = "<=3.8"
let requirements_in = context.temp_dir.child("requirements.in"); let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str(&format!("example @ {}", editable_dir.path().display()))?; requirements_in.write_str(&format!("example @ {}", editable_dir.path().display()))?;
uv_snapshot!(context.compile() uv_snapshot!(context.filters(), context.compile()
.arg("requirements.in"), @r###" .arg("requirements.in"), @r###"
success: false success: false
exit_code: 1 exit_code: 1
@ -6064,7 +6064,7 @@ requires-python = "<=3.8"
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.12.1) does not satisfy Python<=3.8 and example==0.0.0 depends on Python<=3.8, we can conclude that example==0.0.0 cannot be used. Because the current Python version (3.12.[X]) does not satisfy Python<=3.8 and example==0.0.0 depends on Python<=3.8, we can conclude that example==0.0.0 cannot be used.
And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable.
"### "###
); );

View file

@ -84,7 +84,7 @@ fn incompatible_python_compatible_override() -> Result<()> {
package-a==1.0.0 package-a==1.0.0
----- stderr ----- ----- stderr -----
warning: The requested Python version 3.11 is not available; 3.9.18 will be used to build dependencies instead. warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead.
Resolved 1 package in [TIME] Resolved 1 package in [TIME]
"### "###
); );
@ -130,7 +130,7 @@ fn compatible_python_incompatible_override() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
warning: The requested Python version 3.9 is not available; 3.11.7 will be used to build dependencies instead. warning: The requested Python version 3.9 is not available; 3.11.[X] will be used to build dependencies instead.
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the requested Python version (3.9) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. Because the requested Python version (3.9) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
@ -184,9 +184,9 @@ fn incompatible_python_compatible_override_unavailable_no_wheels() -> Result<()>
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
warning: The requested Python version 3.11 is not available; 3.9.18 will be used to build dependencies instead. warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead.
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.9.18) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
"### "###
); );
@ -295,9 +295,9 @@ fn incompatible_python_compatible_override_no_compatible_wheels() -> Result<()>
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
warning: The requested Python version 3.11 is not available; 3.9.18 will be used to build dependencies instead. warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead.
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.9.18) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
"### "###
); );
@ -353,9 +353,9 @@ fn incompatible_python_compatible_override_other_wheel() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
warning: The requested Python version 3.11 is not available; 3.9.18 will be used to build dependencies instead. warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead.
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.9.18) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
And because only the following versions of package-a are available: And because only the following versions of package-a are available:
package-a==1.0.0 package-a==1.0.0
package-a==2.0.0 package-a==2.0.0

View file

@ -2415,7 +2415,7 @@ requires-python = "<=3.8"
"#, "#,
)?; )?;
uv_snapshot!(context.install() uv_snapshot!(context.filters(), context.install()
.arg("--editable") .arg("--editable")
.arg(editable_dir.path()), @r###" .arg(editable_dir.path()), @r###"
success: false success: false
@ -2423,7 +2423,7 @@ requires-python = "<=3.8"
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
error: Editable `example` requires Python <=3.8, but 3.12.1 is installed error: Editable `example` requires Python <=3.8, but 3.12.[X] is installed
"### "###
); );
@ -2864,7 +2864,7 @@ requires-python = "<=3.8"
"#, "#,
)?; )?;
uv_snapshot!(context.install() uv_snapshot!(context.filters(), context.install()
.arg(format!("example @ {}", editable_dir.path().display())), @r###" .arg(format!("example @ {}", editable_dir.path().display())), @r###"
success: false success: false
exit_code: 1 exit_code: 1
@ -2872,7 +2872,7 @@ requires-python = "<=3.8"
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.12.1) does not satisfy Python<=3.8 and example==0.0.0 depends on Python<=3.8, we can conclude that example==0.0.0 cannot be used. Because the current Python version (3.12.[X]) does not satisfy Python<=3.8 and example==0.0.0 depends on Python<=3.8, we can conclude that example==0.0.0 cannot be used.
And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable.
"### "###
); );

View file

@ -3671,7 +3671,7 @@ fn python_version_does_not_exist() {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.8.18) does not satisfy Python>=3.30 and package-a==1.0.0 depends on Python>=3.30, we can conclude that package-a==1.0.0 cannot be used. Because the current Python version (3.8.[X]) does not satisfy Python>=3.30 and package-a==1.0.0 depends on Python>=3.30, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
"###); "###);
@ -3713,7 +3713,7 @@ fn python_less_than_current() {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.9.18) does not satisfy Python<=3.8 and package-a==1.0.0 depends on Python<=3.8, we can conclude that package-a==1.0.0 cannot be used. Because the current Python version (3.9.[X]) does not satisfy Python<=3.8 and package-a==1.0.0 depends on Python<=3.8, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
"###); "###);
@ -3755,7 +3755,7 @@ fn python_greater_than_current() {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.9.18) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
"###); "###);
@ -3961,22 +3961,22 @@ fn python_greater_than_current_excluded() {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.9.18) does not satisfy Python>=3.10,<3.11 and the current Python version (3.9.18) does not satisfy Python>=3.12, we can conclude that any of: Because the current Python version (3.9.[X]) does not satisfy Python>=3.10,<3.11 and the current Python version (3.9.[X]) does not satisfy Python>=3.12, we can conclude that any of:
Python>=3.10,<3.11 Python>=3.10,<3.11
Python>=3.12 Python>=3.12
are incompatible. are incompatible.
And because the current Python version (3.9.18) does not satisfy Python>=3.11,<3.12, we can conclude that Python>=3.10 are incompatible. And because the current Python version (3.9.[X]) does not satisfy Python>=3.11,<3.12, we can conclude that Python>=3.10 are incompatible.
And because package-a==2.0.0 depends on Python>=3.10 and only the following versions of package-a are available: And because package-a==2.0.0 depends on Python>=3.10 and only the following versions of package-a are available:
package-a<=2.0.0 package-a<=2.0.0
package-a==3.0.0 package-a==3.0.0
package-a==4.0.0 package-a==4.0.0
we can conclude that package-a>=2.0.0,<3.0.0 cannot be used. (1) we can conclude that package-a>=2.0.0,<3.0.0 cannot be used. (1)
Because the current Python version (3.9.18) does not satisfy Python>=3.11,<3.12 and the current Python version (3.9.18) does not satisfy Python>=3.12, we can conclude that Python>=3.11 are incompatible. Because the current Python version (3.9.[X]) does not satisfy Python>=3.11,<3.12 and the current Python version (3.9.[X]) does not satisfy Python>=3.12, we can conclude that Python>=3.11 are incompatible.
And because package-a==3.0.0 depends on Python>=3.11, we can conclude that package-a==3.0.0 cannot be used. And because package-a==3.0.0 depends on Python>=3.11, we can conclude that package-a==3.0.0 cannot be used.
And because we know from (1) that package-a>=2.0.0,<3.0.0 cannot be used, we can conclude that package-a>=2.0.0,<4.0.0 cannot be used. (2) And because we know from (1) that package-a>=2.0.0,<3.0.0 cannot be used, we can conclude that package-a>=2.0.0,<4.0.0 cannot be used. (2)
Because the current Python version (3.9.18) does not satisfy Python>=3.12 and package-a==4.0.0 depends on Python>=3.12, we can conclude that package-a==4.0.0 cannot be used. Because the current Python version (3.9.[X]) does not satisfy Python>=3.12 and package-a==4.0.0 depends on Python>=3.12, we can conclude that package-a==4.0.0 cannot be used.
And because we know from (2) that package-a>=2.0.0,<4.0.0 cannot be used, we can conclude that package-a>=2.0.0 cannot be used. And because we know from (2) that package-a>=2.0.0,<4.0.0 cannot be used, we can conclude that package-a>=2.0.0 cannot be used.
And because you require package-a>=2.0.0, we can conclude that the requirements are unsatisfiable. And because you require package-a>=2.0.0, we can conclude that the requirements are unsatisfiable.
"###); "###);

View file

@ -2995,14 +2995,14 @@ requires-python = "<=3.5"
let requirements_in = context.temp_dir.child("requirements.in"); let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?; requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?;
uv_snapshot!(command(&context) uv_snapshot!(context.filters(), command(&context)
.arg("requirements.in"), @r###" .arg("requirements.in"), @r###"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
error: Editable `example` requires Python <=3.5, but 3.12.1 is installed error: Editable `example` requires Python <=3.5, but 3.12.[X] is installed
"### "###
); );
@ -3071,7 +3071,7 @@ requires-python = "<=3.5"
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because the current Python version (3.12.1) does not satisfy Python<=3.5 and example==0.0.0 depends on Python<=3.5, we can conclude that example==0.0.0 cannot be used. Because the current Python version (3.12.[X]) does not satisfy Python<=3.5 and example==0.0.0 depends on Python<=3.5, we can conclude that example==0.0.0 cannot be used.
And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable.
"### "###
); );

View file

@ -1,7 +1,7 @@
#![cfg(feature = "python")] #![cfg(feature = "python")]
use std::ffi::OsString;
use std::process::Command; use std::process::Command;
use std::{ffi::OsString, str::FromStr};
use anyhow::Result; use anyhow::Result;
use assert_cmd::prelude::*; use assert_cmd::prelude::*;
@ -9,6 +9,7 @@ use assert_fs::fixture::ChildPath;
use assert_fs::prelude::*; use assert_fs::prelude::*;
use fs_err::PathExt; use fs_err::PathExt;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_interpreter::PythonVersion;
use crate::common::{ use crate::common::{
create_bin_with_executables, get_bin, uv_snapshot, TestContext, EXCLUDE_NEWER, create_bin_with_executables, get_bin, uv_snapshot, TestContext, EXCLUDE_NEWER,
@ -21,6 +22,7 @@ struct VenvTestContext {
temp_dir: assert_fs::TempDir, temp_dir: assert_fs::TempDir,
venv: ChildPath, venv: ChildPath,
bin: OsString, bin: OsString,
python_versions: Vec<PythonVersion>,
} }
impl VenvTestContext { impl VenvTestContext {
@ -29,11 +31,18 @@ impl VenvTestContext {
let bin = create_bin_with_executables(&temp_dir, python_versions) let bin = create_bin_with_executables(&temp_dir, python_versions)
.expect("Failed to create bin dir"); .expect("Failed to create bin dir");
let venv = temp_dir.child(".venv"); let venv = temp_dir.child(".venv");
let python_versions = python_versions
.iter()
.map(|version| {
PythonVersion::from_str(version).expect("Tests should use valid Python versions")
})
.collect::<Vec<_>>();
Self { Self {
cache_dir: assert_fs::TempDir::new().unwrap(), cache_dir: assert_fs::TempDir::new().unwrap(),
temp_dir, temp_dir,
venv, venv,
bin, bin,
python_versions,
} }
} }
@ -70,6 +79,25 @@ impl VenvTestContext {
r"Activate with: (?:.*)\\Scripts\\activate".to_string(), r"Activate with: (?:.*)\\Scripts\\activate".to_string(),
"Activate with: source .venv/bin/activate".to_string(), "Activate with: source .venv/bin/activate".to_string(),
)); ));
// Add Python patch version filtering unless one was explicitly requested to ensure
// snapshots are patch version agnostic when it is not a part of the test.
if self
.python_versions
.iter()
.all(|version| version.patch().is_none())
{
for python_version in &self.python_versions {
filters.push((
format!(
r"({})\.\d+",
regex::escape(python_version.to_string().as_str())
),
"$1.[X]".to_string(),
));
}
}
filters filters
} }
} }
@ -88,7 +116,7 @@ fn create_venv() {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using Python 3.12.1 interpreter at: [PATH] Using Python 3.12.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
Activate with: source .venv/bin/activate Activate with: source .venv/bin/activate
"### "###
@ -107,7 +135,7 @@ fn create_venv() {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using Python 3.12.1 interpreter at: [PATH] Using Python 3.12.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
Activate with: source .venv/bin/activate Activate with: source .venv/bin/activate
"### "###
@ -128,7 +156,7 @@ fn create_venv_defaults_to_cwd() {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using Python 3.12.1 interpreter at: [PATH] Using Python 3.12.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
Activate with: source .venv/bin/activate Activate with: source .venv/bin/activate
"### "###
@ -151,7 +179,7 @@ fn seed() {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using Python 3.12.1 interpreter at: [PATH] Using Python 3.12.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
+ pip==24.0 + pip==24.0
Activate with: source .venv/bin/activate Activate with: source .venv/bin/activate
@ -175,7 +203,7 @@ fn seed_older_python_version() {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using Python 3.10.13 interpreter at: [PATH] Using Python 3.10.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
+ pip==24.0 + pip==24.0
+ setuptools==69.2.0 + setuptools==69.2.0
@ -293,7 +321,7 @@ fn file_exists() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using Python 3.12.1 interpreter at: [PATH] Using Python 3.12.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
uv::venv::creation uv::venv::creation
@ -321,7 +349,7 @@ fn empty_dir_exists() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using Python 3.12.1 interpreter at: [PATH] Using Python 3.12.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
Activate with: source .venv/bin/activate Activate with: source .venv/bin/activate
"### "###
@ -350,7 +378,7 @@ fn non_empty_dir_exists() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using Python 3.12.1 interpreter at: [PATH] Using Python 3.12.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
uv::venv::creation uv::venv::creation
@ -395,7 +423,7 @@ fn windows_shims() -> Result<()> {
----- stderr ----- ----- stderr -----
warning: virtualenv's `--clear` has no effect (uv always clears the virtual environment). warning: virtualenv's `--clear` has no effect (uv always clears the virtual environment).
Using Python 3.8.12 interpreter at: [PATH] Using Python 3.8.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
Activate with: source .venv/bin/activate Activate with: source .venv/bin/activate
"### "###
@ -422,7 +450,7 @@ fn virtualenv_compatibility() {
----- stderr ----- ----- stderr -----
warning: virtualenv's `--clear` has no effect (uv always clears the virtual environment). warning: virtualenv's `--clear` has no effect (uv always clears the virtual environment).
Using Python 3.12.1 interpreter at: [PATH] Using Python 3.12.[X] interpreter at: [PATH]
Creating virtualenv at: .venv Creating virtualenv at: .venv
Activate with: source .venv/bin/activate Activate with: source .venv/bin/activate
"### "###