Require tests to opt-in to managed Python installation (#10912)

First of all, I want to test automatic managed installs (see #10913) and
need to set that up. Second of all, some tests were _implicitly_
downloading interpreters instead of using the one from their context —
which is unexpected and naughty and very slow.
This commit is contained in:
Zanie Blue 2025-01-23 15:24:40 -06:00 committed by GitHub
parent a05b0e0346
commit fd5131cb7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 60 additions and 44 deletions

View file

@ -102,6 +102,9 @@ pub struct TestContext {
/// Standard filters for this test context. /// Standard filters for this test context.
filters: Vec<(String, String)>, filters: Vec<(String, String)>,
/// Extra environment variables to apply to all commands.
extra_env: Vec<(OsString, OsString)>,
#[allow(dead_code)] #[allow(dead_code)]
_root: tempfile::TempDir, _root: tempfile::TempDir,
} }
@ -233,6 +236,29 @@ impl TestContext {
self self
} }
/// Add extra directories and configuration for managed Python installations.
#[must_use]
pub fn with_managed_python_dirs(mut self) -> Self {
let managed = self.temp_dir.join("managed");
let bin = self.temp_dir.join("bin");
self.extra_env.push((
EnvVars::PATH.into(),
env::join_paths(std::iter::once(bin.clone()).chain(env::split_paths(
&env::var(EnvVars::PATH).unwrap_or_default(),
)))
.unwrap(),
));
self.extra_env
.push((EnvVars::UV_PYTHON_BIN_DIR.into(), bin.into()));
self.extra_env
.push((EnvVars::UV_PYTHON_INSTALL_DIR.into(), managed.into()));
self.extra_env
.push((EnvVars::UV_PYTHON_DOWNLOADS.into(), "automatic".into()));
self
}
/// Discover the path to the XDG state directory. We use this, rather than the OS-specific /// Discover the path to the XDG state directory. We use this, rather than the OS-specific
/// temporary directory, because on macOS (and Windows on GitHub Actions), they involve /// temporary directory, because on macOS (and Windows on GitHub Actions), they involve
/// symlinks. (On macOS, the temporary directory is, like `/var/...`, which resolves to /// symlinks. (On macOS, the temporary directory is, like `/var/...`, which resolves to
@ -456,6 +482,7 @@ impl TestContext {
python_version, python_version,
python_versions, python_versions,
filters, filters,
extra_env: vec![],
_root: root, _root: root,
} }
} }
@ -510,12 +537,18 @@ impl TestContext {
.env(EnvVars::PATH, path) .env(EnvVars::PATH, path)
.env(EnvVars::HOME, self.home_dir.as_os_str()) .env(EnvVars::HOME, self.home_dir.as_os_str())
.env(EnvVars::UV_PYTHON_INSTALL_DIR, "") .env(EnvVars::UV_PYTHON_INSTALL_DIR, "")
// Installations are not allowed by default; see `Self::with_managed_python_dirs`
.env(EnvVars::UV_PYTHON_DOWNLOADS, "never")
.env(EnvVars::UV_TEST_PYTHON_PATH, self.python_path()) .env(EnvVars::UV_TEST_PYTHON_PATH, self.python_path())
.env(EnvVars::UV_EXCLUDE_NEWER, EXCLUDE_NEWER) .env(EnvVars::UV_EXCLUDE_NEWER, EXCLUDE_NEWER)
.env_remove(EnvVars::UV_CACHE_DIR) .env_remove(EnvVars::UV_CACHE_DIR)
.env_remove(EnvVars::UV_TOOL_BIN_DIR) .env_remove(EnvVars::UV_TOOL_BIN_DIR)
.current_dir(self.temp_dir.path()); .current_dir(self.temp_dir.path());
for (key, value) in &self.extra_env {
command.env(key, value);
}
if activate_venv { if activate_venv {
command.env(EnvVars::VIRTUAL_ENV, self.venv.as_os_str()); command.env(EnvVars::VIRTUAL_ENV, self.venv.as_os_str());
} }
@ -677,21 +710,10 @@ impl TestContext {
/// Create a `uv python install` command with options shared across scenarios. /// Create a `uv python install` command with options shared across scenarios.
pub fn python_install(&self) -> Command { pub fn python_install(&self) -> Command {
let mut command = self.new_command(); let mut command = self.new_command();
let managed = self.temp_dir.join("managed");
let bin = self.temp_dir.join("bin");
self.add_shared_args(&mut command, true); self.add_shared_args(&mut command, true);
command command
.arg("python") .arg("python")
.arg("install") .arg("install")
.env(EnvVars::UV_PYTHON_INSTALL_DIR, managed)
.env(EnvVars::UV_PYTHON_BIN_DIR, bin.as_os_str())
.env(
EnvVars::PATH,
env::join_paths(std::iter::once(bin).chain(env::split_paths(
&env::var(EnvVars::PATH).unwrap_or_default(),
)))
.unwrap(),
)
.current_dir(&self.temp_dir); .current_dir(&self.temp_dir);
command command
} }
@ -699,14 +721,10 @@ impl TestContext {
/// Create a `uv python uninstall` command with options shared across scenarios. /// Create a `uv python uninstall` command with options shared across scenarios.
pub fn python_uninstall(&self) -> Command { pub fn python_uninstall(&self) -> Command {
let mut command = self.new_command(); let mut command = self.new_command();
let managed = self.temp_dir.join("managed");
let bin = self.temp_dir.join("bin");
self.add_shared_args(&mut command, true); self.add_shared_args(&mut command, true);
command command
.arg("python") .arg("python")
.arg("uninstall") .arg("uninstall")
.env(EnvVars::UV_PYTHON_INSTALL_DIR, managed)
.env(EnvVars::UV_PYTHON_BIN_DIR, bin)
.current_dir(&self.temp_dir); .current_dir(&self.temp_dir);
command command
} }

View file

@ -3394,7 +3394,7 @@ fn shared_optional_dependency_mixed1() -> Result<()> {
/// Regression test for: <https://github.com/astral-sh/uv/issues/9640> /// Regression test for: <https://github.com/astral-sh/uv/issues/9640>
#[test] #[test]
fn shared_optional_dependency_extra2() -> Result<()> { fn shared_optional_dependency_extra2() -> Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.11");
let pyproject_toml = context.temp_dir.child("pyproject.toml"); let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str( pyproject_toml.write_str(
@ -3432,9 +3432,6 @@ fn shared_optional_dependency_extra2() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using CPython 3.11.11
Removed virtual environment at: .venv
Creating virtual environment at: .venv
Resolved 5 packages in [TIME] Resolved 5 packages in [TIME]
Prepared 3 packages in [TIME] Prepared 3 packages in [TIME]
Installed 3 packages in [TIME] Installed 3 packages in [TIME]
@ -3536,7 +3533,7 @@ fn shared_optional_dependency_extra2() -> Result<()> {
/// Regression test for: <https://github.com/astral-sh/uv/issues/9640> /// Regression test for: <https://github.com/astral-sh/uv/issues/9640>
#[test] #[test]
fn shared_optional_dependency_group2() -> Result<()> { fn shared_optional_dependency_group2() -> Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.11");
let pyproject_toml = context.temp_dir.child("pyproject.toml"); let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str( pyproject_toml.write_str(
@ -3574,9 +3571,6 @@ fn shared_optional_dependency_group2() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using CPython 3.11.11
Removed virtual environment at: .venv
Creating virtual environment at: .venv
Resolved 5 packages in [TIME] Resolved 5 packages in [TIME]
Prepared 3 packages in [TIME] Prepared 3 packages in [TIME]
Installed 3 packages in [TIME] Installed 3 packages in [TIME]
@ -3682,7 +3676,7 @@ fn shared_optional_dependency_group2() -> Result<()> {
/// Regression test for: <https://github.com/astral-sh/uv/issues/9640> /// Regression test for: <https://github.com/astral-sh/uv/issues/9640>
#[test] #[test]
fn shared_optional_dependency_mixed2() -> Result<()> { fn shared_optional_dependency_mixed2() -> Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.11");
let pyproject_toml = context.temp_dir.child("pyproject.toml"); let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str( pyproject_toml.write_str(
@ -3722,9 +3716,6 @@ fn shared_optional_dependency_mixed2() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using CPython 3.11.11
Removed virtual environment at: .venv
Creating virtual environment at: .venv
Resolved 5 packages in [TIME] Resolved 5 packages in [TIME]
Prepared 3 packages in [TIME] Prepared 3 packages in [TIME]
Installed 3 packages in [TIME] Installed 3 packages in [TIME]
@ -4364,7 +4355,7 @@ fn shared_dependency_mixed() -> Result<()> {
/// Ref <https://github.com/astral-sh/uv/issues/9289> /// Ref <https://github.com/astral-sh/uv/issues/9289>
#[test] #[test]
fn extras_are_namespaced() -> Result<()> { fn extras_are_namespaced() -> Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.11");
let root_pyproject_toml = context.temp_dir.child("pyproject.toml"); let root_pyproject_toml = context.temp_dir.child("pyproject.toml");
root_pyproject_toml.write_str( root_pyproject_toml.write_str(
@ -4423,9 +4414,6 @@ conflicts = [
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using CPython 3.11.11
Removed virtual environment at: .venv
Creating virtual environment at: .venv
Resolved 7 packages in [TIME] Resolved 7 packages in [TIME]
Prepared 3 packages in [TIME] Prepared 3 packages in [TIME]
Installed 3 packages in [TIME] Installed 3 packages in [TIME]
@ -7297,7 +7285,7 @@ fn deduplicate_resolution_markers() -> Result<()> {
#[test] #[test]
fn overlapping_resolution_markers() -> Result<()> { fn overlapping_resolution_markers() -> Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.10");
let pyproject_toml = context.temp_dir.child("pyproject.toml"); let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str( pyproject_toml.write_str(
@ -7305,7 +7293,7 @@ fn overlapping_resolution_markers() -> Result<()> {
[project] [project]
name = "ads-mega-model" name = "ads-mega-model"
version = "0.1.0" version = "0.1.0"
requires-python = "==3.10.12" requires-python = "==3.10.*"
dependencies = [ dependencies = [
"wandb==0.17.6", "wandb==0.17.6",
] ]
@ -7344,7 +7332,6 @@ fn overlapping_resolution_markers() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using CPython 3.10.12
Resolved 45 packages in [TIME] Resolved 45 packages in [TIME]
"###); "###);
@ -7356,7 +7343,7 @@ fn overlapping_resolution_markers() -> Result<()> {
lock, lock,
@r###" @r###"
version = 1 version = 1
requires-python = "==3.10.12" requires-python = "==3.10.*"
resolution-markers = [ resolution-markers = [
"sys_platform == 'linux' and extra != 'extra-14-ads-mega-model-cpu' and extra == 'extra-14-ads-mega-model-cu118'", "sys_platform == 'linux' and extra != 'extra-14-ads-mega-model-cpu' and extra == 'extra-14-ads-mega-model-cu118'",
"sys_platform != 'linux' and extra != 'extra-14-ads-mega-model-cpu' and extra == 'extra-14-ads-mega-model-cu118'", "sys_platform != 'linux' and extra != 'extra-14-ads-mega-model-cpu' and extra == 'extra-14-ads-mega-model-cu118'",

View file

@ -13,7 +13,8 @@ use crate::common::{uv_snapshot, TestContext};
fn python_install() { fn python_install() {
let context: TestContext = TestContext::new_with_versions(&[]) let context: TestContext = TestContext::new_with_versions(&[])
.with_filtered_python_keys() .with_filtered_python_keys()
.with_filtered_exe_suffix(); .with_filtered_exe_suffix()
.with_managed_python_dirs();
// Install the latest version // Install the latest version
uv_snapshot!(context.filters(), context.python_install(), @r###" uv_snapshot!(context.filters(), context.python_install(), @r###"
@ -95,7 +96,8 @@ fn python_install() {
fn python_install_preview() { fn python_install_preview() {
let context: TestContext = TestContext::new_with_versions(&[]) let context: TestContext = TestContext::new_with_versions(&[])
.with_filtered_python_keys() .with_filtered_python_keys()
.with_filtered_exe_suffix(); .with_filtered_exe_suffix()
.with_managed_python_dirs();
// Install the latest version // Install the latest version
uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r###" uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r###"
@ -269,7 +271,8 @@ fn python_install_preview() {
fn python_install_preview_upgrade() { fn python_install_preview_upgrade() {
let context = TestContext::new_with_versions(&[]) let context = TestContext::new_with_versions(&[])
.with_filtered_python_keys() .with_filtered_python_keys()
.with_filtered_exe_suffix(); .with_filtered_exe_suffix()
.with_managed_python_dirs();
let bin_python = context let bin_python = context
.temp_dir .temp_dir
@ -408,7 +411,8 @@ fn python_install_preview_upgrade() {
fn python_install_freethreaded() { fn python_install_freethreaded() {
let context: TestContext = TestContext::new_with_versions(&[]) let context: TestContext = TestContext::new_with_versions(&[])
.with_filtered_python_keys() .with_filtered_python_keys()
.with_filtered_exe_suffix(); .with_filtered_exe_suffix()
.with_managed_python_dirs();
// Install the latest version // Install the latest version
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13t"), @r###" uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13t"), @r###"
@ -482,7 +486,8 @@ fn python_install_freethreaded() {
fn python_install_invalid_request() { fn python_install_invalid_request() {
let context: TestContext = TestContext::new_with_versions(&[]) let context: TestContext = TestContext::new_with_versions(&[])
.with_filtered_python_keys() .with_filtered_python_keys()
.with_filtered_exe_suffix(); .with_filtered_exe_suffix()
.with_managed_python_dirs();
// Request something that is not a Python version // Request something that is not a Python version
uv_snapshot!(context.filters(), context.python_install().arg("foobar"), @r###" uv_snapshot!(context.filters(), context.python_install().arg("foobar"), @r###"
@ -519,7 +524,8 @@ fn python_install_invalid_request() {
fn python_install_default() { fn python_install_default() {
let context: TestContext = TestContext::new_with_versions(&[]) let context: TestContext = TestContext::new_with_versions(&[])
.with_filtered_python_keys() .with_filtered_python_keys()
.with_filtered_exe_suffix(); .with_filtered_exe_suffix()
.with_managed_python_dirs();
let bin_python_minor_13 = context let bin_python_minor_13 = context
.temp_dir .temp_dir
@ -815,7 +821,7 @@ fn read_link_path(path: &Path) -> String {
#[test] #[test]
fn python_install_unknown() { fn python_install_unknown() {
let context: TestContext = TestContext::new_with_versions(&[]); let context: TestContext = TestContext::new_with_versions(&[]).with_managed_python_dirs();
// An unknown request // An unknown request
uv_snapshot!(context.filters(), context.python_install().arg("foobar"), @r###" uv_snapshot!(context.filters(), context.python_install().arg("foobar"), @r###"
@ -848,7 +854,8 @@ fn python_install_preview_broken_link() {
let context: TestContext = TestContext::new_with_versions(&[]) let context: TestContext = TestContext::new_with_versions(&[])
.with_filtered_python_keys() .with_filtered_python_keys()
.with_filtered_exe_suffix(); .with_filtered_exe_suffix()
.with_managed_python_dirs();
let bin_python = context.temp_dir.child("bin").child("python3.13"); let bin_python = context.temp_dir.child("bin").child("python3.13");
@ -883,7 +890,9 @@ fn python_dylib_install_name_is_patched_on_install() {
use assert_cmd::assert::OutputAssertExt; use assert_cmd::assert::OutputAssertExt;
use uv_python::managed::platform_key_from_env; use uv_python::managed::platform_key_from_env;
let context: TestContext = TestContext::new_with_versions(&[]).with_filtered_python_keys(); let context: TestContext = TestContext::new_with_versions(&[])
.with_filtered_python_keys()
.with_managed_python_dirs();
// Install the latest version // Install the latest version
context context

View file

@ -0,0 +1 @@
3.12

View file

@ -0,0 +1 @@
3.12