Skip to content

Commit 278295e

Browse files
zaniebkonstin
andauthored
Add test cases for find_uv_bin (#15110)
Adds test cases to unblock - #14181 - #14182 - #14184 - #14184 - tox-dev/pre-commit-uv#70 We use a package with a symlink to the Python module to get a mock installation of uv without building (or packaging) the uv binary. This lets us test real patterns like `uv pip install --prefix` without encoding logic about where things are placed during those installs. --------- Co-authored-by: konstin <konstin@mailbox.org>
1 parent aec90f0 commit 278295e

9 files changed

Lines changed: 442 additions & 75 deletions

File tree

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

Lines changed: 108 additions & 73 deletions
Large diffs are not rendered by default.

crates/uv/tests/it/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ mod python_find;
7878
#[cfg(feature = "python")]
7979
mod python_list;
8080

81+
#[cfg(all(feature = "python", feature = "pypi"))]
82+
mod python_module;
83+
8184
#[cfg(feature = "python-managed")]
8285
mod python_install;
8386

crates/uv/tests/it/pip_uninstall.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,14 @@ fn missing_record() -> Result<()> {
153153
fs_err::remove_file(dist_info.join("RECORD"))?;
154154

155155
uv_snapshot!(context.filters(), context.pip_uninstall()
156-
.arg("MarkupSafe"), @r###"
156+
.arg("MarkupSafe"), @r"
157157
success: false
158158
exit_code: 2
159159
----- stdout -----
160160
161161
----- stderr -----
162162
error: Cannot uninstall package; `RECORD` file not found at: [SITE_PACKAGES]/MarkupSafe-2.1.3.dist-info/RECORD
163-
"###
163+
"
164164
);
165165

166166
Ok(())
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
use assert_cmd::assert::OutputAssertExt;
2+
use assert_fs::prelude::{FileWriteStr, PathChild};
3+
use indoc::{formatdoc, indoc};
4+
5+
use uv_fs::Simplified;
6+
use uv_static::EnvVars;
7+
8+
use crate::common::{TestContext, site_packages_path, uv_snapshot};
9+
10+
/// Filter the user scheme, which differs between Windows and Unix.
11+
fn user_scheme_bin_filter() -> (String, String) {
12+
if cfg!(windows) {
13+
(
14+
r"\[USER_CONFIG_DIR\][\\/]Python[\\/]Python\d+".to_string(),
15+
"[USER_SCHEME]".to_string(),
16+
)
17+
} else {
18+
(r"\[HOME\]/\.local".to_string(), "[USER_SCHEME]".to_string())
19+
}
20+
}
21+
22+
#[test]
23+
fn find_uv_bin_venv() {
24+
let context = TestContext::new("3.12")
25+
.with_filtered_python_names()
26+
.with_filtered_virtualenv_bin()
27+
.with_filtered_exe_suffix()
28+
.with_filter(user_scheme_bin_filter());
29+
30+
// Install in a virtual environment
31+
uv_snapshot!(context.filters(), context.pip_install()
32+
.arg(context.workspace_root.join("scripts/packages/fake-uv")), @r"
33+
success: true
34+
exit_code: 0
35+
----- stdout -----
36+
37+
----- stderr -----
38+
Resolved 1 package in [TIME]
39+
Prepared 1 package in [TIME]
40+
Installed 1 package in [TIME]
41+
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
42+
"
43+
);
44+
45+
// We should find the binary in the virtual environment
46+
uv_snapshot!(context.filters(), context.python_command()
47+
.arg("-c")
48+
.arg("import uv; print(uv.find_uv_bin())"), @r"
49+
success: true
50+
exit_code: 0
51+
----- stdout -----
52+
[VENV]/[BIN]/uv
53+
54+
----- stderr -----
55+
"
56+
);
57+
}
58+
59+
#[test]
60+
fn find_uv_bin_target() {
61+
let context = TestContext::new("3.12")
62+
.with_filtered_python_names()
63+
.with_filtered_virtualenv_bin()
64+
.with_filtered_exe_suffix()
65+
.with_filter(user_scheme_bin_filter())
66+
// Target installs always use "bin" on all platforms. On Windows,
67+
// with_filtered_virtualenv_bin only filters "Scripts", not "bin"
68+
.with_filter((r"[\\/]bin[\\/]".to_string(), "/[BIN]/".to_string()));
69+
70+
// Install in a target directory
71+
uv_snapshot!(context.filters(), context.pip_install()
72+
.arg(context.workspace_root.join("scripts/packages/fake-uv"))
73+
.arg("--target")
74+
.arg("target"), @r"
75+
success: true
76+
exit_code: 0
77+
----- stdout -----
78+
79+
----- stderr -----
80+
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
81+
Resolved 1 package in [TIME]
82+
Prepared 1 package in [TIME]
83+
Installed 1 package in [TIME]
84+
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
85+
"
86+
);
87+
88+
// We should find the binary in the target directory
89+
uv_snapshot!(context.filters(), context.python_command()
90+
.arg("-c")
91+
.arg("import uv; print(uv.find_uv_bin())")
92+
.env(EnvVars::PYTHONPATH, context.temp_dir.child("target").path()), @r"
93+
success: true
94+
exit_code: 0
95+
----- stdout -----
96+
[TEMP_DIR]/target/[BIN]/uv
97+
98+
----- stderr -----
99+
"
100+
);
101+
}
102+
103+
#[test]
104+
fn find_uv_bin_prefix() {
105+
let context = TestContext::new("3.12")
106+
.with_filtered_python_names()
107+
.with_filtered_virtualenv_bin()
108+
.with_filtered_exe_suffix()
109+
.with_filter(user_scheme_bin_filter());
110+
111+
// Install in a prefix directory
112+
let prefix = context.temp_dir.child("prefix");
113+
114+
uv_snapshot!(context.filters(), context.pip_install()
115+
.arg(context.workspace_root.join("scripts/packages/fake-uv"))
116+
.arg("--prefix")
117+
.arg(prefix.path()), @r"
118+
success: true
119+
exit_code: 0
120+
----- stdout -----
121+
122+
----- stderr -----
123+
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
124+
Resolved 1 package in [TIME]
125+
Prepared 1 package in [TIME]
126+
Installed 1 package in [TIME]
127+
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
128+
"
129+
);
130+
131+
// We should find the binary in the prefix directory
132+
uv_snapshot!(context.filters(), context.python_command()
133+
.arg("-c")
134+
.arg("import uv; print(uv.find_uv_bin())")
135+
.env(
136+
EnvVars::PYTHONPATH,
137+
site_packages_path(&context.temp_dir.join("prefix"), "python3.12"),
138+
), @r#"
139+
success: false
140+
exit_code: 1
141+
----- stdout -----
142+
143+
----- stderr -----
144+
Traceback (most recent call last):
145+
File "<string>", line 1, in <module>
146+
File "[TEMP_DIR]/prefix/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 36, in find_uv_bin
147+
raise FileNotFoundError(path)
148+
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
149+
"#
150+
);
151+
}
152+
153+
#[test]
154+
fn find_uv_bin_base_prefix() {
155+
let context = TestContext::new("3.12")
156+
.with_filtered_python_names()
157+
.with_filtered_virtualenv_bin()
158+
.with_filtered_exe_suffix()
159+
.with_filter(user_scheme_bin_filter());
160+
161+
// Test base prefix fallback by mutating sys.base_prefix
162+
// First, create a "base" environment with fake-uv installed
163+
let base_venv = context.temp_dir.child("base-venv");
164+
context.venv().arg(base_venv.path()).assert().success();
165+
166+
// Install fake-uv in the "base" venv
167+
uv_snapshot!(context.filters(), context.pip_install()
168+
.arg("--python")
169+
.arg(base_venv.path())
170+
.arg(context.workspace_root.join("scripts/packages/fake-uv")), @r"
171+
success: true
172+
exit_code: 0
173+
----- stdout -----
174+
175+
----- stderr -----
176+
Using Python 3.12.[X] environment at: base-venv
177+
Resolved 1 package in [TIME]
178+
Prepared 1 package in [TIME]
179+
Installed 1 package in [TIME]
180+
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
181+
"
182+
);
183+
184+
context.venv().assert().success();
185+
186+
// Mutate `base_prefix` to simulate lookup in a system Python installation
187+
uv_snapshot!(context.filters(), context.python_command()
188+
.arg("-c")
189+
.arg(format!(r#"import sys, uv; sys.base_prefix = "{}"; print(uv.find_uv_bin())"#, base_venv.path().portable_display()))
190+
.env(EnvVars::PYTHONPATH, site_packages_path(base_venv.path(), "python3.12")), @r#"
191+
success: false
192+
exit_code: 1
193+
----- stdout -----
194+
195+
----- stderr -----
196+
Traceback (most recent call last):
197+
File "<string>", line 1, in <module>
198+
File "[TEMP_DIR]/base-venv/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 36, in find_uv_bin
199+
raise FileNotFoundError(path)
200+
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
201+
"#
202+
);
203+
}
204+
205+
#[test]
206+
fn find_uv_bin_in_ephemeral_environment() -> anyhow::Result<()> {
207+
let context = TestContext::new("3.12")
208+
.with_filtered_python_names()
209+
.with_filtered_virtualenv_bin()
210+
.with_filtered_exe_suffix()
211+
.with_filter(user_scheme_bin_filter());
212+
213+
// Create a minimal pyproject.toml
214+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
215+
pyproject_toml.write_str(indoc! { r#"
216+
[project]
217+
name = "test-project"
218+
version = "1.0.0"
219+
requires-python = ">=3.8"
220+
dependencies = []
221+
"#
222+
})?;
223+
224+
// We should find the binary in an ephemeral `--with` environment
225+
uv_snapshot!(context.filters(), context.run()
226+
.arg("--with")
227+
.arg(context.workspace_root.join("scripts/packages/fake-uv"))
228+
.arg("python")
229+
.arg("-c")
230+
.arg("import uv; print(uv.find_uv_bin())"), @r#"
231+
success: false
232+
exit_code: 1
233+
----- stdout -----
234+
235+
----- stderr -----
236+
Resolved 1 package in [TIME]
237+
Audited in [TIME]
238+
Resolved 1 package in [TIME]
239+
Prepared 1 package in [TIME]
240+
Installed 1 package in [TIME]
241+
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
242+
Traceback (most recent call last):
243+
File "<string>", line 1, in <module>
244+
File "[CACHE_DIR]/archive-v0/[HASH]/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 36, in find_uv_bin
245+
raise FileNotFoundError(path)
246+
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
247+
"#
248+
);
249+
250+
Ok(())
251+
}
252+
253+
#[test]
254+
fn find_uv_bin_in_parent_of_ephemeral_environment() -> anyhow::Result<()> {
255+
let context = TestContext::new("3.12")
256+
.with_filtered_python_names()
257+
.with_filtered_virtualenv_bin()
258+
.with_filtered_exe_suffix()
259+
.with_filter(user_scheme_bin_filter());
260+
261+
// Add the fake-uv package as a dependency
262+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
263+
pyproject_toml.write_str(&formatdoc! { r#"
264+
[project]
265+
name = "test-project"
266+
version = "1.0.0"
267+
requires-python = ">=3.8"
268+
dependencies = ["uv"]
269+
270+
[tool.uv.sources]
271+
uv = {{ path = "{}" }}
272+
"#,
273+
context.workspace_root.join("scripts/packages/fake-uv").portable_display()
274+
})?;
275+
276+
// When running in an ephemeral environment, we should find the binary in the project
277+
// environment
278+
uv_snapshot!(context.filters(), context.run()
279+
.arg("--with")
280+
.arg("anyio")
281+
.arg("python")
282+
.arg("-c")
283+
.arg("import uv; print(uv.find_uv_bin())"),
284+
@r#"
285+
success: false
286+
exit_code: 1
287+
----- stdout -----
288+
289+
----- stderr -----
290+
Resolved 2 packages in [TIME]
291+
Prepared 1 package in [TIME]
292+
Installed 1 package in [TIME]
293+
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
294+
Resolved 3 packages in [TIME]
295+
Prepared 3 packages in [TIME]
296+
Installed 3 packages in [TIME]
297+
+ anyio==4.3.0
298+
+ idna==3.6
299+
+ sniffio==1.3.1
300+
Traceback (most recent call last):
301+
File "<string>", line 1, in <module>
302+
File "[SITE_PACKAGES]/uv/_find_uv.py", line 36, in find_uv_bin
303+
raise FileNotFoundError(path)
304+
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
305+
"#
306+
);
307+
308+
Ok(())
309+
}

scripts/packages/fake-uv/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This fake uv package symlinks the Python module of uv in-tree and has a fake `uv` binary, allowing
2+
testing of the Python module behaviors. Consumers can replace the `uv` binary with a debug binary or
3+
similar if they need it to actually work.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[project]
2+
name = "uv"
3+
version = "0.1.0"
4+
requires-python = ">=3.8"
5+
6+
[tool.uv.build-backend.data]
7+
scripts = "scripts"
8+
9+
[build-system]
10+
requires = ["uv_build>=0.8.0,<0.9"]
11+
build-backend = "uv_build"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env sh
2+
3+
echo "This is a fake uv binary"
4+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is a fake uv binary

scripts/packages/fake-uv/src

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../python/

0 commit comments

Comments
 (0)