Skip to content

Commit

Permalink
Add filtering of interpreter names for tests with multiple Python ver…
Browse files Browse the repository at this point in the history
…sions (#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.
  • Loading branch information
zanieb authored Jun 17, 2024
1 parent 3f164b5 commit 0587060
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 99 deletions.
91 changes: 69 additions & 22 deletions crates/uv/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -116,15 +120,15 @@ 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)
.expect("The test site-packages directory is always in the tempdir"),
),
"[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"),
Expand All @@ -134,20 +138,21 @@ 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,
cache_dir,
venv,
python_version: python_version.to_string(),
filters,
python_versions: Vec::new(),
workspace_root,
};

Expand All @@ -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();
Expand All @@ -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)) {
Expand Down Expand Up @@ -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)) {
Expand All @@ -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)) {
Expand All @@ -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
}

Expand All @@ -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)) {
Expand All @@ -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);
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)]
Expand Down
24 changes: 6 additions & 18 deletions crates/uv/tests/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 -----
Expand All @@ -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]
Expand Down
Loading

0 comments on commit 0587060

Please sign in to comment.