mirror of https://github.com/astral-sh/uv
Add test case for automatic installs (#10913)
This commit is contained in:
parent
10654cb8c2
commit
2a0fa8a8ee
|
|
@ -154,10 +154,17 @@ impl TestContext {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add extra standard filtering for executable suffixes on the current platform e.g.
|
/// Add extra standard filtering for Python interpreter sources
|
||||||
/// drops `.exe` on Windows.
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_filtered_python_sources(mut self) -> Self {
|
pub fn with_filtered_python_sources(mut self) -> Self {
|
||||||
|
self.filters.push((
|
||||||
|
"virtual environments, managed installations, or search path".to_string(),
|
||||||
|
"[PYTHON SOURCES]".to_string(),
|
||||||
|
));
|
||||||
|
self.filters.push((
|
||||||
|
"virtual environments, managed installations, search path, or registry".to_string(),
|
||||||
|
"[PYTHON SOURCES]".to_string(),
|
||||||
|
));
|
||||||
self.filters.push((
|
self.filters.push((
|
||||||
"managed installations or search path".to_string(),
|
"managed installations or search path".to_string(),
|
||||||
"[PYTHON SOURCES]".to_string(),
|
"[PYTHON SOURCES]".to_string(),
|
||||||
|
|
@ -240,17 +247,11 @@ impl TestContext {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_managed_python_dirs(mut self) -> Self {
|
pub fn with_managed_python_dirs(mut self) -> Self {
|
||||||
let managed = self.temp_dir.join("managed");
|
let managed = self.temp_dir.join("managed");
|
||||||
let bin = self.temp_dir.join("bin");
|
|
||||||
|
|
||||||
self.extra_env.push((
|
self.extra_env.push((
|
||||||
EnvVars::PATH.into(),
|
EnvVars::UV_PYTHON_BIN_DIR.into(),
|
||||||
env::join_paths(std::iter::once(bin.clone()).chain(env::split_paths(
|
self.bin_dir.as_os_str().to_owned(),
|
||||||
&env::var(EnvVars::PATH).unwrap_or_default(),
|
|
||||||
)))
|
|
||||||
.unwrap(),
|
|
||||||
));
|
));
|
||||||
self.extra_env
|
|
||||||
.push((EnvVars::UV_PYTHON_BIN_DIR.into(), bin.into()));
|
|
||||||
self.extra_env
|
self.extra_env
|
||||||
.push((EnvVars::UV_PYTHON_INSTALL_DIR.into(), managed.into()));
|
.push((EnvVars::UV_PYTHON_INSTALL_DIR.into(), managed.into()));
|
||||||
self.extra_env
|
self.extra_env
|
||||||
|
|
@ -360,6 +361,11 @@ impl TestContext {
|
||||||
filters.push((r#"link-mode = "copy"\n"#.to_string(), String::new()));
|
filters.push((r#"link-mode = "copy"\n"#.to_string(), String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filters.extend(
|
||||||
|
Self::path_patterns(&bin_dir)
|
||||||
|
.into_iter()
|
||||||
|
.map(|pattern| (pattern, "[BIN]/".to_string())),
|
||||||
|
);
|
||||||
filters.extend(
|
filters.extend(
|
||||||
Self::path_patterns(&cache_dir)
|
Self::path_patterns(&cache_dir)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -524,9 +530,10 @@ impl TestContext {
|
||||||
/// * Increase the stack size to avoid stack overflows on windows due to large async functions.
|
/// * Increase the stack size to avoid stack overflows on windows due to large async functions.
|
||||||
pub fn add_shared_args(&self, command: &mut Command, activate_venv: bool) {
|
pub fn add_shared_args(&self, command: &mut Command, activate_venv: bool) {
|
||||||
// Push the test context bin to the front of the PATH
|
// Push the test context bin to the front of the PATH
|
||||||
let mut path = OsString::from(self.bin_dir.as_ref());
|
let path = env::join_paths(std::iter::once(self.bin_dir.to_path_buf()).chain(
|
||||||
path.push(if cfg!(windows) { ";" } else { ":" });
|
env::split_paths(&env::var(EnvVars::PATH).unwrap_or_default()),
|
||||||
path.push(env::var(EnvVars::PATH).unwrap_or_default());
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
command
|
command
|
||||||
.arg("--cache-dir")
|
.arg("--cache-dir")
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,7 @@ fn python_install() {
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
let bin_python = context
|
let bin_python = context
|
||||||
.temp_dir
|
.bin_dir
|
||||||
.child("bin")
|
|
||||||
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// The executable should not be installed in the bin directory (requires preview)
|
// The executable should not be installed in the bin directory (requires preview)
|
||||||
|
|
@ -92,6 +91,117 @@ fn python_install() {
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn python_install_automatic() {
|
||||||
|
let context: TestContext = TestContext::new_with_versions(&[])
|
||||||
|
.with_filtered_python_keys()
|
||||||
|
.with_filtered_exe_suffix()
|
||||||
|
.with_filtered_python_sources()
|
||||||
|
.with_managed_python_dirs();
|
||||||
|
|
||||||
|
// With downloads disabled, the automatic install should fail
|
||||||
|
uv_snapshot!(context.filters(), context.run()
|
||||||
|
.env_remove("VIRTUAL_ENV")
|
||||||
|
.arg("--no-python-downloads")
|
||||||
|
.arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: No interpreter found in [PYTHON SOURCES]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Otherwise, we should fetch the latest Python version
|
||||||
|
uv_snapshot!(context.filters(), context.run()
|
||||||
|
.env_remove("VIRTUAL_ENV")
|
||||||
|
.arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
(3, 13)
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Subsequently, we can use the interpreter even with downloads disabled
|
||||||
|
uv_snapshot!(context.filters(), context.run()
|
||||||
|
.env_remove("VIRTUAL_ENV")
|
||||||
|
.arg("--no-python-downloads")
|
||||||
|
.arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
(3, 13)
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// We should respect the Python request
|
||||||
|
uv_snapshot!(context.filters(), context.run()
|
||||||
|
.env_remove("VIRTUAL_ENV")
|
||||||
|
.arg("-p").arg("3.12")
|
||||||
|
.arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
(3, 12)
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// But some requests cannot be mapped to a download
|
||||||
|
uv_snapshot!(context.filters(), context.run()
|
||||||
|
.env_remove("VIRTUAL_ENV")
|
||||||
|
.arg("-p").arg("foobar")
|
||||||
|
.arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: No interpreter found for executable name `foobar` in [PYTHON SOURCES]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Create a "broken" Python executable in the test context `bin`
|
||||||
|
// (the snapshot is different on Windows so we just test on Unix)
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
|
let contents = r"#!/bin/sh
|
||||||
|
echo 'error: intentionally broken python executable' >&2
|
||||||
|
exit 1";
|
||||||
|
let python = context
|
||||||
|
.bin_dir
|
||||||
|
.join(format!("python3{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
fs_err::write(&python, contents).unwrap();
|
||||||
|
|
||||||
|
let mut perms = fs_err::metadata(&python).unwrap().permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
fs_err::set_permissions(&python, perms).unwrap();
|
||||||
|
|
||||||
|
// We should ignore the broken executable and download a version still
|
||||||
|
uv_snapshot!(context.filters(), context.run()
|
||||||
|
.env_remove("VIRTUAL_ENV")
|
||||||
|
// In tests, we ignore `PATH` during Python discovery so we need to add the context `bin`
|
||||||
|
.env("UV_TEST_PYTHON_PATH", context.bin_dir.as_os_str())
|
||||||
|
.arg("-p").arg("3.11")
|
||||||
|
.arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: Failed to inspect Python interpreter from search path at `[BIN]/python3`
|
||||||
|
Caused by: Querying Python at `[BIN]/python3` failed with exit status exit status: 1
|
||||||
|
|
||||||
|
[stderr]
|
||||||
|
error: intentionally broken python executable
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn python_install_preview() {
|
fn python_install_preview() {
|
||||||
let context: TestContext = TestContext::new_with_versions(&[])
|
let context: TestContext = TestContext::new_with_versions(&[])
|
||||||
|
|
@ -111,8 +221,7 @@ fn python_install_preview() {
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
let bin_python = context
|
let bin_python = context
|
||||||
.temp_dir
|
.bin_dir
|
||||||
.child("bin")
|
|
||||||
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// The executable should be installed in the bin directory
|
// The executable should be installed in the bin directory
|
||||||
|
|
@ -182,7 +291,7 @@ fn python_install_preview() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: Failed to install cpython-3.13.1-[PLATFORM]
|
error: Failed to install cpython-3.13.1-[PLATFORM]
|
||||||
Caused by: Executable already exists at `[TEMP_DIR]/bin/python3.13` but is not managed by uv; use `--force` to replace it
|
Caused by: Executable already exists at `[BIN]/python3.13` but is not managed by uv; use `--force` to replace it
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--force").arg("3.13"), @r###"
|
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--force").arg("3.13"), @r###"
|
||||||
|
|
@ -243,8 +352,7 @@ fn python_install_preview() {
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
let bin_python = context
|
let bin_python = context
|
||||||
.temp_dir
|
.bin_dir
|
||||||
.child("bin")
|
|
||||||
.child(format!("python3.12{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3.12{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// The link should be for the newer patch version
|
// The link should be for the newer patch version
|
||||||
|
|
@ -275,8 +383,7 @@ fn python_install_preview_upgrade() {
|
||||||
.with_managed_python_dirs();
|
.with_managed_python_dirs();
|
||||||
|
|
||||||
let bin_python = context
|
let bin_python = context
|
||||||
.temp_dir
|
.bin_dir
|
||||||
.child("bin")
|
|
||||||
.child(format!("python3.12{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3.12{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// Install 3.12.5
|
// Install 3.12.5
|
||||||
|
|
@ -426,8 +533,7 @@ fn python_install_freethreaded() {
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
let bin_python = context
|
let bin_python = context
|
||||||
.temp_dir
|
.bin_dir
|
||||||
.child("bin")
|
|
||||||
.child(format!("python3.13t{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3.13t{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// The executable should be installed in the bin directory
|
// The executable should be installed in the bin directory
|
||||||
|
|
@ -528,18 +634,15 @@ fn python_install_default() {
|
||||||
.with_managed_python_dirs();
|
.with_managed_python_dirs();
|
||||||
|
|
||||||
let bin_python_minor_13 = context
|
let bin_python_minor_13 = context
|
||||||
.temp_dir
|
.bin_dir
|
||||||
.child("bin")
|
|
||||||
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
let bin_python_major = context
|
let bin_python_major = context
|
||||||
.temp_dir
|
.bin_dir
|
||||||
.child("bin")
|
|
||||||
.child(format!("python3{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
let bin_python_default = context
|
let bin_python_default = context
|
||||||
.temp_dir
|
.bin_dir
|
||||||
.child("bin")
|
|
||||||
.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// `--preview` is required for `--default`
|
// `--preview` is required for `--default`
|
||||||
|
|
@ -656,8 +759,7 @@ fn python_install_default() {
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
let bin_python_minor_12 = context
|
let bin_python_minor_12 = context
|
||||||
.temp_dir
|
.bin_dir
|
||||||
.child("bin")
|
|
||||||
.child(format!("python3.12{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3.12{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// All the executables should exist
|
// All the executables should exist
|
||||||
|
|
@ -857,10 +959,10 @@ fn python_install_preview_broken_link() {
|
||||||
.with_filtered_exe_suffix()
|
.with_filtered_exe_suffix()
|
||||||
.with_managed_python_dirs();
|
.with_managed_python_dirs();
|
||||||
|
|
||||||
let bin_python = context.temp_dir.child("bin").child("python3.13");
|
let bin_python = context.bin_dir.child("python3.13");
|
||||||
|
|
||||||
// Create a broken symlink
|
// Create a broken symlink
|
||||||
context.temp_dir.child("bin").create_dir_all().unwrap();
|
context.bin_dir.create_dir_all().unwrap();
|
||||||
symlink(context.temp_dir.join("does-not-exist"), &bin_python).unwrap();
|
symlink(context.temp_dir.join("does-not-exist"), &bin_python).unwrap();
|
||||||
|
|
||||||
// Install
|
// Install
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue