Skip to content

Commit 1c1d1a6

Browse files
committed
Add test cases
1 parent a2d3a66 commit 1c1d1a6

File tree

9 files changed

+411
-1
lines changed

9 files changed

+411
-1
lines changed

crates/uv-python/src/discovery.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,16 @@ fn python_executables_from_installed<'a>(
446446
.flatten();
447447

448448
match preference {
449-
PythonPreference::OnlyManaged => Box::new(from_managed_installations),
449+
PythonPreference::OnlyManaged => {
450+
// TODO(zanieb): Ideally, we'd create "fake" managed installation directories for tests,
451+
// but for now... we'll just include the test interpreters which are always on the
452+
// search path.
453+
if std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED).is_ok() {
454+
Box::new(from_managed_installations.chain(from_search_path))
455+
} else {
456+
Box::new(from_managed_installations)
457+
}
458+
}
450459
PythonPreference::Managed => Box::new(
451460
from_managed_installations
452461
.chain(from_search_path)

crates/uv-python/src/interpreter.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,23 @@ impl Interpreter {
268268
///
269269
/// Returns `false` if we cannot determine the path of the uv managed Python interpreters.
270270
pub fn is_managed(&self) -> bool {
271+
if let Ok(test_managed) =
272+
std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED)
273+
{
274+
// During testing, we collect interpreters into an artificial search path and need to
275+
// be able to mock whether an interpreter is managed or not.
276+
return test_managed.split_ascii_whitespace().any(|item| {
277+
let version = <PythonVersion as std::str::FromStr>::from_str(item).expect(
278+
"`UV_INTERNAL__TEST_PYTHON_MANAGED` items should be valid Python versions",
279+
);
280+
if version.patch().is_some() {
281+
version.version() == self.python_version()
282+
} else {
283+
(version.major(), version.minor()) == self.python_tuple()
284+
}
285+
});
286+
}
287+
271288
let Ok(installations) = ManagedPythonInstallations::from_settings(None) else {
272289
return false;
273290
};

crates/uv-static/src/env_vars.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,14 @@ impl EnvVars {
359359
#[attr_hidden]
360360
pub const UV_INTERNAL__SHOW_DERIVATION_TREE: &'static str = "UV_INTERNAL__SHOW_DERIVATION_TREE";
361361

362+
/// Used to set a temporary directory for some tests.
363+
#[attr_hidden]
364+
pub const UV_INTERNAL__TEST_DIR: &'static str = "UV_INTERNAL__TEST_DIR";
365+
366+
/// Used to force treating an interpreter as "managed" during tests.
367+
#[attr_hidden]
368+
pub const UV_INTERNAL__TEST_PYTHON_MANAGED: &'static str = "UV_INTERNAL__TEST_PYTHON_MANAGED";
369+
362370
/// Path to system-level configuration directory on Unix systems.
363371
pub const XDG_CONFIG_DIRS: &'static str = "XDG_CONFIG_DIRS";
364372

crates/uv/tests/it/common/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,18 @@ impl TestContext {
187187
"virtual environments, managed installations, search path, or registry".to_string(),
188188
"[PYTHON SOURCES]".to_string(),
189189
));
190+
self.filters.push((
191+
"virtual environments, search path, or registry".to_string(),
192+
"[PYTHON SOURCES]".to_string(),
193+
));
194+
self.filters.push((
195+
"virtual environments, registry, or search path".to_string(),
196+
"[PYTHON SOURCES]".to_string(),
197+
));
198+
self.filters.push((
199+
"virtual environments or search path".to_string(),
200+
"[PYTHON SOURCES]".to_string(),
201+
));
190202
self.filters.push((
191203
"managed installations or search path".to_string(),
192204
"[PYTHON SOURCES]".to_string(),
@@ -406,6 +418,15 @@ impl TestContext {
406418
self
407419
}
408420

421+
pub fn with_versions_as_managed(mut self, versions: &[&str]) -> Self {
422+
self.extra_env.push((
423+
EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED.into(),
424+
versions.iter().join(" ").into(),
425+
));
426+
427+
self
428+
}
429+
409430
/// Clear filters on `TestContext`.
410431
pub fn clear_filters(mut self) -> Self {
411432
self.filters.clear();

crates/uv/tests/it/pip_install.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11659,3 +11659,58 @@ fn strip_shebang_arguments() -> Result<()> {
1165911659

1166011660
Ok(())
1166111661
}
11662+
11663+
#[test]
11664+
fn install_python_preference() {
11665+
let context =
11666+
TestContext::new_with_versions(&["3.12", "3.11"]).with_versions_as_managed(&["3.12"]);
11667+
11668+
// Create a managed interpreter environment
11669+
uv_snapshot!(context.filters(), context.venv(), @r"
11670+
success: true
11671+
exit_code: 0
11672+
----- stdout -----
11673+
11674+
----- stderr -----
11675+
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
11676+
Creating virtual environment at: .venv
11677+
Activate with: source .venv/[BIN]/activate
11678+
");
11679+
11680+
// Install a package, requesting managed Python
11681+
uv_snapshot!(context.filters(), context.pip_install().arg("anyio").arg("--managed-python"), @r"
11682+
success: true
11683+
exit_code: 0
11684+
----- stdout -----
11685+
11686+
----- stderr -----
11687+
Resolved 3 packages in [TIME]
11688+
Prepared 3 packages in [TIME]
11689+
Installed 3 packages in [TIME]
11690+
+ anyio==4.3.0
11691+
+ idna==3.6
11692+
+ sniffio==1.3.1
11693+
");
11694+
11695+
// Install a package, requesting unmanaged Python
11696+
// This is allowed, because the virtual environment already exists
11697+
uv_snapshot!(context.filters(), context.pip_install().arg("anyio").arg("--no-managed-python"), @r"
11698+
success: true
11699+
exit_code: 0
11700+
----- stdout -----
11701+
11702+
----- stderr -----
11703+
Audited 1 package in [TIME]
11704+
");
11705+
11706+
// This also works with `VIRTUAL_ENV` unset
11707+
uv_snapshot!(context.filters(), context.pip_install()
11708+
.arg("anyio").arg("--no-managed-python").env_remove("VIRTUAL_ENV"), @r"
11709+
success: true
11710+
exit_code: 0
11711+
----- stdout -----
11712+
11713+
----- stderr -----
11714+
Audited 1 package in [TIME]
11715+
");
11716+
}

crates/uv/tests/it/python_find.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,57 @@ fn python_find_venv_invalid() {
729729
"###);
730730
}
731731

732+
#[test]
733+
fn python_find_managed() {
734+
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"])
735+
.with_filtered_python_sources()
736+
.with_versions_as_managed(&["3.12"]);
737+
738+
// We find the managed interpreter
739+
uv_snapshot!(context.filters(), context.python_find().arg("--managed-python"), @r"
740+
success: true
741+
exit_code: 0
742+
----- stdout -----
743+
[PYTHON-3.12]
744+
745+
----- stderr -----
746+
");
747+
748+
// Request an interpreter that cannot be satisfied
749+
uv_snapshot!(context.filters(), context.python_find().arg("--managed-python").arg("3.11"), @r"
750+
success: false
751+
exit_code: 2
752+
----- stdout -----
753+
754+
----- stderr -----
755+
error: No interpreter found for Python 3.11 in virtual environments or managed installations
756+
");
757+
758+
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"])
759+
.with_filtered_python_sources()
760+
.with_versions_as_managed(&["3.11"]);
761+
762+
// We find the unmanaged interpreter
763+
uv_snapshot!(context.filters(), context.python_find().arg("--no-managed-python"), @r"
764+
success: true
765+
exit_code: 0
766+
----- stdout -----
767+
[PYTHON-3.12]
768+
769+
----- stderr -----
770+
");
771+
772+
// Request an interpreter that cannot be satisfied
773+
uv_snapshot!(context.filters(), context.python_find().arg("--no-managed-python").arg("3.11"), @r"
774+
success: false
775+
exit_code: 2
776+
----- stdout -----
777+
778+
----- stderr -----
779+
error: No interpreter found for Python 3.11 in [PYTHON SOURCES]
780+
");
781+
}
782+
732783
/// See: <https://github.com/astral-sh/uv/issues/11825>
733784
///
734785
/// This test will not succeed on macOS if using a Homebrew provided interpreter. The interpreter

crates/uv/tests/it/run.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5500,3 +5500,49 @@ fn run_no_sync_incompatible_python() -> Result<()> {
55005500

55015501
Ok(())
55025502
}
5503+
5504+
#[test]
5505+
fn run_python_preference_no_project() {
5506+
let context =
5507+
TestContext::new_with_versions(&["3.12", "3.11"]).with_versions_as_managed(&["3.12"]);
5508+
5509+
context.venv().assert().success();
5510+
5511+
uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r"
5512+
success: true
5513+
exit_code: 0
5514+
----- stdout -----
5515+
Python 3.12.[X]
5516+
5517+
----- stderr -----
5518+
");
5519+
5520+
uv_snapshot!(context.filters(), context.run().arg("--managed-python").arg("python").arg("--version"), @r"
5521+
success: true
5522+
exit_code: 0
5523+
----- stdout -----
5524+
Python 3.12.[X]
5525+
5526+
----- stderr -----
5527+
");
5528+
5529+
// `VIRTUAL_ENV` is set here, so we'll ignore the flag
5530+
uv_snapshot!(context.filters(), context.run().arg("--no-managed-python").arg("python").arg("--version"), @r"
5531+
success: true
5532+
exit_code: 0
5533+
----- stdout -----
5534+
Python 3.12.[X]
5535+
5536+
----- stderr -----
5537+
");
5538+
5539+
// If we remove the `VIRTUAL_ENV` variable, we should get the unmanaged Python
5540+
uv_snapshot!(context.filters(), context.run().arg("--no-managed-python").arg("python").arg("--version").env_remove("VIRTUAL_ENV"), @r"
5541+
success: true
5542+
exit_code: 0
5543+
----- stdout -----
5544+
Python 3.11.[X]
5545+
5546+
----- stderr -----
5547+
");
5548+
}

0 commit comments

Comments
 (0)