mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Add filtering of interpreter names for tests with multiple Python versions (#4368)
Extends new filters for interpreter paths to apply to tests with multiple Python versions. Adds patch version filtering for them as well, which is needed for #4360 tests.
This commit is contained in:
parent
3f164b5a3a
commit
05870609ee
3 changed files with 114 additions and 105 deletions
|
@ -7,6 +7,7 @@ use assert_fs::assert::PathAssert;
|
|||
use assert_fs::fixture::{ChildPath, PathChild};
|
||||
use regex::Regex;
|
||||
use std::borrow::BorrowMut;
|
||||
use std::collections::VecDeque;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::iter::Iterator;
|
||||
|
@ -62,8 +63,11 @@ pub struct TestContext {
|
|||
pub python_version: String,
|
||||
pub workspace_root: PathBuf,
|
||||
|
||||
// Additional Python versions
|
||||
python_versions: Vec<(PythonVersion, PathBuf)>,
|
||||
|
||||
// Standard filters for this test context
|
||||
filters: Vec<(String, String)>,
|
||||
filters: VecDeque<(String, String)>,
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
|
@ -87,7 +91,7 @@ impl TestContext {
|
|||
let python_version =
|
||||
PythonVersion::from_str(python_version).expect("Tests must use valid Python versions");
|
||||
|
||||
let mut filters = Vec::new();
|
||||
let mut filters = VecDeque::new();
|
||||
|
||||
filters.extend(
|
||||
Self::path_patterns(&cache_dir)
|
||||
|
@ -116,7 +120,7 @@ impl TestContext {
|
|||
);
|
||||
|
||||
// Account for [`Simplified::user_display`] which is relative to the command working directory
|
||||
filters.push((
|
||||
filters.push_back((
|
||||
Self::path_pattern(
|
||||
site_packages
|
||||
.strip_prefix(&temp_dir)
|
||||
|
@ -124,7 +128,7 @@ impl TestContext {
|
|||
),
|
||||
"[SITE_PACKAGES]/".to_string(),
|
||||
));
|
||||
filters.push((
|
||||
filters.push_back((
|
||||
Self::path_pattern(
|
||||
venv.strip_prefix(&temp_dir)
|
||||
.expect("The test virtual environment directory is always in the tempdir"),
|
||||
|
@ -134,13 +138,13 @@ impl TestContext {
|
|||
|
||||
// Filter non-deterministic temporary directory names
|
||||
// Note we apply this _after_ all the full paths to avoid breaking their matching
|
||||
filters.push((r"(\\|\/)\.tmp.*(\\|\/)".to_string(), "/[TMP]/".to_string()));
|
||||
filters.push_back((r"(\\|\/)\.tmp.*(\\|\/)".to_string(), "/[TMP]/".to_string()));
|
||||
|
||||
// Account for platform prefix differences `file://` (Unix) vs `file:///` (Windows)
|
||||
filters.push((r"file:///".to_string(), "file://".to_string()));
|
||||
filters.push_back((r"file:///".to_string(), "file://".to_string()));
|
||||
|
||||
// Destroy any remaining UNC prefixes (Windows only)
|
||||
filters.push((r"\\\\\?\\".to_string(), String::new()));
|
||||
filters.push_back((r"\\\\\?\\".to_string(), String::new()));
|
||||
|
||||
let mut result = Self {
|
||||
temp_dir,
|
||||
|
@ -148,6 +152,7 @@ impl TestContext {
|
|||
venv,
|
||||
python_version: python_version.to_string(),
|
||||
filters,
|
||||
python_versions: Vec::new(),
|
||||
workspace_root,
|
||||
};
|
||||
|
||||
|
@ -156,22 +161,47 @@ impl TestContext {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn new_with_versions(python_versions: &[&str]) -> Self {
|
||||
let mut context = Self::new(
|
||||
python_versions
|
||||
.first()
|
||||
.expect("At least one test Python version must be provided"),
|
||||
);
|
||||
|
||||
let python_versions: Vec<_> = python_versions
|
||||
.iter()
|
||||
.map(|version| PythonVersion::from_str(version).unwrap())
|
||||
.zip(
|
||||
python_toolchains_for_versions(&context.temp_dir, python_versions)
|
||||
.expect("Failed to find test Python versions"),
|
||||
)
|
||||
.collect();
|
||||
|
||||
for (version, path) in &python_versions {
|
||||
context.add_filters_for_python_version(version, path.clone());
|
||||
}
|
||||
|
||||
context.python_versions = python_versions;
|
||||
|
||||
context
|
||||
}
|
||||
|
||||
fn add_filters_for_python_version(&mut self, version: &PythonVersion, executable: PathBuf) {
|
||||
// Add filtering for the interpreter path
|
||||
self.filters.extend(
|
||||
Self::path_patterns(executable)
|
||||
.into_iter()
|
||||
.map(|pattern| (format!("{pattern}python.*"), format!("[PYTHON-{version}]"))),
|
||||
);
|
||||
for pattern in Self::path_patterns(executable) {
|
||||
self.filters
|
||||
.push_front((format!("{pattern}python.*"), format!("[PYTHON-{version}]")));
|
||||
}
|
||||
// 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 version.patch().is_none() {
|
||||
self.filters.push((
|
||||
self.filters.push_back((
|
||||
format!(r"({})\.\d+", regex::escape(version.to_string().as_str())),
|
||||
"$1.[X]".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `pip compile` command for testing.
|
||||
pub fn compile(&self) -> std::process::Command {
|
||||
let mut command = self.compile_without_exclude_newer();
|
||||
|
@ -193,7 +223,7 @@ impl TestContext {
|
|||
.arg(self.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", self.venv.as_os_str())
|
||||
.env("UV_NO_WRAP", "1")
|
||||
.env("UV_TEST_PYTHON_PATH", "/dev/null")
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path())
|
||||
.current_dir(self.temp_dir.path());
|
||||
|
||||
if cfg!(all(windows, debug_assertions)) {
|
||||
|
@ -227,7 +257,7 @@ impl TestContext {
|
|||
.arg(self.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", self.venv.as_os_str())
|
||||
.env("UV_NO_WRAP", "1")
|
||||
.env("UV_TEST_PYTHON_PATH", "/dev/null")
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path())
|
||||
.current_dir(&self.temp_dir);
|
||||
|
||||
if cfg!(all(windows, debug_assertions)) {
|
||||
|
@ -247,9 +277,9 @@ impl TestContext {
|
|||
.arg("--cache-dir")
|
||||
.arg(self.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", self.venv.as_os_str())
|
||||
.env("UV_TEST_PYTHON_PATH", "/dev/null")
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path())
|
||||
.env("UV_NO_WRAP", "1")
|
||||
.env("UV_TEST_PYTHON_PATH", "/dev/null")
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path())
|
||||
.current_dir(&self.temp_dir);
|
||||
|
||||
if cfg!(all(windows, debug_assertions)) {
|
||||
|
@ -267,7 +297,7 @@ impl TestContext {
|
|||
command
|
||||
.arg("--exclude-newer")
|
||||
.arg(EXCLUDE_NEWER)
|
||||
.env("UV_TEST_PYTHON_PATH", "/dev/null");
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path());
|
||||
command
|
||||
}
|
||||
|
||||
|
@ -285,7 +315,7 @@ impl TestContext {
|
|||
.arg(self.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", self.venv.as_os_str())
|
||||
.env("UV_NO_WRAP", "1")
|
||||
.env("UV_TEST_PYTHON_PATH", "/dev/null")
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path())
|
||||
.current_dir(&self.temp_dir);
|
||||
|
||||
if cfg!(all(windows, debug_assertions)) {
|
||||
|
@ -311,7 +341,7 @@ impl TestContext {
|
|||
.arg(self.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", self.venv.as_os_str())
|
||||
.env("UV_NO_WRAP", "1")
|
||||
.env("UV_TEST_PYTHON_PATH", "/dev/null")
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path())
|
||||
.env("UV_PREVIEW", "1")
|
||||
.env("UV_TOOLCHAIN_DIR", self.toolchains_dir().as_os_str())
|
||||
.current_dir(&self.temp_dir);
|
||||
|
@ -346,7 +376,7 @@ impl TestContext {
|
|||
.arg(self.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", self.venv.as_os_str())
|
||||
.env("UV_NO_WRAP", "1")
|
||||
.env("UV_TEST_PYTHON_PATH", "/dev/null")
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path())
|
||||
.current_dir(&self.temp_dir);
|
||||
|
||||
if cfg!(all(windows, debug_assertions)) {
|
||||
|
@ -467,6 +497,10 @@ impl TestContext {
|
|||
)
|
||||
}
|
||||
|
||||
fn python_path(&self) -> OsString {
|
||||
std::env::join_paths(self.python_versions.iter().map(|(_, path)| path)).unwrap()
|
||||
}
|
||||
|
||||
/// Standard snapshot filters _plus_ those for this test context.
|
||||
pub fn filters(&self) -> Vec<(&str, &str)> {
|
||||
// Put test context snapshots before the default filters
|
||||
|
@ -606,6 +640,19 @@ pub fn python_path_with_versions(
|
|||
temp_dir: &assert_fs::TempDir,
|
||||
python_versions: &[&str],
|
||||
) -> anyhow::Result<OsString> {
|
||||
Ok(std::env::join_paths(python_toolchains_for_versions(
|
||||
temp_dir,
|
||||
python_versions,
|
||||
)?)?)
|
||||
}
|
||||
|
||||
/// Create a `PATH` with the requested Python versions available in order.
|
||||
///
|
||||
/// Generally this should be used with `UV_TEST_PYTHON_PATH`.
|
||||
pub fn python_toolchains_for_versions(
|
||||
temp_dir: &assert_fs::TempDir,
|
||||
python_versions: &[&str],
|
||||
) -> anyhow::Result<Vec<PathBuf>> {
|
||||
let cache = Cache::from_path(temp_dir.child("cache").to_path_buf()).init()?;
|
||||
let selected_pythons = python_versions
|
||||
.iter()
|
||||
|
@ -658,7 +705,7 @@ pub fn python_path_with_versions(
|
|||
"Failed to fulfill requested test Python versions: {selected_pythons:?}"
|
||||
);
|
||||
|
||||
Ok(env::join_paths(selected_pythons)?)
|
||||
Ok(selected_pythons)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
|
|
|
@ -4,16 +4,14 @@ use anyhow::Result;
|
|||
use assert_fs::prelude::*;
|
||||
use indoc::indoc;
|
||||
|
||||
use common::{python_path_with_versions, uv_snapshot, TestContext};
|
||||
use common::{uv_snapshot, TestContext};
|
||||
|
||||
mod common;
|
||||
|
||||
/// Run with different python versions, which also depend on different dependency versions.
|
||||
#[test]
|
||||
fn run_with_python_version() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let python_path = python_path_with_versions(&context.temp_dir, &["3.11", "3.12"])
|
||||
.expect("Failed to create Python test path");
|
||||
let context = TestContext::new_with_versions(&["3.12", "3.11"]);
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! { r#"
|
||||
|
@ -44,8 +42,7 @@ fn run_with_python_version() -> Result<()> {
|
|||
.arg("--preview")
|
||||
.arg("python")
|
||||
.arg("-B")
|
||||
.arg("main.py")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path);
|
||||
.arg("main.py");
|
||||
uv_snapshot!(context.filters(), command_with_args, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -71,8 +68,7 @@ fn run_with_python_version() -> Result<()> {
|
|||
.arg("3.12")
|
||||
.arg("python")
|
||||
.arg("-B")
|
||||
.arg("main.py")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path);
|
||||
.arg("main.py");
|
||||
uv_snapshot!(context.filters(), command_with_args, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -94,17 +90,9 @@ fn run_with_python_version() -> Result<()> {
|
|||
.arg("python")
|
||||
.arg("-B")
|
||||
.arg("main.py")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path)
|
||||
.env_remove("VIRTUAL_ENV");
|
||||
|
||||
let mut filters = context.filters();
|
||||
filters.push((
|
||||
r"Using Python 3.11.\d+ interpreter at: .*",
|
||||
"Using Python 3.11.[X] interpreter at: [PYTHON]",
|
||||
));
|
||||
filters.push((r"3.11.\d+", "3.11.[X]"));
|
||||
|
||||
uv_snapshot!(filters, command_with_args, @r###"
|
||||
uv_snapshot!(context.filters(), command_with_args, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -113,7 +101,7 @@ fn run_with_python_version() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Removing virtual environment at: [VENV]/
|
||||
Using Python 3.11.[X] interpreter at: [PYTHON]
|
||||
Using Python 3.11.[X] interpreter at: [PYTHON-3.11]
|
||||
Creating virtualenv at: [VENV]/
|
||||
Resolved 5 packages in [TIME]
|
||||
Downloaded 4 packages in [TIME]
|
||||
|
|
|
@ -7,110 +7,85 @@ mod common;
|
|||
|
||||
#[test]
|
||||
fn toolchain_find() {
|
||||
let context: TestContext = TestContext::new("3.12");
|
||||
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
|
||||
|
||||
// No interpreters on the path
|
||||
uv_snapshot!(context.filters(), context.toolchain_find(), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No Python interpreters found in provided path, active virtual environment, or search path
|
||||
"###);
|
||||
|
||||
let python_path = python_path_with_versions(&context.temp_dir, &["3.11", "3.12"])
|
||||
.expect("Failed to create Python test path");
|
||||
|
||||
// Create some filters for the test interpreters, otherwise they'll be a path on the dev's machine
|
||||
// TODO(zanieb): Standardize this when writing more tests
|
||||
let python_path_filters = std::env::split_paths(&python_path)
|
||||
.zip(["3.11", "3.12"])
|
||||
.flat_map(|(path, version)| {
|
||||
TestContext::path_patterns(path)
|
||||
.into_iter()
|
||||
.map(move |pattern| {
|
||||
(
|
||||
format!("{pattern}python.*"),
|
||||
format!("[PYTHON-PATH-{version}]"),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let filters = python_path_filters
|
||||
.iter()
|
||||
.map(|(pattern, replacement)| (pattern.as_str(), replacement.as_str()))
|
||||
.chain(context.filters())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// We find the first interpreter on the path
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-PATH-3.11]
|
||||
[PYTHON-3.11]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// We find the first interpreter on the path
|
||||
uv_snapshot!(context.filters(), context.toolchain_find()
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-3.11]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Request Python 3.12
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
uv_snapshot!(context.filters(), context.toolchain_find()
|
||||
.arg("3.12")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-PATH-3.12]
|
||||
[PYTHON-3.12]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Request Python 3.11
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
uv_snapshot!(context.filters(), context.toolchain_find()
|
||||
.arg("3.11")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-PATH-3.11]
|
||||
[PYTHON-3.11]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Request CPython
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
uv_snapshot!(context.filters(), context.toolchain_find()
|
||||
.arg("cpython")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-PATH-3.11]
|
||||
[PYTHON-3.11]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Request CPython 3.12
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
uv_snapshot!(context.filters(), context.toolchain_find()
|
||||
.arg("cpython@3.12")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-PATH-3.12]
|
||||
[PYTHON-3.12]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Request CPython 3.12 via partial key syntax
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
uv_snapshot!(context.filters(), context.toolchain_find()
|
||||
.arg("cpython-3.12")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-PATH-3.12]
|
||||
[PYTHON-3.12]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
@ -119,21 +94,21 @@ fn toolchain_find() {
|
|||
let os = Os::from_env();
|
||||
let arch = Arch::from_env();
|
||||
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
uv_snapshot!(context.filters(), context.toolchain_find()
|
||||
.arg(format!("cpython-3.12-{os}-{arch}"))
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-PATH-3.12]
|
||||
[PYTHON-3.12]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Request PyPy
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
uv_snapshot!(context.filters(), context.toolchain_find()
|
||||
.arg("pypy")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
@ -142,28 +117,27 @@ fn toolchain_find() {
|
|||
error: No interpreter found for PyPy in provided path, active virtual environment, or search path
|
||||
"###);
|
||||
|
||||
// Swap the order (but don't change the filters to preserve our indices)
|
||||
// Swap the order of the Python versions
|
||||
let python_path = python_path_with_versions(&context.temp_dir, &["3.12", "3.11"])
|
||||
.expect("Failed to create Python test path");
|
||||
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
uv_snapshot!(context.filters(), context.toolchain_find().env("UV_TEST_PYTHON_PATH", python_path), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-PATH-3.12]
|
||||
[PYTHON-3.12]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Request Python 3.11
|
||||
uv_snapshot!(filters, context.toolchain_find()
|
||||
uv_snapshot!(context.filters(), context.toolchain_find()
|
||||
.arg("3.11")
|
||||
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-PATH-3.11]
|
||||
[PYTHON-3.11]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue