Add Python version filtering for ad-hoc virtual environments (#4365)

Extends #4364 automatically adding filters to the test context for
additional test virtual environments.

It turns out that the `pip sync` tests were really on the loose with
their virtual environment creation and it was difficult to use the new
helpers because they require mutability and the tests had immutable
borrows to extend the filters 😭. I refactored the tests to just reset
the environment.
This commit is contained in:
Zanie Blue 2024-06-17 16:15:00 -04:00 committed by GitHub
parent 56f0a117ca
commit 3f164b5a3a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 98 additions and 106 deletions

View file

@ -88,11 +88,7 @@ impl TestContext {
PythonVersion::from_str(python_version).expect("Tests must use valid Python versions");
let mut filters = Vec::new();
filters.extend(
Self::path_patterns(python)
.into_iter()
.map(|pattern| (format!("{pattern}python.*"), "[PYTHON]".to_string())),
);
filters.extend(
Self::path_patterns(&cache_dir)
.into_iter()
@ -146,28 +142,36 @@ impl TestContext {
// Destroy any remaining UNC prefixes (Windows only)
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 {
let mut result = Self {
temp_dir,
cache_dir,
venv,
python_version: python_version.to_string(),
filters,
workspace_root,
}
};
result.add_filters_for_python_version(&python_version, python);
result
}
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}]"))),
);
// 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((
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();
@ -487,6 +491,31 @@ impl TestContext {
&format!("{}{}", self.python_kind(), self.python_version),
)
}
/// Reset the virtual environment in the test context.
pub fn reset_venv(&self) {
create_venv_from_executable(
&self.temp_dir,
&self.cache_dir,
&get_toolchain(&self.python_version),
);
}
/// Create a new virtual environment named `.venv` in the test context.
fn create_venv(&mut self, python: &str) -> PathBuf {
let parent = self.temp_dir.to_path_buf();
self.create_venv_in_parent(parent, python)
}
/// Create a new virtual environment named `.venv` in the given directory.
fn create_venv_in_parent<P: AsRef<Path>>(&mut self, path: P, python: &str) -> PathBuf {
let executable = get_toolchain(python);
self.add_filters_for_python_version(
&PythonVersion::from_str(python).unwrap(),
executable.clone(),
);
create_venv_from_executable(&ChildPath::new(path.as_ref()), &self.cache_dir, &executable)
}
}
pub fn site_packages_path(venv: &Path, python: &str) -> PathBuf {
@ -539,17 +568,6 @@ pub fn get_toolchain(python: &str) -> PathBuf {
.unwrap_or(PathBuf::from(python))
}
/// Create a virtual environment named `.venv` in a temporary directory with the given
/// Python version.
pub fn create_venv<Parent: assert_fs::prelude::PathChild + AsRef<std::path::Path>>(
temp_dir: &Parent,
cache_dir: &assert_fs::TempDir,
python: &str,
) -> PathBuf {
let python = get_toolchain(python);
create_venv_from_executable(temp_dir, cache_dir, &python)
}
/// Create a virtual environment named `.venv` in a temporary directory with the given
/// Python executable.
pub fn create_venv_from_executable<

View file

@ -13,7 +13,7 @@ use indoc::indoc;
use predicates::Predicate;
use url::Url;
use common::{create_venv, uv_snapshot, venv_to_interpreter};
use common::{uv_snapshot, venv_to_interpreter};
use uv_fs::Simplified;
use crate::common::{
@ -998,13 +998,13 @@ fn install_local_wheel() -> Result<()> {
context.assert_command("import tomli").success();
// Create a new virtual environment.
let venv = create_venv(&context.temp_dir, &context.cache_dir, "3.12");
context.reset_venv();
// Reinstall. The wheel should come from the cache, so there shouldn't be a "download".
uv_snapshot!(context.filters(), sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1019,7 +1019,7 @@ fn install_local_wheel() -> Result<()> {
context.assert_command("import tomli").success();
// Create a new virtual environment.
let venv = create_venv(&context.temp_dir, &context.cache_dir, "3.12");
context.reset_venv();
// "Modify" the wheel.
// The `filetime` crate works on Windows unlike the std.
@ -1029,7 +1029,7 @@ fn install_local_wheel() -> Result<()> {
uv_snapshot!(context.filters(), sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1280,14 +1280,12 @@ fn install_url_source_dist_cached() -> Result<()> {
.success();
// Re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1304,16 +1302,14 @@ fn install_url_source_dist_cached() -> Result<()> {
.success();
// Clear the cache, then re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(Command::new(get_bin())
.arg("clean")
.arg("source_distribution")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
@ -1327,7 +1323,7 @@ fn install_url_source_dist_cached() -> Result<()> {
uv_snapshot!(sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1376,14 +1372,12 @@ fn install_git_source_dist_cached() -> Result<()> {
.success();
// Re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1395,12 +1389,12 @@ fn install_git_source_dist_cached() -> Result<()> {
"###
);
check_command(&venv, "import uv_public_pypackage", &context.temp_dir);
context
.assert_command("import uv_public_pypackage")
.success();
// Clear the cache, then re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
let filters = if cfg!(windows) {
[("Removed 2 files", "Removed 3 files")]
@ -1415,7 +1409,7 @@ fn install_git_source_dist_cached() -> Result<()> {
.arg("werkzeug")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
@ -1429,7 +1423,7 @@ fn install_git_source_dist_cached() -> Result<()> {
uv_snapshot!(sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1476,14 +1470,12 @@ fn install_registry_source_dist_cached() -> Result<()> {
.success();
// Re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1500,9 +1492,7 @@ fn install_registry_source_dist_cached() -> Result<()> {
.success();
// Clear the cache, then re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
let filters: Vec<(&str, &str)> = if cfg!(windows) {
// On Windows, the number of files removed is different.
@ -1522,7 +1512,7 @@ fn install_registry_source_dist_cached() -> Result<()> {
.arg("source_distribution")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
@ -1536,7 +1526,7 @@ fn install_registry_source_dist_cached() -> Result<()> {
uv_snapshot!(sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1593,14 +1583,12 @@ fn install_path_source_dist_cached() -> Result<()> {
.success();
// Re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(context.filters(), sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1617,16 +1605,14 @@ fn install_path_source_dist_cached() -> Result<()> {
.success();
// Clear the cache, then re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(Command::new(get_bin())
.arg("clean")
.arg("source-distribution")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
@ -1640,7 +1626,7 @@ fn install_path_source_dist_cached() -> Result<()> {
uv_snapshot!(context.filters(), sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1693,14 +1679,12 @@ fn install_path_built_dist_cached() -> Result<()> {
context.assert_command("import tomli").success();
// Re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&context.temp_dir, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(context.filters(), sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1712,12 +1696,10 @@ fn install_path_built_dist_cached() -> Result<()> {
"###
);
check_command(&venv, "import tomli", &parent);
context.assert_command("import tomli").success();
// Clear the cache, then re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
let filters = if cfg!(windows) {
// We do not display sizes on Windows
@ -1736,7 +1718,7 @@ fn install_path_built_dist_cached() -> Result<()> {
.arg("tomli")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
@ -1750,7 +1732,7 @@ fn install_path_built_dist_cached() -> Result<()> {
uv_snapshot!(context.filters(), sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1763,7 +1745,7 @@ fn install_path_built_dist_cached() -> Result<()> {
"###
);
check_command(&venv, "import tomli", &context.temp_dir);
context.assert_command("import tomli").success();
Ok(())
}
@ -1802,14 +1784,12 @@ fn install_url_built_dist_cached() -> Result<()> {
context.assert_command("import tqdm").success();
// Re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(filters, sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1821,19 +1801,17 @@ fn install_url_built_dist_cached() -> Result<()> {
"###
);
check_command(&venv, "import tqdm", &context.temp_dir);
context.assert_command("import tqdm").success();
// Clear the cache, then re-run the installation in a new virtual environment.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(Command::new(get_bin())
.arg("clean")
.arg("tqdm")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
@ -1847,7 +1825,7 @@ fn install_url_built_dist_cached() -> Result<()> {
uv_snapshot!(filters, sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -1860,7 +1838,7 @@ fn install_url_built_dist_cached() -> Result<()> {
"###
);
check_command(&venv, "import tqdm", &context.temp_dir);
context.assert_command("import tqdm").success();
Ok(())
}
@ -2103,15 +2081,13 @@ fn refresh() -> Result<()> {
// Re-run the installation into with `--refresh`. Ensure that we resolve and download the
// latest versions of the packages.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--refresh")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -2125,8 +2101,8 @@ fn refresh() -> Result<()> {
"###
);
check_command(&venv, "import markupsafe", &context.temp_dir);
check_command(&venv, "import tomli", &context.temp_dir);
context.assert_command("import markupsafe").success();
context.assert_command("import tomli").success();
Ok(())
}
@ -2160,16 +2136,14 @@ fn refresh_package() -> Result<()> {
// Re-run the installation into with `--refresh`. Ensure that we resolve and download the
// latest versions of the packages.
let parent = context.temp_dir.child("parent");
parent.create_dir_all()?;
let venv = create_venv(&parent, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(sync_without_exclude_newer(&context)
.arg("requirements.txt")
.arg("--refresh-package")
.arg("tomli")
.arg("--strict")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----
@ -2833,12 +2807,12 @@ fn offline() -> Result<()> {
);
// Install with `--offline` with a populated cache.
let venv = create_venv(&context.temp_dir, &context.cache_dir, "3.12");
context.reset_venv();
uv_snapshot!(sync_without_exclude_newer(&context)
.arg("requirements.in")
.arg("--offline")
.env("VIRTUAL_ENV", venv.as_os_str()), @r###"
, @r###"
success: true
exit_code: 0
----- stdout -----