mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Add test cases for uv tool install
(#4509)
Adds test cases for functionality in #4492. Includes #4520 which was needed to pass CI.
This commit is contained in:
parent
fe13ea39f0
commit
dc408146ac
3 changed files with 443 additions and 1 deletions
|
@ -10,7 +10,9 @@ use tracing::debug;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::Connectivity;
|
use uv_client::Connectivity;
|
||||||
use uv_configuration::{Concurrency, PreviewMode};
|
use uv_configuration::{Concurrency, PreviewMode};
|
||||||
use uv_fs::{replace_symlink, Simplified};
|
#[cfg(unix)]
|
||||||
|
use uv_fs::replace_symlink;
|
||||||
|
use uv_fs::Simplified;
|
||||||
use uv_installer::SitePackages;
|
use uv_installer::SitePackages;
|
||||||
use uv_requirements::RequirementsSource;
|
use uv_requirements::RequirementsSource;
|
||||||
use uv_tool::{entrypoint_paths, find_executable_directory, InstalledTools, Tool};
|
use uv_tool::{entrypoint_paths, find_executable_directory, InstalledTools, Tool};
|
||||||
|
@ -122,7 +124,10 @@ pub(crate) async fn install(
|
||||||
for (name, path) in entrypoints {
|
for (name, path) in entrypoints {
|
||||||
let target = executable_directory.join(path.file_name().unwrap());
|
let target = executable_directory.join(path.file_name().unwrap());
|
||||||
debug!("Installing {name} to {}", target.user_display());
|
debug!("Installing {name} to {}", target.user_display());
|
||||||
|
#[cfg(unix)]
|
||||||
replace_symlink(&path, &target).context("Failed to install entrypoint")?;
|
replace_symlink(&path, &target).context("Failed to install entrypoint")?;
|
||||||
|
#[cfg(windows)]
|
||||||
|
fs_err::copy(&path, &target).context("Failed to install entrypoint")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Adding `{name}` to {}", path.user_display());
|
debug!("Adding `{name}` to {}", path.user_display());
|
||||||
|
|
|
@ -362,6 +362,26 @@ impl TestContext {
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a `uv tool install` command with options shared across scenarios.
|
||||||
|
pub fn tool_install(&self) -> std::process::Command {
|
||||||
|
let mut command = self.tool_install_without_exclude_newer();
|
||||||
|
command.arg("--exclude-newer").arg(EXCLUDE_NEWER);
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `uv tool install` command with no `--exclude-newer` option.
|
||||||
|
///
|
||||||
|
/// One should avoid using this in tests to the extent possible because
|
||||||
|
/// it can result in tests failing when the index state changes. Therefore,
|
||||||
|
/// if you use this, there should be some other kind of mitigation in place.
|
||||||
|
/// For example, pinning package versions.
|
||||||
|
pub fn tool_install_without_exclude_newer(&self) -> std::process::Command {
|
||||||
|
let mut command = std::process::Command::new(get_bin());
|
||||||
|
command.arg("tool").arg("install");
|
||||||
|
self.add_shared_args(&mut command);
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a `uv add` command for the given requirements.
|
/// Create a `uv add` command for the given requirements.
|
||||||
pub fn add(&self, reqs: &[&str]) -> Command {
|
pub fn add(&self, reqs: &[&str]) -> Command {
|
||||||
let mut command = Command::new(get_bin());
|
let mut command = Command::new(get_bin());
|
||||||
|
@ -641,6 +661,7 @@ pub fn python_toolchains_for_versions(
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum WindowsFilters {
|
pub enum WindowsFilters {
|
||||||
|
CachedPlatform,
|
||||||
Platform,
|
Platform,
|
||||||
Universal,
|
Universal,
|
||||||
}
|
}
|
||||||
|
@ -721,6 +742,10 @@ pub fn run_and_format<T: AsRef<str>>(
|
||||||
WindowsFilters::Platform => {
|
WindowsFilters::Platform => {
|
||||||
["Resolved", "Prepared", "Installed", "Uninstalled"].iter()
|
["Resolved", "Prepared", "Installed", "Uninstalled"].iter()
|
||||||
}
|
}
|
||||||
|
// When cached, "Prepared" should not change.
|
||||||
|
WindowsFilters::CachedPlatform => {
|
||||||
|
["Resolved", "Installed", "Uninstalled"].iter()
|
||||||
|
}
|
||||||
WindowsFilters::Universal => {
|
WindowsFilters::Universal => {
|
||||||
["Prepared", "Installed", "Uninstalled"].iter()
|
["Prepared", "Installed", "Uninstalled"].iter()
|
||||||
}
|
}
|
||||||
|
@ -814,6 +839,12 @@ macro_rules! uv_snapshot {
|
||||||
::insta::assert_snapshot!(snapshot, @$snapshot);
|
::insta::assert_snapshot!(snapshot, @$snapshot);
|
||||||
output
|
output
|
||||||
}};
|
}};
|
||||||
|
($filters:expr, cached_windows_filters=true, $spawnable:expr, @$snapshot:literal) => {{
|
||||||
|
// Take a reference for backwards compatibility with the vec-expecting insta filters.
|
||||||
|
let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, function_name!(), Some($crate::common::WindowsFilters::CachedPlatform));
|
||||||
|
::insta::assert_snapshot!(snapshot, @$snapshot);
|
||||||
|
output
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://stackoverflow.com/a/31749071/3549270>
|
/// <https://stackoverflow.com/a/31749071/3549270>
|
||||||
|
|
406
crates/uv/tests/tool_install.rs
Normal file
406
crates/uv/tests/tool_install.rs
Normal file
|
@ -0,0 +1,406 @@
|
||||||
|
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use assert_fs::{
|
||||||
|
assert::PathAssert,
|
||||||
|
fixture::{FileTouch, PathChild},
|
||||||
|
};
|
||||||
|
use common::{uv_snapshot, TestContext};
|
||||||
|
use insta::assert_snapshot;
|
||||||
|
use predicates::prelude::predicate;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
/// Test installing a tool with `uv tool install`
|
||||||
|
#[test]
|
||||||
|
fn tool_install() {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
// Install `black`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Prepared 6 packages in [TIME]
|
||||||
|
Installed 6 packages in [TIME]
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
||||||
|
tool_dir
|
||||||
|
.child("tools.toml")
|
||||||
|
.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
assert!(executable.exists());
|
||||||
|
|
||||||
|
// On Windows, we can't snapshot an executable file.
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// Should run black in the virtual environment
|
||||||
|
assert_snapshot!(fs_err::read_to_string(executable).unwrap(), @r###"
|
||||||
|
#![TEMP_DIR]/tools/black/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from black import patched_main
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
|
||||||
|
sys.exit(patched_main())
|
||||||
|
"###);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have a tool entry
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||||
|
[tools]
|
||||||
|
black = { requirements = ["black"] }
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), Command::new("black").arg("--version").env("PATH", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
black, 24.3.0 (compiled: yes)
|
||||||
|
Python (CPython) 3.12.[X]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Install another tool
|
||||||
|
uv_snapshot!(context.filters(), cached_windows_filters=true, context.tool_install()
|
||||||
|
.arg("flask")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
Prepared 6 packages in [TIME]
|
||||||
|
Installed 7 packages in [TIME]
|
||||||
|
+ blinker==1.7.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ flask==3.0.2
|
||||||
|
+ itsdangerous==2.1.2
|
||||||
|
+ jinja2==3.1.3
|
||||||
|
+ markupsafe==2.1.5
|
||||||
|
+ werkzeug==3.0.1
|
||||||
|
"###);
|
||||||
|
|
||||||
|
tool_dir.child("flask").assert(predicate::path::is_dir());
|
||||||
|
assert!(bin_dir
|
||||||
|
.child(format!("flask{}", std::env::consts::EXE_SUFFIX))
|
||||||
|
.exists());
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
assert_snapshot!(fs_err::read_to_string(bin_dir.join("flask")).unwrap(), @r###"
|
||||||
|
#![TEMP_DIR]/tools/flask/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from flask.cli import main
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
|
"###);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), Command::new("flask").arg("--version").env("PATH", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
Python 3.12.[X]
|
||||||
|
Flask 3.0.2
|
||||||
|
Werkzeug 3.0.1
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have an additional tool entry
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||||
|
[tools]
|
||||||
|
black = { requirements = ["black"] }
|
||||||
|
flask = { requirements = ["flask"] }
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test installing a tool twice with `uv tool install`
|
||||||
|
#[test]
|
||||||
|
fn tool_install_twice() {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
// Install `black`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Prepared 6 packages in [TIME]
|
||||||
|
Installed 6 packages in [TIME]
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
||||||
|
tool_dir
|
||||||
|
.child("tools.toml")
|
||||||
|
.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
assert!(executable.exists());
|
||||||
|
|
||||||
|
// On Windows, we can't snapshot an executable file.
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// Should run black in the virtual environment
|
||||||
|
assert_snapshot!(fs_err::read_to_string(executable).unwrap(), @r###"
|
||||||
|
#![TEMP_DIR]/tools/black/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from black import patched_main
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
|
||||||
|
sys.exit(patched_main())
|
||||||
|
"###);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have a tool entry
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||||
|
[tools]
|
||||||
|
black = { requirements = ["black"] }
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Install `black` again
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
Tool `black` is already installed.
|
||||||
|
"###);
|
||||||
|
|
||||||
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
||||||
|
bin_dir
|
||||||
|
.child(format!("black{}", std::env::consts::EXE_SUFFIX))
|
||||||
|
.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should not have an additional tool entry
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||||
|
[tools]
|
||||||
|
black = { requirements = ["black"] }
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test installing a tool when its entry pint already exists
|
||||||
|
#[test]
|
||||||
|
fn tool_install_entry_point_exists() {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
executable.touch().unwrap();
|
||||||
|
|
||||||
|
// Install `black`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Prepared 6 packages in [TIME]
|
||||||
|
Installed 6 packages in [TIME]
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// TODO(zanieb): We happily overwrite entry points by default right now
|
||||||
|
// https://github.com/astral-sh/uv/pull/4501 should resolve this
|
||||||
|
|
||||||
|
// We should not create a virtual environment
|
||||||
|
// assert!(tool_dir.child("black").exists());
|
||||||
|
|
||||||
|
// // We should not write a tools entry
|
||||||
|
// assert!(!tool_dir.join("tools.toml").exists());
|
||||||
|
|
||||||
|
// insta::with_settings!({
|
||||||
|
// filters => context.filters(),
|
||||||
|
// }, {
|
||||||
|
// // Nor should we change the `black` entry point that exists
|
||||||
|
// assert_snapshot!(fs_err::read_to_string(bin_dir.join("black")).unwrap(), @"");
|
||||||
|
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test `uv tool install` when the bin directory is inferred from `$HOME`
|
||||||
|
///
|
||||||
|
/// Only tested on Linux right now because it's not clear how to change the %USERPROFILE% on Windows
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn tool_install_home() {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
|
||||||
|
// Install `black`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install().arg("black").env("UV_TOOL_DIR", tool_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Prepared 6 packages in [TIME]
|
||||||
|
Installed 6 packages in [TIME]
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
context
|
||||||
|
.home_dir
|
||||||
|
.child(format!(".local/bin/black{}", std::env::consts::EXE_SUFFIX))
|
||||||
|
.assert(predicate::path::exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test `uv tool install` when the bin directory is inferred from `$XDG_DATA_HOME`
|
||||||
|
#[test]
|
||||||
|
fn tool_install_xdg_data_home() {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let data_home = context.temp_dir.child("data/home");
|
||||||
|
|
||||||
|
// Install `black`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_DATA_HOME", data_home.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Prepared 6 packages in [TIME]
|
||||||
|
Installed 6 packages in [TIME]
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
context
|
||||||
|
.temp_dir
|
||||||
|
.child(format!("data/bin/black{}", std::env::consts::EXE_SUFFIX))
|
||||||
|
.assert(predicate::path::exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test `uv tool install` when the bin directory is set by `$XDG_BIN_HOME`
|
||||||
|
#[test]
|
||||||
|
fn tool_install_xdg_bin_home() {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
// Install `black`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Prepared 6 packages in [TIME]
|
||||||
|
Installed 6 packages in [TIME]
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
bin_dir
|
||||||
|
.child(format!("black{}", std::env::consts::EXE_SUFFIX))
|
||||||
|
.assert(predicate::path::exists());
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue