mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00

## Summary `uv tool install X` fails for tools already installed and exits with code 1. Allow already installed tools to exit with code 0.
1549 lines
50 KiB
Rust
1549 lines
50 KiB
Rust
#![cfg(all(feature = "python", feature = "pypi"))]
|
|
|
|
use std::process::Command;
|
|
|
|
use assert_fs::{
|
|
assert::PathAssert,
|
|
fixture::{FileTouch, PathChild},
|
|
};
|
|
use insta::assert_snapshot;
|
|
use predicates::prelude::predicate;
|
|
|
|
use common::{uv_snapshot, TestContext};
|
|
|
|
mod common;
|
|
|
|
/// Test installing a tool with `uv tool install`
|
|
#[test]
|
|
fn tool_install() {
|
|
let context = TestContext::new("3.12")
|
|
.with_filtered_counts()
|
|
.with_filtered_exe_suffix();
|
|
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())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] 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
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
tool_dir
|
|
.child("black")
|
|
.child("uv-receipt.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 receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
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(), context.tool_install()
|
|
.arg("flask")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] 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
|
|
Installed 1 executable: flask
|
|
"###);
|
|
|
|
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 a new tool receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["flask"]
|
|
entrypoints = [
|
|
{ name = "flask", install-path = "[TEMP_DIR]/bin/flask" },
|
|
]
|
|
"###);
|
|
});
|
|
}
|
|
|
|
/// Test installing a tool at a version
|
|
#[test]
|
|
fn tool_install_version() {
|
|
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
|
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==24.2.0")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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.2.0
|
|
+ click==8.1.7
|
|
+ mypy-extensions==1.0.0
|
|
+ packaging==24.0
|
|
+ pathspec==0.12.1
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
tool_dir
|
|
.child("black")
|
|
.child("uv-receipt.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 receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black==24.2.0"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
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.2.0 (compiled: yes)
|
|
Python (CPython) 3.12.[X]
|
|
|
|
----- stderr -----
|
|
"###);
|
|
}
|
|
|
|
/// Test installing a tool with `uv tool install --from`
|
|
#[test]
|
|
fn tool_install_from() {
|
|
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
|
let tool_dir = context.temp_dir.child("tools");
|
|
let bin_dir = context.temp_dir.child("bin");
|
|
|
|
// Install `black` using `--from` to specify the version
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--from")
|
|
.arg("black==24.2.0")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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.2.0
|
|
+ click==8.1.7
|
|
+ mypy-extensions==1.0.0
|
|
+ packaging==24.0
|
|
+ pathspec==0.12.1
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
// Attempt to install `black` using `--from` with a different package name
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--from")
|
|
.arg("flask==24.2.0")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
error: Package name (`flask`) provided with `--from` does not match install request (`black`)
|
|
"###);
|
|
|
|
// Attempt to install `black` using `--from` with a different version
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black==24.2.0")
|
|
.arg("--from")
|
|
.arg("black==24.3.0")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
error: Package requirement (`black==24.3.0`) provided with `--from` conflicts with install request (`black==24.2.0`)
|
|
"###);
|
|
}
|
|
|
|
/// Test installing and reinstalling an already installed tool
|
|
#[test]
|
|
fn tool_install_already_installed() {
|
|
let context = TestContext::new("3.12")
|
|
.with_filtered_counts()
|
|
.with_filtered_exe_suffix();
|
|
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())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] 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
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
tool_dir
|
|
.child("black")
|
|
.child("uv-receipt.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 receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
// 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())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
`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 receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
// Install `black` again with the `--reinstall` flag
|
|
// We should recreate the entire environment and reinstall the entry points
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--reinstall")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Uninstalled [N] packages in [TIME]
|
|
Installed [N] packages in [TIME]
|
|
- black==24.3.0
|
|
+ black==24.3.0
|
|
- click==8.1.7
|
|
+ click==8.1.7
|
|
- mypy-extensions==1.0.0
|
|
+ mypy-extensions==1.0.0
|
|
- packaging==24.0
|
|
+ packaging==24.0
|
|
- pathspec==0.12.1
|
|
+ pathspec==0.12.1
|
|
- platformdirs==4.2.0
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
// Install `black` again with `--reinstall-package` for `black`
|
|
// We should reinstall `black` in the environment and reinstall the entry points
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--reinstall-package")
|
|
.arg("black")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Uninstalled [N] packages in [TIME]
|
|
Installed [N] packages in [TIME]
|
|
- black==24.3.0
|
|
+ black==24.3.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
// Install `black` again with `--reinstall-package` for a dependency
|
|
// We should reinstall `click` in the environment but not reinstall `black`
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--reinstall-package")
|
|
.arg("click")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Uninstalled [N] packages in [TIME]
|
|
Installed [N] packages in [TIME]
|
|
- click==8.1.7
|
|
+ click==8.1.7
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
}
|
|
|
|
/// Test installing a tool when its entry point already exists
|
|
#[test]
|
|
fn tool_install_entry_point_exists() {
|
|
let context = TestContext::new("3.12")
|
|
.with_filtered_counts()
|
|
.with_filtered_exe_suffix();
|
|
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();
|
|
|
|
// Attempt to 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())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
Resolved [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] 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
|
|
error: Executable already exists: black (use `--force` to overwrite)
|
|
"###);
|
|
|
|
// We should delete the virtual environment
|
|
assert!(!tool_dir.child("black").exists());
|
|
|
|
// We should not write a tools entry
|
|
assert!(!tool_dir.join("black").join("uv-receipt.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(&executable).unwrap(), @"");
|
|
|
|
});
|
|
|
|
// Attempt to install `black` with the `--reinstall` flag
|
|
// Should have no effect
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--reinstall")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
Resolved [N] packages in [TIME]
|
|
Installed [N] 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
|
|
error: Executable already exists: black (use `--force` to overwrite)
|
|
"###);
|
|
|
|
// 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(&executable).unwrap(), @"");
|
|
|
|
});
|
|
|
|
// Test error message when multiple entry points exist
|
|
bin_dir
|
|
.child(format!("blackd{}", std::env::consts::EXE_SUFFIX))
|
|
.touch()
|
|
.unwrap();
|
|
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())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
Resolved [N] packages in [TIME]
|
|
Installed [N] 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
|
|
error: Executables already exist: black, blackd (use `--force` to overwrite)
|
|
"###);
|
|
|
|
// Install `black` with `--force`
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--force")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Installed [N] 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
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
|
|
// Re-install `black` with `--force`
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--force")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
|
|
// Re-install `black` without `--force`
|
|
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())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
`black` is already installed
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
|
|
// Re-install `black` with `--reinstall`
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--reinstall")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Uninstalled [N] packages in [TIME]
|
|
Installed [N] packages in [TIME]
|
|
- black==24.3.0
|
|
+ black==24.3.0
|
|
- click==8.1.7
|
|
+ click==8.1.7
|
|
- mypy-extensions==1.0.0
|
|
+ mypy-extensions==1.0.0
|
|
- packaging==24.0
|
|
+ packaging==24.0
|
|
- pathspec==0.12.1
|
|
+ pathspec==0.12.1
|
|
- platformdirs==4.2.0
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
// We write a tool receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
// 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/python3
|
|
# -*- 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 receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
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 -----
|
|
"###);
|
|
}
|
|
|
|
/// 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").with_filtered_exe_suffix();
|
|
let tool_dir = context.temp_dir.child("tools");
|
|
|
|
// Install `black`
|
|
let mut cmd = context.tool_install();
|
|
cmd.arg("black")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env(
|
|
"XDG_DATA_HOME",
|
|
context.home_dir.child(".local").child("share").as_os_str(),
|
|
)
|
|
.env(
|
|
"PATH",
|
|
context.home_dir.child(".local").child("bin").as_os_str(),
|
|
);
|
|
uv_snapshot!(context.filters(), cmd, @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
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
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").with_filtered_exe_suffix();
|
|
let tool_dir = context.temp_dir.child("tools");
|
|
let data_home = context.temp_dir.child("data/home");
|
|
let bin_dir = context.temp_dir.child("data/bin");
|
|
|
|
// 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())
|
|
.env("PATH", 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
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
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").with_filtered_exe_suffix();
|
|
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())
|
|
.env("PATH", 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
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
bin_dir
|
|
.child(format!("black{}", std::env::consts::EXE_SUFFIX))
|
|
.assert(predicate::path::exists());
|
|
}
|
|
|
|
/// Test installing a tool that lacks entrypoints
|
|
#[test]
|
|
fn tool_install_no_entrypoints() {
|
|
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
|
let tool_dir = context.temp_dir.child("tools");
|
|
let bin_dir = context.temp_dir.child("bin");
|
|
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("iniconfig")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
Resolved 1 package in [TIME]
|
|
Prepared 1 package in [TIME]
|
|
Installed 1 package in [TIME]
|
|
+ iniconfig==2.0.0
|
|
error: No executables found for `iniconfig`
|
|
"###);
|
|
}
|
|
|
|
/// Test installing a tool with a bare URL requirement.
|
|
#[test]
|
|
fn tool_install_unnamed_package() {
|
|
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
|
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("https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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.4.2 (from https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl)
|
|
+ click==8.1.7
|
|
+ mypy-extensions==1.0.0
|
|
+ packaging==24.0
|
|
+ pathspec==0.12.1
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
tool_dir
|
|
.child("black")
|
|
.child("uv-receipt.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 receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black @ https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
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.4.2 (compiled: no)
|
|
Python (CPython) 3.12.[X]
|
|
|
|
----- stderr -----
|
|
"###);
|
|
}
|
|
|
|
/// Test installing a tool with a bare URL requirement using `--from`, where the URL and the package
|
|
/// name conflict.
|
|
#[test]
|
|
fn tool_install_unnamed_conflict() {
|
|
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
|
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")
|
|
.arg("--from")
|
|
.arg("https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
error: Package name (`iniconfig`) provided with `--from` does not match install request (`black`)
|
|
"###);
|
|
}
|
|
|
|
/// Test installing a tool with a bare URL requirement using `--from`.
|
|
#[test]
|
|
fn tool_install_unnamed_from() {
|
|
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
|
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")
|
|
.arg("--from")
|
|
.arg("https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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.4.2 (from https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl)
|
|
+ click==8.1.7
|
|
+ mypy-extensions==1.0.0
|
|
+ packaging==24.0
|
|
+ pathspec==0.12.1
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
tool_dir
|
|
.child("black")
|
|
.child("uv-receipt.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 receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black @ https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
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.4.2 (compiled: no)
|
|
Python (CPython) 3.12.[X]
|
|
|
|
----- stderr -----
|
|
"###);
|
|
}
|
|
|
|
/// Test installing a tool with a bare URL requirement using `--with`.
|
|
#[test]
|
|
fn tool_install_unnamed_with() {
|
|
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
|
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")
|
|
.arg("--with")
|
|
.arg("https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 7 packages in [TIME]
|
|
Installed 7 packages in [TIME]
|
|
+ black==24.3.0
|
|
+ click==8.1.7
|
|
+ iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
|
|
+ mypy-extensions==1.0.0
|
|
+ packaging==24.0
|
|
+ pathspec==0.12.1
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
|
tool_dir
|
|
.child("black")
|
|
.child("uv-receipt.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 receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = [
|
|
"black",
|
|
"iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl",
|
|
]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
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 -----
|
|
"###);
|
|
}
|
|
|
|
/// Test upgrading an already installed tool.
|
|
#[test]
|
|
fn tool_install_upgrade() {
|
|
let context = TestContext::new("3.12")
|
|
.with_filtered_counts()
|
|
.with_filtered_exe_suffix();
|
|
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==24.1.1")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] packages in [TIME]
|
|
+ black==24.1.1
|
|
+ click==8.1.7
|
|
+ mypy-extensions==1.0.0
|
|
+ packaging==24.0
|
|
+ pathspec==0.12.1
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
// We should have a tool receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black==24.1.1"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
// Install without the constraint. It should be replaced, but the package shouldn't be installed
|
|
// since it's already satisfied in the environment.
|
|
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())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
// We should have a tool receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
// Install with a `with`. It should be added to the environment.
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--with")
|
|
.arg("iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] packages in [TIME]
|
|
+ iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
// We should have a tool receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = [
|
|
"black",
|
|
"iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl",
|
|
]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
|
|
// Install with `--upgrade`. `black` should be reinstalled with a more recent version, and
|
|
// `iniconfig` should be removed.
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black")
|
|
.arg("--upgrade")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Uninstalled [N] packages in [TIME]
|
|
Installed [N] packages in [TIME]
|
|
- black==24.1.1
|
|
+ black==24.3.0
|
|
- iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
// We should have a tool receipt
|
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
|
[tool]
|
|
requirements = ["black"]
|
|
entrypoints = [
|
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
|
]
|
|
"###);
|
|
});
|
|
}
|
|
|
|
/// Test reinstalling tools with varying `--python` requests.
|
|
#[test]
|
|
fn tool_install_python_request() {
|
|
let context = TestContext::new_with_versions(&["3.11", "3.12"])
|
|
.with_filtered_counts()
|
|
.with_filtered_exe_suffix();
|
|
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("-p")
|
|
.arg("3.12")
|
|
.arg("black")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] 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
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
// Install with Python 3.12 (compatible).
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("-p")
|
|
.arg("3.12")
|
|
.arg("black")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
`black` is already installed
|
|
"###);
|
|
|
|
// Install with Python 3.11 (incompatible).
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("-p")
|
|
.arg("3.11")
|
|
.arg("black")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
Existing environment for `black` does not satisfy the requested Python interpreter
|
|
Resolved [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] 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
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
}
|
|
|
|
/// Test preserving a tool environment when new but incompatible requirements are requested.
|
|
#[test]
|
|
fn tool_install_preserve_environment() {
|
|
let context = TestContext::new("3.12")
|
|
.with_filtered_counts()
|
|
.with_filtered_exe_suffix();
|
|
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==24.1.1")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", 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 [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] packages in [TIME]
|
|
+ black==24.1.1
|
|
+ click==8.1.7
|
|
+ mypy-extensions==1.0.0
|
|
+ packaging==24.0
|
|
+ pathspec==0.12.1
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
"###);
|
|
|
|
// Install `black`, but with an incompatible requirement.
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black==24.1.1")
|
|
.arg("--with")
|
|
.arg("packaging==0.0.1")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
error: Because black==24.1.1 depends on packaging>=22.0 and you require black==24.1.1, we can conclude that you require packaging>=22.0.
|
|
And because you require packaging==0.0.1, we can conclude that the requirements are unsatisfiable.
|
|
"###);
|
|
|
|
// Install `black`. The tool should already be installed, since we didn't remove the environment.
|
|
uv_snapshot!(context.filters(), context.tool_install()
|
|
.arg("black==24.1.1")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
`black==24.1.1` is already installed
|
|
"###);
|
|
}
|
|
|
|
/// Test warning when the binary directory is not on the user's PATH.
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn tool_install_warn_path() {
|
|
let context = TestContext::new("3.12")
|
|
.with_filtered_counts()
|
|
.with_filtered_exe_suffix();
|
|
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==24.1.1")
|
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
|
.env_remove("PATH"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
warning: `uv tool install` is experimental and may change without warning.
|
|
Resolved [N] packages in [TIME]
|
|
Prepared [N] packages in [TIME]
|
|
Installed [N] packages in [TIME]
|
|
+ black==24.1.1
|
|
+ click==8.1.7
|
|
+ mypy-extensions==1.0.0
|
|
+ packaging==24.0
|
|
+ pathspec==0.12.1
|
|
+ platformdirs==4.2.0
|
|
Installed 2 executables: black, blackd
|
|
warning: `[TEMP_DIR]/bin` is not on your PATH. To use installed tools, run:
|
|
export PATH="[TEMP_DIR]/bin:$PATH"
|
|
"###);
|
|
}
|