diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 4c845d680fd91..fb7adff0c4b4e 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -212,12 +212,18 @@ impl TestContext { self } - /// Adds a filter that ignores platform information in a Python installation key. + /// Adds a filter that ignores platform and patch information in a Python installation key. pub fn with_filtered_python_keys(mut self) -> Self { + // Filter platform keys self.filters.push(( - r"((?:cpython|pypy)-\d+\.\d+(:?\.\d+)?[a-z]?(:?\+[a-z]+)?)-.*".to_string(), + r"((?:cpython|pypy)-\d+\.\d+(?:\.(?:\[X\]|\d+))?[a-z]?(?:\+[a-z]+)?)-.*".to_string(), "$1-[PLATFORM]".to_string(), )); + // Filter patch versions + self.filters.push(( + r"((?:cpython|pypy)-\d+\.\d+)\.\d+([a-z])?".to_string(), + "$1.[X]$2".to_string(), + )); self } @@ -673,6 +679,19 @@ impl TestContext { command } + /// Create a `uv python list` command with options shared across scenarios. + pub fn python_list(&self) -> Command { + let mut command = Command::new(get_bin()); + command + .arg("python") + .arg("list") + .env("UV_PREVIEW", "1") + .env("UV_PYTHON_INSTALL_DIR", "") + .current_dir(&self.temp_dir); + self.add_shared_args(&mut command, false); + command + } + /// Create a `uv python pin` command with options shared across scenarios. pub fn python_pin(&self) -> Command { let mut command = Command::new(get_bin()); diff --git a/crates/uv/tests/it/main.rs b/crates/uv/tests/it/main.rs index 74393449a10b9..62dd1951de6ec 100644 --- a/crates/uv/tests/it/main.rs +++ b/crates/uv/tests/it/main.rs @@ -72,6 +72,9 @@ mod python_find; #[cfg(feature = "python-managed")] mod python_install; +#[cfg(feature = "python")] +mod python_list; + #[cfg(feature = "python")] mod python_pin; diff --git a/crates/uv/tests/it/python_list.rs b/crates/uv/tests/it/python_list.rs new file mode 100644 index 0000000000000..e1c52fa785feb --- /dev/null +++ b/crates/uv/tests/it/python_list.rs @@ -0,0 +1,95 @@ +use assert_fs::prelude::PathChild; + +use crate::common::{uv_snapshot, TestContext}; + +#[test] +fn python_list() { + let context: TestContext = + TestContext::new_with_versions(&["3.11", "3.12"]).with_filtered_python_keys(); + + let filters: Vec<_> = [("-> .*", "-> [LINK PATH]")] + .into_iter() + .chain(context.filters()) + .collect(); + + uv_snapshot!(filters, context.python_list(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + cpython-3.13.[X]+freethreaded-[PLATFORM] + cpython-3.12.[X]-[PLATFORM] + cpython-3.12.[X]-[PLATFORM] + cpython-3.11.[X]-[PLATFORM] + cpython-3.11.[X]-[PLATFORM] + cpython-3.10.[X]-[PLATFORM] + cpython-3.9.[X]-[PLATFORM] + cpython-3.8.[X]-[PLATFORM] + pypy-3.10.[X]-[PLATFORM] + pypy-3.9.[X]-[PLATFORM] + pypy-3.8.[X]-[PLATFORM] + + ----- stderr ----- + "###); +} + +#[test] +fn python_list_no_versions() { + let context: TestContext = TestContext::new_with_versions(&[]).with_filtered_python_keys(); + + uv_snapshot!(context.filters(), context.python_list().env("UV_TEST_PYTHON_PATH", ""), @r###" + success: true + exit_code: 0 + ----- stdout ----- + cpython-3.13.[X]+freethreaded-[PLATFORM] + cpython-3.12.[X]-[PLATFORM] + cpython-3.11.[X]-[PLATFORM] + cpython-3.10.[X]-[PLATFORM] + cpython-3.9.[X]-[PLATFORM] + cpython-3.8.[X]-[PLATFORM] + pypy-3.10.[X]-[PLATFORM] + pypy-3.9.[X]-[PLATFORM] + pypy-3.8.[X]-[PLATFORM] + + ----- stderr ----- + "###); +} + +#[cfg(unix)] +#[test] +fn python_list_symlink() { + let context: TestContext = + TestContext::new_with_versions(&["3.11", "3.12"]).with_filtered_python_keys(); + + let filters: Vec<_> = [("-> .*", "-> [LINK PATH]")] + .into_iter() + .chain(context.filters()) + .collect(); + + let target = &context.python_versions.first().unwrap().1; + let link = context.temp_dir.child("python"); + fs_err::os::unix::fs::symlink(target, &link).unwrap(); + + let mut path = context.python_path(); + path.push(":"); + path.push(link.parent().unwrap().as_os_str()); + + uv_snapshot!(filters, context.python_list().env("UV_TEST_PYTHON_PATH", path), @r###" + success: true + exit_code: 0 + ----- stdout ----- + cpython-3.13.[X]+freethreaded-[PLATFORM] + cpython-3.12.[X]-[PLATFORM] + cpython-3.12.[X]-[PLATFORM] + cpython-3.11.[X]-[PLATFORM] + cpython-3.11.[X]-[PLATFORM] + cpython-3.11.[X]-[PLATFORM] + cpython-3.10.[X]-[PLATFORM] + cpython-3.9.[X]-[PLATFORM] + cpython-3.8.[X]-[PLATFORM] + pypy-3.10.[X]-[PLATFORM] + pypy-3.9.[X]-[PLATFORM] + pypy-3.8.[X]-[PLATFORM] + + ----- stderr ----- + "###); +}