mirror of https://github.com/astral-sh/uv
Add test cases for `find_uv_bin` (#15110)
Adds test cases to unblock - https://github.com/astral-sh/uv/pull/14181 - https://github.com/astral-sh/uv/pull/14182 - https://github.com/astral-sh/uv/pull/14184 - https://github.com/astral-sh/uv/pull/14184 - https://github.com/tox-dev/pre-commit-uv/issues/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>
This commit is contained in:
parent
aec90f0a3c
commit
278295ef02
|
|
@ -465,6 +465,12 @@ impl TestContext {
|
|||
self
|
||||
}
|
||||
|
||||
/// Add a custom filter to the `TestContext`.
|
||||
pub fn with_filter(mut self, filter: (String, String)) -> Self {
|
||||
self.filters.push(filter);
|
||||
self
|
||||
}
|
||||
|
||||
/// Clear filters on `TestContext`.
|
||||
pub fn clear_filters(mut self) -> Self {
|
||||
self.filters.clear();
|
||||
|
|
@ -608,6 +614,26 @@ impl TestContext {
|
|||
.into_iter()
|
||||
.map(|pattern| (pattern, "[VENV]/".to_string())),
|
||||
);
|
||||
|
||||
// Account for [`Simplified::user_display`] which is relative to the command working directory
|
||||
if let Some(site_packages) = site_packages {
|
||||
filters.push((
|
||||
Self::path_pattern(
|
||||
site_packages
|
||||
.strip_prefix(&canonical_temp_dir)
|
||||
.expect("The test site-packages directory is always in the tempdir"),
|
||||
),
|
||||
"[SITE_PACKAGES]/".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Filter Python library path differences between Windows and Unix
|
||||
filters.push((
|
||||
r"[\\/]lib[\\/]python\d+\.\d+[\\/]".to_string(),
|
||||
"/[PYTHON-LIB]/".to_string(),
|
||||
));
|
||||
filters.push((r"[\\/]Lib[\\/]".to_string(), "/[PYTHON-LIB]/".to_string()));
|
||||
|
||||
for (version, executable) in &python_versions {
|
||||
// Add filtering for the interpreter path
|
||||
filters.extend(
|
||||
|
|
@ -680,18 +706,6 @@ impl TestContext {
|
|||
"Activate with: source $1/[BIN]/activate".to_string(),
|
||||
));
|
||||
|
||||
// Account for [`Simplified::user_display`] which is relative to the command working directory
|
||||
if let Some(site_packages) = site_packages {
|
||||
filters.push((
|
||||
Self::path_pattern(
|
||||
site_packages
|
||||
.strip_prefix(&canonical_temp_dir)
|
||||
.expect("The test site-packages directory is always in the tempdir"),
|
||||
),
|
||||
"[SITE_PACKAGES]/".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Filter non-deterministic temporary directory names
|
||||
// Note we apply this _after_ all the full paths to avoid breaking their matching
|
||||
filters.push((r"(\\|\/)\.tmp.*(\\|\/)".to_string(), "/[TMP]/".to_string()));
|
||||
|
|
@ -727,6 +741,11 @@ impl TestContext {
|
|||
r"environments-v(\d+)[\\/](\w+)-[a-z0-9]+".to_string(),
|
||||
"environments-v$1/$2-[HASH]".to_string(),
|
||||
));
|
||||
// Filter archive hashes
|
||||
filters.push((
|
||||
r"archive-v(\d+)[\\/][A-Za-z0-9\-\_]+".to_string(),
|
||||
"archive-v$1/[HASH]".to_string(),
|
||||
));
|
||||
|
||||
Self {
|
||||
root: ChildPath::new(root.path()),
|
||||
|
|
@ -748,7 +767,7 @@ impl TestContext {
|
|||
|
||||
/// Create a uv command for testing.
|
||||
pub fn command(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
}
|
||||
|
|
@ -826,6 +845,20 @@ impl TestContext {
|
|||
.env_remove(EnvVars::UV_TOOL_BIN_DIR)
|
||||
.env_remove(EnvVars::XDG_CONFIG_HOME)
|
||||
.env_remove(EnvVars::XDG_DATA_HOME)
|
||||
// I believe the intent of all tests is that they are run outside the
|
||||
// context of an existing git repository. And when they aren't, state
|
||||
// from the parent git repository can bleed into the behavior of `uv
|
||||
// init` in a way that makes it difficult to test consistently. By
|
||||
// setting GIT_CEILING_DIRECTORIES, we specifically prevent git from
|
||||
// climbing up past the root of our test directory to look for any
|
||||
// other git repos.
|
||||
//
|
||||
// If one wants to write a test specifically targeting uv within a
|
||||
// pre-existing git repository, then the test should make the parent
|
||||
// git repo explicitly. The GIT_CEILING_DIRECTORIES here shouldn't
|
||||
// impact it, since it only prevents git from discovering repositories
|
||||
// at or above the root.
|
||||
.env(EnvVars::GIT_CEILING_DIRECTORIES, self.root.path())
|
||||
.current_dir(self.temp_dir.path());
|
||||
|
||||
for (key, value) in &self.extra_env {
|
||||
|
|
@ -844,7 +877,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `pip compile` command for testing.
|
||||
pub fn pip_compile(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("pip").arg("compile");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -852,14 +885,14 @@ impl TestContext {
|
|||
|
||||
/// Create a `pip compile` command for testing.
|
||||
pub fn pip_sync(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("pip").arg("sync");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
}
|
||||
|
||||
pub fn pip_show(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("pip").arg("show");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -867,7 +900,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `pip freeze` command with options shared across scenarios.
|
||||
pub fn pip_freeze(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("pip").arg("freeze");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -875,14 +908,14 @@ impl TestContext {
|
|||
|
||||
/// Create a `pip check` command with options shared across scenarios.
|
||||
pub fn pip_check(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("pip").arg("check");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
}
|
||||
|
||||
pub fn pip_list(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("pip").arg("list");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -890,7 +923,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv venv` command
|
||||
pub fn venv(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("venv");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -898,7 +931,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `pip install` command with options shared across scenarios.
|
||||
pub fn pip_install(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("pip").arg("install");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -906,7 +939,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `pip uninstall` command with options shared across scenarios.
|
||||
pub fn pip_uninstall(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("pip").arg("uninstall");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -914,7 +947,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `pip tree` command for testing.
|
||||
pub fn pip_tree(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("pip").arg("tree");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -923,7 +956,7 @@ impl TestContext {
|
|||
/// Create a `uv help` command with options shared across scenarios.
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn help(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("help");
|
||||
command.env_remove(EnvVars::UV_CACHE_DIR);
|
||||
command
|
||||
|
|
@ -932,7 +965,7 @@ impl TestContext {
|
|||
/// Create a `uv init` command with options shared across scenarios and
|
||||
/// isolated from any git repository that may exist in a parent directory.
|
||||
pub fn init(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("init");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -940,7 +973,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv sync` command with options shared across scenarios.
|
||||
pub fn sync(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("sync");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -948,7 +981,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv lock` command with options shared across scenarios.
|
||||
pub fn lock(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("lock");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -956,7 +989,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv export` command with options shared across scenarios.
|
||||
pub fn export(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("export");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -964,43 +997,44 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv build` command with options shared across scenarios.
|
||||
pub fn build(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("build");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
}
|
||||
|
||||
pub fn version(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("version");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
}
|
||||
|
||||
pub fn self_version(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("self").arg("version");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
}
|
||||
|
||||
pub fn self_update(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("self").arg("update");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
}
|
||||
|
||||
/// Create a `uv publish` command with options shared across scenarios.
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn publish(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("publish");
|
||||
command
|
||||
}
|
||||
|
||||
/// Create a `uv python find` command with options shared across scenarios.
|
||||
pub fn python_find(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command
|
||||
.arg("python")
|
||||
.arg("find")
|
||||
|
|
@ -1012,7 +1046,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv python list` command with options shared across scenarios.
|
||||
pub fn python_list(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command
|
||||
.arg("python")
|
||||
.arg("list")
|
||||
|
|
@ -1023,7 +1057,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv python install` command with options shared across scenarios.
|
||||
pub fn python_install(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
self.add_shared_options(&mut command, true);
|
||||
command.arg("python").arg("install");
|
||||
command
|
||||
|
|
@ -1031,7 +1065,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv python uninstall` command with options shared across scenarios.
|
||||
pub fn python_uninstall(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
self.add_shared_options(&mut command, true);
|
||||
command.arg("python").arg("uninstall");
|
||||
command
|
||||
|
|
@ -1039,7 +1073,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv python upgrade` command with options shared across scenarios.
|
||||
pub fn python_upgrade(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
self.add_shared_options(&mut command, true);
|
||||
command.arg("python").arg("upgrade");
|
||||
command
|
||||
|
|
@ -1047,7 +1081,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv python pin` command with options shared across scenarios.
|
||||
pub fn python_pin(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("python").arg("pin");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -1055,7 +1089,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv python dir` command with options shared across scenarios.
|
||||
pub fn python_dir(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("python").arg("dir");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -1063,7 +1097,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv run` command with options shared across scenarios.
|
||||
pub fn run(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("run").env(EnvVars::UV_SHOW_RESOLUTION, "1");
|
||||
self.add_shared_options(&mut command, true);
|
||||
command
|
||||
|
|
@ -1071,7 +1105,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv tool run` command with options shared across scenarios.
|
||||
pub fn tool_run(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command
|
||||
.arg("tool")
|
||||
.arg("run")
|
||||
|
|
@ -1082,7 +1116,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv upgrade run` command with options shared across scenarios.
|
||||
pub fn tool_upgrade(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("tool").arg("upgrade");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1090,7 +1124,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv tool install` command with options shared across scenarios.
|
||||
pub fn tool_install(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("tool").arg("install");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1098,7 +1132,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv tool list` command with options shared across scenarios.
|
||||
pub fn tool_list(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("tool").arg("list");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1106,7 +1140,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv tool dir` command with options shared across scenarios.
|
||||
pub fn tool_dir(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("tool").arg("dir");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1114,7 +1148,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv tool uninstall` command with options shared across scenarios.
|
||||
pub fn tool_uninstall(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("tool").arg("uninstall");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1122,7 +1156,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv add` command for the given requirements.
|
||||
pub fn add(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("add");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1130,7 +1164,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv remove` command for the given requirements.
|
||||
pub fn remove(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("remove");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1138,7 +1172,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv tree` command with options shared across scenarios.
|
||||
pub fn tree(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("tree");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1146,7 +1180,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv cache clean` command.
|
||||
pub fn clean(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("cache").arg("clean");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1154,7 +1188,7 @@ impl TestContext {
|
|||
|
||||
/// Create a `uv cache prune` command.
|
||||
pub fn prune(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("cache").arg("prune");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1164,7 +1198,7 @@ impl TestContext {
|
|||
///
|
||||
/// Note that this command is hidden and only invoking it through a build frontend is supported.
|
||||
pub fn build_backend(&self) -> Command {
|
||||
let mut command = self.new_command();
|
||||
let mut command = Self::new_command();
|
||||
command.arg("build-backend");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
|
|
@ -1182,13 +1216,29 @@ impl TestContext {
|
|||
}
|
||||
|
||||
pub fn python_command(&self) -> Command {
|
||||
let mut command = self.new_command_with(&self.interpreter());
|
||||
let mut interpreter = self.interpreter();
|
||||
|
||||
// If there's not a virtual environment, use the first Python interpreter in the context
|
||||
if !interpreter.exists() {
|
||||
interpreter.clone_from(
|
||||
&self
|
||||
.python_versions
|
||||
.first()
|
||||
.expect("At least one Python version is required")
|
||||
.1,
|
||||
);
|
||||
}
|
||||
|
||||
let mut command = Self::new_command_with(&interpreter);
|
||||
command
|
||||
// Our tests change files in <1s, so we must disable CPython bytecode caching or we'll get stale files
|
||||
// https://github.com/python/cpython/issues/75953
|
||||
.arg("-B")
|
||||
// Python on windows
|
||||
.env(EnvVars::PYTHONUTF8, "1");
|
||||
|
||||
self.add_shared_env(&mut command, false);
|
||||
|
||||
command
|
||||
}
|
||||
|
||||
|
|
@ -1385,29 +1435,14 @@ impl TestContext {
|
|||
|
||||
/// Creates a new `Command` that is intended to be suitable for use in
|
||||
/// all tests.
|
||||
fn new_command(&self) -> Command {
|
||||
self.new_command_with(&get_bin())
|
||||
fn new_command() -> Command {
|
||||
Self::new_command_with(&get_bin())
|
||||
}
|
||||
|
||||
/// Creates a new `Command` that is intended to be suitable for use in
|
||||
/// all tests, but with the given binary.
|
||||
fn new_command_with(&self, bin: &Path) -> Command {
|
||||
let mut command = Command::new(bin);
|
||||
// I believe the intent of all tests is that they are run outside the
|
||||
// context of an existing git repository. And when they aren't, state
|
||||
// from the parent git repository can bleed into the behavior of `uv
|
||||
// init` in a way that makes it difficult to test consistently. By
|
||||
// setting GIT_CEILING_DIRECTORIES, we specifically prevent git from
|
||||
// climbing up past the root of our test directory to look for any
|
||||
// other git repos.
|
||||
//
|
||||
// If one wants to write a test specifically targeting uv within a
|
||||
// pre-existing git repository, then the test should make the parent
|
||||
// git repo explicitly. The GIT_CEILING_DIRECTORIES here shouldn't
|
||||
// impact it, since it only prevents git from discovering repositories
|
||||
// at or above the root.
|
||||
command.env(EnvVars::GIT_CEILING_DIRECTORIES, self.root.path());
|
||||
command
|
||||
fn new_command_with(bin: &Path) -> Command {
|
||||
Command::new(bin)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@ mod python_find;
|
|||
#[cfg(feature = "python")]
|
||||
mod python_list;
|
||||
|
||||
#[cfg(all(feature = "python", feature = "pypi"))]
|
||||
mod python_module;
|
||||
|
||||
#[cfg(feature = "python-managed")]
|
||||
mod python_install;
|
||||
|
||||
|
|
|
|||
|
|
@ -153,14 +153,14 @@ fn missing_record() -> Result<()> {
|
|||
fs_err::remove_file(dist_info.join("RECORD"))?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_uninstall()
|
||||
.arg("MarkupSafe"), @r###"
|
||||
.arg("MarkupSafe"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Cannot uninstall package; `RECORD` file not found at: [SITE_PACKAGES]/MarkupSafe-2.1.3.dist-info/RECORD
|
||||
"###
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,309 @@
|
|||
use assert_cmd::assert::OutputAssertExt;
|
||||
use assert_fs::prelude::{FileWriteStr, PathChild};
|
||||
use indoc::{formatdoc, indoc};
|
||||
|
||||
use uv_fs::Simplified;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
use crate::common::{TestContext, site_packages_path, uv_snapshot};
|
||||
|
||||
/// Filter the user scheme, which differs between Windows and Unix.
|
||||
fn user_scheme_bin_filter() -> (String, String) {
|
||||
if cfg!(windows) {
|
||||
(
|
||||
r"\[USER_CONFIG_DIR\][\\/]Python[\\/]Python\d+".to_string(),
|
||||
"[USER_SCHEME]".to_string(),
|
||||
)
|
||||
} else {
|
||||
(r"\[HOME\]/\.local".to_string(), "[USER_SCHEME]".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_uv_bin_venv() {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_python_names()
|
||||
.with_filtered_virtualenv_bin()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_filter(user_scheme_bin_filter());
|
||||
|
||||
// Install in a virtual environment
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(context.workspace_root.join("scripts/packages/fake-uv")), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
|
||||
"
|
||||
);
|
||||
|
||||
// We should find the binary in the virtual environment
|
||||
uv_snapshot!(context.filters(), context.python_command()
|
||||
.arg("-c")
|
||||
.arg("import uv; print(uv.find_uv_bin())"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[VENV]/[BIN]/uv
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_uv_bin_target() {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_python_names()
|
||||
.with_filtered_virtualenv_bin()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_filter(user_scheme_bin_filter())
|
||||
// Target installs always use "bin" on all platforms. On Windows,
|
||||
// with_filtered_virtualenv_bin only filters "Scripts", not "bin"
|
||||
.with_filter((r"[\\/]bin[\\/]".to_string(), "/[BIN]/".to_string()));
|
||||
|
||||
// Install in a target directory
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(context.workspace_root.join("scripts/packages/fake-uv"))
|
||||
.arg("--target")
|
||||
.arg("target"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
|
||||
"
|
||||
);
|
||||
|
||||
// We should find the binary in the target directory
|
||||
uv_snapshot!(context.filters(), context.python_command()
|
||||
.arg("-c")
|
||||
.arg("import uv; print(uv.find_uv_bin())")
|
||||
.env(EnvVars::PYTHONPATH, context.temp_dir.child("target").path()), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/target/[BIN]/uv
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_uv_bin_prefix() {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_python_names()
|
||||
.with_filtered_virtualenv_bin()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_filter(user_scheme_bin_filter());
|
||||
|
||||
// Install in a prefix directory
|
||||
let prefix = context.temp_dir.child("prefix");
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(context.workspace_root.join("scripts/packages/fake-uv"))
|
||||
.arg("--prefix")
|
||||
.arg(prefix.path()), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
|
||||
"
|
||||
);
|
||||
|
||||
// We should find the binary in the prefix directory
|
||||
uv_snapshot!(context.filters(), context.python_command()
|
||||
.arg("-c")
|
||||
.arg("import uv; print(uv.find_uv_bin())")
|
||||
.env(
|
||||
EnvVars::PYTHONPATH,
|
||||
site_packages_path(&context.temp_dir.join("prefix"), "python3.12"),
|
||||
), @r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 1, in <module>
|
||||
File "[TEMP_DIR]/prefix/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 36, in find_uv_bin
|
||||
raise FileNotFoundError(path)
|
||||
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_uv_bin_base_prefix() {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_python_names()
|
||||
.with_filtered_virtualenv_bin()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_filter(user_scheme_bin_filter());
|
||||
|
||||
// Test base prefix fallback by mutating sys.base_prefix
|
||||
// First, create a "base" environment with fake-uv installed
|
||||
let base_venv = context.temp_dir.child("base-venv");
|
||||
context.venv().arg(base_venv.path()).assert().success();
|
||||
|
||||
// Install fake-uv in the "base" venv
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--python")
|
||||
.arg(base_venv.path())
|
||||
.arg(context.workspace_root.join("scripts/packages/fake-uv")), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: base-venv
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
|
||||
"
|
||||
);
|
||||
|
||||
context.venv().assert().success();
|
||||
|
||||
// Mutate `base_prefix` to simulate lookup in a system Python installation
|
||||
uv_snapshot!(context.filters(), context.python_command()
|
||||
.arg("-c")
|
||||
.arg(format!(r#"import sys, uv; sys.base_prefix = "{}"; print(uv.find_uv_bin())"#, base_venv.path().portable_display()))
|
||||
.env(EnvVars::PYTHONPATH, site_packages_path(base_venv.path(), "python3.12")), @r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 1, in <module>
|
||||
File "[TEMP_DIR]/base-venv/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 36, in find_uv_bin
|
||||
raise FileNotFoundError(path)
|
||||
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_uv_bin_in_ephemeral_environment() -> anyhow::Result<()> {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_python_names()
|
||||
.with_filtered_virtualenv_bin()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_filter(user_scheme_bin_filter());
|
||||
|
||||
// Create a minimal pyproject.toml
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! { r#"
|
||||
[project]
|
||||
name = "test-project"
|
||||
version = "1.0.0"
|
||||
requires-python = ">=3.8"
|
||||
dependencies = []
|
||||
"#
|
||||
})?;
|
||||
|
||||
// We should find the binary in an ephemeral `--with` environment
|
||||
uv_snapshot!(context.filters(), context.run()
|
||||
.arg("--with")
|
||||
.arg(context.workspace_root.join("scripts/packages/fake-uv"))
|
||||
.arg("python")
|
||||
.arg("-c")
|
||||
.arg("import uv; print(uv.find_uv_bin())"), @r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 1, in <module>
|
||||
File "[CACHE_DIR]/archive-v0/[HASH]/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 36, in find_uv_bin
|
||||
raise FileNotFoundError(path)
|
||||
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
|
||||
"#
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_uv_bin_in_parent_of_ephemeral_environment() -> anyhow::Result<()> {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_python_names()
|
||||
.with_filtered_virtualenv_bin()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_filter(user_scheme_bin_filter());
|
||||
|
||||
// Add the fake-uv package as a dependency
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(&formatdoc! { r#"
|
||||
[project]
|
||||
name = "test-project"
|
||||
version = "1.0.0"
|
||||
requires-python = ">=3.8"
|
||||
dependencies = ["uv"]
|
||||
|
||||
[tool.uv.sources]
|
||||
uv = {{ path = "{}" }}
|
||||
"#,
|
||||
context.workspace_root.join("scripts/packages/fake-uv").portable_display()
|
||||
})?;
|
||||
|
||||
// When running in an ephemeral environment, we should find the binary in the project
|
||||
// environment
|
||||
uv_snapshot!(context.filters(), context.run()
|
||||
.arg("--with")
|
||||
.arg("anyio")
|
||||
.arg("python")
|
||||
.arg("-c")
|
||||
.arg("import uv; print(uv.find_uv_bin())"),
|
||||
@r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
|
||||
Resolved 3 packages in [TIME]
|
||||
Prepared 3 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ idna==3.6
|
||||
+ sniffio==1.3.1
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 1, in <module>
|
||||
File "[SITE_PACKAGES]/uv/_find_uv.py", line 36, in find_uv_bin
|
||||
raise FileNotFoundError(path)
|
||||
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
|
||||
"#
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
This fake uv package symlinks the Python module of uv in-tree and has a fake `uv` binary, allowing
|
||||
testing of the Python module behaviors. Consumers can replace the `uv` binary with a debug binary or
|
||||
similar if they need it to actually work.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[project]
|
||||
name = "uv"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.8"
|
||||
|
||||
[tool.uv.build-backend.data]
|
||||
scripts = "scripts"
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.8.0,<0.9"]
|
||||
build-backend = "uv_build"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
echo "This is a fake uv binary"
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
This is a fake uv binary
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../../python/
|
||||
Loading…
Reference in New Issue