use assert_cmd::assert::OutputAssertExt;
use assert_fs::prelude::{FileTouch, PathChild};
use assert_fs::{fixture::FileWriteStr, prelude::PathCreateDir};
use indoc::indoc;
use uv_platform::{Arch, Os};
use uv_static::EnvVars;
use uv_test::{uv_snapshot, venv_bin_path};
#[test]
fn python_find() {
let mut context =
uv_test::test_context_with_versions!(&["3.11", "3.12"]).with_filtered_python_sources();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, ""), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found in [PYTHON SOURCES]
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.12"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("==3.12.*"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("cpython"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("cpython@3.12"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("cpython-3.12"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("any-3.12-any"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
let os = Os::from_env();
let arch = Arch::from_env();
uv_snapshot!(context.filters(), context.python_find()
.arg(format!("cpython-3.12-{os}-{arch}")), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("pypy"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for PyPy in [PYTHON SOURCES]
");
context.python_versions.reverse();
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
}
#[test]
fn python_find_pin() {
let context = uv_test::test_context_with_versions!(&["3.11", "3.12"]);
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @"
success: true
exit_code: 0
----- stdout -----
Pinned `.python-version` to `3.12`
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("--no-config"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
let child_dir = context.temp_dir.child("child");
child_dir.create_dir_all().unwrap();
uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_pin().arg("3.11").current_dir(&child_dir), @"
success: true
exit_code: 0
----- stdout -----
Pinned `.python-version` to `3.11`
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
}
#[test]
fn python_find_pin_arbitrary_name() {
let context = uv_test::test_context_with_versions!(&["3.11", "3.12"]);
uv_snapshot!(context.filters(), context.python_pin().arg("foo"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Requests for arbitrary names (e.g., `foo`) are not supported in version files
");
context
.temp_dir
.child(".python-version")
.write_str("foo")
.unwrap();
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
warning: Ignoring unsupported Python request `foo` in version file: [TEMP_DIR]/.python-version
");
uv_snapshot!(context.filters(), context.python_pin().arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
Pinned `.python-version` to `3.11`
----- stderr -----
warning: Ignoring unsupported Python request `foo` in version file: [TEMP_DIR]/.python-version
");
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @"
success: true
exit_code: 0
----- stdout -----
Updated `.python-version` from `3.11` -> `3.12`
----- stderr -----
");
context.temp_dir.child("foo").create_dir_all().unwrap();
context
.temp_dir
.child("foo")
.child(".python-version")
.write_str("foo")
.unwrap();
uv_snapshot!(context.filters(), context.python_find().current_dir(context.temp_dir.child("foo").path()), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
warning: Ignoring unsupported Python request `foo` in version file: [TEMP_DIR]/foo/.python-version
");
}
#[test]
fn python_find_project() {
let context = uv_test::test_context_with_versions!(&["3.10", "3.11", "3.12"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml
.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["anyio==3.7.0"]
"#})
.unwrap();
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.10"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.10]
----- stderr -----
warning: The requested interpreter resolved to Python 3.10.[X], which is incompatible with the project's Python requirement: `>=3.11` (from `project.requires-python`)
");
uv_snapshot!(context.filters(), context.python_find().arg("--no-project"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.10]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @"
success: true
exit_code: 0
----- stdout -----
Pinned `.python-version` to `3.12`
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_pin().arg("3.10").arg("--no-workspace"), @"
success: true
exit_code: 0
----- stdout -----
Updated `.python-version` from `3.12` -> `3.10`
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.10]
----- stderr -----
warning: The Python request from `.python-version` resolved to Python 3.10.[X], which is incompatible with the project's Python requirement: `>=3.11` (from `project.requires-python`)
Use `uv python pin` to update the `.python-version` file to a compatible version
");
let child_dir = context.temp_dir.child("child");
child_dir.create_dir_all().unwrap();
let pyproject_toml = child_dir.child("pyproject.toml");
pyproject_toml
.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["anyio==3.7.0"]
"#})
.unwrap();
uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
}
#[test]
fn virtual_empty() {
let context = uv_test::test_context_with_versions!(&["3.10", "3.11", "3.12"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml
.write_str(indoc! {r#"
[tool.mycooltool]
wow = "someconfig"
"#})
.unwrap();
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.10]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find()
.arg("--no-project"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.10]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @"
success: true
exit_code: 0
----- stdout -----
Pinned `.python-version` to `3.12`
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
}
#[test]
fn virtual_dependency_group() {
let context = uv_test::test_context_with_versions!(&["3.10", "3.11", "3.12"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml
.write_str(indoc! {r#"
[dependency-groups]
foo = ["sortedcontainers"]
bar = ["iniconfig"]
dev = ["sniffio"]
"#})
.unwrap();
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.10]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find()
.arg("--no-project"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.10]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @"
success: true
exit_code: 0
----- stdout -----
Pinned `.python-version` to `3.12`
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
}
#[test]
fn python_find_venv() {
let context = uv_test::test_context_with_versions!(&["3.11", "3.12"])
.with_filtered_exe_suffix()
.with_filtered_python_names()
.with_filtered_virtualenv_bin();
uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.12").arg("-q"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
");
#[cfg(not(windows))]
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[VENV]/[BIN]/[PYTHON]
----- stderr -----
");
let child_dir = context.temp_dir.child("child");
child_dir.create_dir_all().unwrap();
uv_snapshot!(context.filters(), context.python_find().arg("--system"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_SYSTEM_PYTHON, "1"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("--no-system").env(EnvVars::UV_SYSTEM_PYTHON, "1"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: the argument '--no-system' cannot be used with '--system'
Usage: uv python find --cache-dir [CACHE_DIR] [REQUEST]
For more information, try '--help'.
");
#[cfg(not(windows))]
uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir), @"
success: true
exit_code: 0
----- stdout -----
[VENV]/[BIN]/[PYTHON]
----- stderr -----
");
uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11").arg("-q").current_dir(&child_dir), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
");
#[cfg(not(windows))]
uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/.venv/[BIN]/[PYTHON]
----- stderr -----
");
fs_err::remove_dir_all(context.temp_dir.child(".venv")).unwrap();
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
#[cfg(not(windows))]
uv_snapshot!(context.filters(), context.python_find().arg("child/.venv"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/.venv/[BIN]/[PYTHON]
----- stderr -----
");
#[cfg(not(windows))]
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, child_dir.join(".venv").as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/.venv/[BIN]/[PYTHON]
----- stderr -----
");
#[cfg(not(windows))]
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, child_dir.join(".venv").join("bin").as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/.venv/[BIN]/[PYTHON]
----- stderr -----
");
#[cfg(not(windows))]
{
let path = std::env::join_paths(&[
context.temp_dir.to_path_buf(),
child_dir.join(".venv").join("bin"),
])
.unwrap();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/.venv/[BIN]/[PYTHON]
----- stderr -----
");
}
#[cfg(not(windows))]
{
let path = std::env::join_paths(
std::env::split_paths(&context.python_path())
.chain(std::iter::once(child_dir.join(".venv").join("bin"))),
)
.unwrap();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
}
}
#[cfg(unix)]
#[test]
fn python_find_unsupported_version() {
let context = uv_test::test_context_with_versions!(&["3.12"]);
uv_snapshot!(context.filters(), context.python_find().arg("3.5"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Invalid version request: Python <3.6 is not supported but 3.5 was requested.
");
uv_snapshot!(context.filters(), context.python_find().arg("3.5.9"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Invalid version request: Python <3.6 is not supported but 3.5.9 was requested.
");
uv_snapshot!(context.filters(), context.python_find().arg("2.6"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Invalid version request: Python <3.6 is not supported but 2.6 was requested.
");
uv_snapshot!(context.filters(), context.python_find().arg("2.6.8"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Invalid version request: Python <3.6 is not supported but 2.6.8 was requested.
");
uv_snapshot!(context.filters(), context.python_find().arg("4.2"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python 4.2 in virtual environments, managed installations, or search path
");
uv_snapshot!(context.filters(), context.python_find().arg("<3.0"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python <3.0 in virtual environments, managed installations, or search path
");
uv_snapshot!(context.filters(), context.python_find().arg("3.12t"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Invalid version request: Python <3.13 does not support free-threading but 3.12+freethreaded was requested.
");
}
#[test]
fn python_find_venv_invalid() {
let context = uv_test::test_context!("3.12")
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[VENV]/[BIN]/[PYTHON]
----- stderr -----
");
fs_err::remove_dir_all(venv_bin_path(&context.venv)).unwrap();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str()), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to inspect Python interpreter from active virtual environment at `.venv/[BIN]/[PYTHON]`
Caused by: Python interpreter not found at `[VENV]/[BIN]/[PYTHON]`
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
fs_err::remove_file(context.venv.join("pyvenv.cfg")).unwrap();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
}
#[test]
fn python_find_managed() {
let context = uv_test::test_context_with_versions!(&["3.11", "3.12"])
.with_filtered_python_sources()
.with_versions_as_managed(&["3.12"]);
uv_snapshot!(context.filters(), context.python_find().arg("--managed-python"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("--managed-python").arg("3.11"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python 3.11 in virtual environments or managed installations
");
let context = uv_test::test_context_with_versions!(&["3.11", "3.12"])
.with_filtered_python_sources()
.with_versions_as_managed(&["3.11"]);
uv_snapshot!(context.filters(), context.python_find().arg("--no-managed-python"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("--no-managed-python").arg("3.11"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python 3.11 in [PYTHON SOURCES]
");
uv_snapshot!(context.filters(), context.python_find().arg("--python-preference").arg("system"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("--python-preference").arg("system").arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
}
#[test]
#[cfg(unix)]
#[cfg(feature = "test-python-managed")]
fn python_required_python_major_minor() {
let context = uv_test::test_context_with_versions!(&["3.11", "3.12"]);
let path = &context.python_versions.first().unwrap().1;
fs_err::create_dir_all(context.temp_dir.child("child")).unwrap();
fs_err::os::unix::fs::symlink(path, context.temp_dir.child("child").join("python3.11"))
.unwrap();
uv_snapshot!(context.filters(), context.python_find().arg(">=3.11.4, <3.12").env(EnvVars::UV_PYTHON_SEARCH_PATH, context.temp_dir.child("child").path()), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/python3.11
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg(">3.11.4, <3.12").env(EnvVars::UV_PYTHON_SEARCH_PATH, context.temp_dir.child("child").path()), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/python3.11
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg(">3.11.255, <3.12").env(EnvVars::UV_PYTHON_SEARCH_PATH, context.temp_dir.child("child").path()), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python >3.11.[X], <3.12 in virtual environments, managed installations, or search path
");
}
#[test]
fn python_find_script() {
let context = uv_test::test_context!("3.13")
.with_filtered_virtualenv_bin()
.with_filtered_python_names()
.with_filtered_exe_suffix();
uv_snapshot!(context.filters(), context.init().arg("--script").arg("foo.py"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Initialized script at `foo.py`
");
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("foo.py"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Creating script environment at: [CACHE_DIR]/environments-v2/foo-[HASH]
Resolved in [TIME]
Checked in [TIME]
");
uv_snapshot!(context.filters(), context.python_find().arg("--script").arg("foo.py"), @"
success: true
exit_code: 0
----- stdout -----
[CACHE_DIR]/environments-v2/foo-[HASH]/[BIN]/[PYTHON]
----- stderr -----
");
}
#[test]
fn python_find_script_no_environment() {
let context = uv_test::test_context!("3.13")
.with_filtered_virtualenv_bin()
.with_filtered_python_names()
.with_filtered_exe_suffix();
let script = context.temp_dir.child("foo.py");
script
.write_str(indoc! {r"
# /// script
# dependencies = []
# ///
"})
.unwrap();
uv_snapshot!(context.filters(), context.python_find().arg("--script").arg("foo.py"), @"
success: true
exit_code: 0
----- stdout -----
[VENV]/[BIN]/[PYTHON]
----- stderr -----
");
}
#[test]
fn python_find_script_python_not_found() {
let context = uv_test::test_context_with_versions!(&[]).with_filtered_python_sources();
let script = context.temp_dir.child("foo.py");
script
.write_str(indoc! {r"
# /// script
# dependencies = []
# ///
"})
.unwrap();
uv_snapshot!(context.filters(), context.python_find().arg("--script").arg("foo.py"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
No interpreter found in [PYTHON SOURCES]
hint: A managed Python download is available, but Python downloads are set to 'never'
");
}
#[test]
fn python_find_script_no_such_version() {
let context = uv_test::test_context!("3.13")
.with_filtered_virtualenv_bin()
.with_filtered_python_names()
.with_filtered_exe_suffix()
.with_filtered_python_sources();
let script = context.temp_dir.child("foo.py");
script
.write_str(indoc! {r#"
# /// script
# requires-python = ">=3.13"
# dependencies = []
# ///
"#})
.unwrap();
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("foo.py"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Creating script environment at: [CACHE_DIR]/environments-v2/foo-[HASH]
Resolved in [TIME]
Checked in [TIME]
");
script
.write_str(indoc! {r#"
# /// script
# requires-python = ">=3.15"
# dependencies = []
# ///
"#})
.unwrap();
uv_snapshot!(context.filters(), context.python_find().arg("--script").arg("foo.py"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
No interpreter found for Python >=3.15 in [PYTHON SOURCES]
");
}
#[test]
fn python_find_show_version() {
let context =
uv_test::test_context_with_versions!(&["3.11", "3.12"]).with_filtered_python_sources();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, "").arg("--show-version"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found in [PYTHON SOURCES]
");
uv_snapshot!(context.filters(), context.python_find().arg("--show-version"), @"
success: true
exit_code: 0
----- stdout -----
3.11.[X]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("--show-version").arg("3.12"), @"
success: true
exit_code: 0
----- stdout -----
3.12.[X]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("--show-version").arg("3.11"), @"
success: true
exit_code: 0
----- stdout -----
3.11.[X]
----- stderr -----
");
}
#[test]
fn python_find_path() {
let context = uv_test::test_context_with_versions!(&[]).with_filtered_not_executable();
context.temp_dir.child("foo").create_dir_all().unwrap();
context.temp_dir.child("bar").touch().unwrap();
uv_snapshot!(context.filters(), context.python_find().arg(context.temp_dir.child("foo").as_os_str()), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found in directory `foo`
");
uv_snapshot!(context.filters(), context.python_find().arg(context.temp_dir.child("bar").as_os_str()), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to inspect Python interpreter from provided path at `bar`
Caused by: Failed to query Python interpreter at `[TEMP_DIR]/bar`
Caused by: [PERMISSION DENIED]
");
uv_snapshot!(context.filters(), context.python_find().arg(context.temp_dir.child("foobar").as_os_str()), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found at path `foobar`
");
}
#[test]
#[cfg(feature = "test-python-managed")]
fn python_find_freethreaded_313() {
let context = uv_test::test_context_with_versions!(&[])
.with_filtered_python_keys()
.with_filtered_python_sources()
.with_managed_python_dirs()
.with_python_download_cache()
.with_filtered_python_install_bin()
.with_filtered_python_names()
.with_filtered_exe_suffix();
context
.python_install()
.arg("--preview")
.arg("3.13t")
.assert()
.success();
uv_snapshot!(context.filters(), context.python_find().arg("3.13"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python 3.13 in [PYTHON SOURCES]
");
uv_snapshot!(context.filters(), context.python_find().arg("3.13t"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.13+freethreaded-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
}
#[test]
#[cfg(feature = "test-python-managed")]
fn python_find_freethreaded_314() {
let context = uv_test::test_context_with_versions!(&[])
.with_filtered_python_keys()
.with_filtered_python_sources()
.with_managed_python_dirs()
.with_python_download_cache()
.with_filtered_python_install_bin()
.with_filtered_python_names()
.with_filtered_exe_suffix();
context
.python_install()
.arg("--preview")
.arg("3.14t")
.assert()
.success();
uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14+freethreaded-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.14t"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14+freethreaded-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.14+gil"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python 3.14+gil in [PYTHON SOURCES]
");
context
.python_install()
.arg("--preview")
.arg("3.14")
.assert()
.success();
uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("3.14+gil"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
}
#[test]
#[cfg(feature = "test-python-managed")]
fn python_find_prerelease_version_specifiers() {
let context = uv_test::test_context_with_versions!(&[])
.with_filtered_python_keys()
.with_filtered_python_sources()
.with_managed_python_dirs()
.with_python_download_cache()
.with_filtered_python_install_bin()
.with_filtered_python_names()
.with_filtered_exe_suffix();
context.python_install().arg("3.14.0rc2").assert().success();
context.python_install().arg("3.14.0rc3").assert().success();
uv_snapshot!(context.filters(), context.python_find().arg(">=3.14").arg("--resolve-links"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0rc3-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
warning: You're using a pre-release version of Python (3.14.0rc3) but a stable version is available. Use `uv python upgrade 3.14` to upgrade.
");
uv_snapshot!(context.filters(), context.python_find().arg(">3.14.0rc2").arg("--resolve-links"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0rc3-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg(">3.14.0rc3"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python >3.14.0rc3 in [PYTHON SOURCES]
");
uv_snapshot!(context.filters(), context.python_find().arg(">=3.14.0rc3").arg("--resolve-links"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0rc3-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("<3.14.0rc3").arg("--resolve-links"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0rc2-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg("<=3.14.0rc3").arg("--resolve-links"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0rc3-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
context.python_install().arg("3.14.0").assert().success();
uv_snapshot!(context.filters(), context.python_find().arg(">=3.14").arg("--resolve-links"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
uv_snapshot!(context.filters(), context.python_find().arg(">3.14.0rc2").arg("--resolve-links"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
}
#[test]
#[cfg(feature = "test-python-managed")]
fn python_find_prerelease_with_patch_request() {
let context = uv_test::test_context_with_versions!(&[])
.with_filtered_python_keys()
.with_filtered_python_sources()
.with_managed_python_dirs()
.with_python_download_cache()
.with_filtered_python_install_bin()
.with_filtered_python_names()
.with_filtered_exe_suffix();
context.python_install().arg("3.14.0rc3").assert().success();
uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
warning: You're using a pre-release version of Python (3.14.0rc3) but a stable version is available. Use `uv python upgrade 3.14` to upgrade.
");
uv_snapshot!(context.filters(), context.python_find().arg("3.14.0"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python 3.14.0 in [PYTHON SOURCES]
");
context.python_install().arg("3.14.0").assert().success();
uv_snapshot!(context.filters(), context.python_find().arg("3.14.0"), @"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
}
#[test]
fn python_find_equal() {
let context = uv_test::test_context_with_versions!(&["3.11", "3.12"]);
uv_snapshot!(context.filters(), context.python_find().arg("==3.11"), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
"###);
uv_snapshot!(context.filters(), context.python_find().arg("==3.12"), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
"###);
}
#[test]
fn python_find_search_path() {
let context =
uv_test::test_context_with_versions!(&["3.11", "3.12"]).with_filtered_python_sources();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, ""), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found in [PYTHON SOURCES]
");
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
");
let python_path_3_12_only = std::env::join_paths(
std::env::split_paths(&context.python_path())
.filter(|p| p.to_string_lossy().contains("3.12")),
)
.unwrap();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, python_path_3_12_only.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
let reversed_path = std::env::join_paths(
std::env::split_paths(&context.python_path())
.collect::<Vec<_>>()
.into_iter()
.rev(),
)
.unwrap();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, reversed_path.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
");
}
#[test]
#[cfg(unix)]
fn python_find_project_requires_python_minor_range() {
let context = uv_test::test_context_with_versions!(&["3.11", "3.12"]);
let child = context.temp_dir.child("child");
child.create_dir_all().unwrap();
let python_3_12 = &context.python_versions.last().unwrap().1;
fs_err::os::unix::fs::symlink(python_3_12, child.join("python3.12")).unwrap();
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml
.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = "==3.12"
dependencies = []
"#})
.unwrap();
uv_snapshot!(context.filters(), context.python_find()
.env(EnvVars::UV_PYTHON_SEARCH_PATH, child.path()), @r#"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/python3.12
----- stderr -----
warning: The resolved Python interpreter (Python 3.12.[X]) is incompatible with the project's Python requirement: `==3.12` (from `project.requires-python`)
"#);
pyproject_toml
.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = "==3.12.*"
dependencies = []
"#})
.unwrap();
uv_snapshot!(context.filters(), context.python_find()
.env(EnvVars::UV_PYTHON_SEARCH_PATH, child.path()), @r#"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/python3.12
----- stderr -----
"#);
pyproject_toml
.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = "~=3.12.0"
dependencies = []
"#})
.unwrap();
uv_snapshot!(context.filters(), context.python_find()
.env(EnvVars::UV_PYTHON_SEARCH_PATH, child.path()), @r#"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/child/python3.12
----- stderr -----
"#);
}