uv/crates/uv/tests/tool_run.rs
Charlie Marsh a917cdba51
Fix colors in uv tool run suggestion (#5267)
## Summary

More consistent with other commands.

Before:

![Screenshot 2024-07-21 at 2 22
18 PM](https://github.com/user-attachments/assets/9f6dfc7d-3c54-47a4-9b9f-bdf3794ae06d)

Now: all cyan, and green command, following the style guide.
2024-07-21 23:43:27 +00:00

621 lines
19 KiB
Rust

#![cfg(all(feature = "python", feature = "pypi"))]
use assert_cmd::prelude::*;
use assert_fs::prelude::*;
use common::{uv_snapshot, TestContext};
mod common;
#[test]
fn tool_run_args() {
let context = TestContext::new("3.12").with_filtered_counts();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");
// We treat arguments before the command as uv arguments
uv_snapshot!(context.filters(), context.tool_run()
.arg("--version")
.arg("pytest")
.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 -----
uv [VERSION] ([COMMIT] DATE)
----- stderr -----
"###);
// We don't treat arguments after the command as uv arguments
uv_snapshot!(context.filters(), context.tool_run()
.arg("pytest")
.arg("--version")
.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 -----
pytest 8.1.1
----- stderr -----
warning: `uv tool run` 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
+ packaging==24.0
+ pluggy==1.4.0
+ pytest==8.1.1
"###);
// Can use `--` to separate uv arguments from the command arguments.
uv_snapshot!(context.filters(), context.tool_run()
.arg("--")
.arg("pytest")
.arg("--version")
.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 -----
pytest 8.1.1
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved [N] packages in [TIME]
"###);
}
#[test]
fn tool_run_at_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");
uv_snapshot!(context.filters(), context.tool_run()
.arg("pytest@8.0.0")
.arg("--version")
.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 -----
pytest 8.0.0
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved 4 packages in [TIME]
Prepared 4 packages in [TIME]
Installed 4 packages in [TIME]
+ iniconfig==2.0.0
+ packaging==24.0
+ pluggy==1.4.0
+ pytest==8.0.0
"###);
// Empty versions are just treated as package and command names
uv_snapshot!(context.filters(), context.tool_run()
.arg("pytest@")
.arg("--version")
.env("UV_TOOL_DIR", tool_dir.as_os_str())
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
error: Failed to parse: `pytest@`
Caused by: Expected URL
pytest@
^
"###);
// Invalid versions are just treated as package and command names
uv_snapshot!(context.filters(), context.tool_run()
.arg("pytest@invalid")
.arg("--version")
.env("UV_TOOL_DIR", tool_dir.as_os_str())
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
error: Distribution not found at: file://[TEMP_DIR]/invalid
"###);
let filters = context
.filters()
.into_iter()
.chain([(
// The error message is different on Windows
"Caused by: program not found",
"Caused by: No such file or directory (os error 2)",
)])
.collect::<Vec<_>>();
// When `--from` is used, `@` is not treated as a version request
uv_snapshot!(filters, context.tool_run()
.arg("--from")
.arg("pytest")
.arg("pytest@8.0.0")
.arg("--version")
.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 -----
The executable `pytest@8.0.0` was not found.
The following executables are provided by `pytest`:
- py.test
- pytest
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved 4 packages in [TIME]
Prepared 1 package in [TIME]
Installed 4 packages in [TIME]
+ iniconfig==2.0.0
+ packaging==24.0
+ pluggy==1.4.0
+ pytest==8.1.1
warning: An executable named `pytest@8.0.0` is not provided by package `pytest`.
"###);
}
#[test]
fn tool_run_from_version() {
let context = TestContext::new("3.12");
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");
uv_snapshot!(context.filters(), context.tool_run()
.arg("--from")
.arg("pytest==8.0.0")
.arg("pytest")
.arg("--version")
.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 -----
pytest 8.0.0
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved 4 packages in [TIME]
Prepared 4 packages in [TIME]
Installed 4 packages in [TIME]
+ iniconfig==2.0.0
+ packaging==24.0
+ pluggy==1.4.0
+ pytest==8.0.0
"###);
}
#[test]
fn tool_run_suggest_valid_commands() {
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_run()
.arg("--from")
.arg("black")
.arg("orange")
.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 -----
The executable `orange` was not found.
The following executables are provided by `black`:
- black
- blackd
----- stderr -----
warning: `uv tool run` 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
warning: An executable named `orange` is not provided by package `black`.
"###);
uv_snapshot!(context.filters(), context.tool_run()
.arg("fastapi-cli")
.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 -----
The executable `fastapi-cli` was not found.
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved 3 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ fastapi-cli==0.0.1
+ importlib-metadata==1.7.0
+ zipp==3.18.1
warning: An executable named `fastapi-cli` is not provided by package `fastapi-cli`.
"###);
}
#[test]
fn tool_run_warn_executable_not_in_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");
let mut filters = context.filters();
filters.push(("\\+ uvloop(.+)\n ", ""));
// Strip off the `fastapi` command output.
filters.push(("(?s)fastapi` instead.*", "fastapi` instead."));
uv_snapshot!(filters, context.tool_run()
.arg("--from")
.arg("fastapi")
.arg("fastapi")
.env("UV_EXCLUDE_NEWER", "2024-05-04T00:00:00Z") // TODO: Remove this once EXCLUDE_NEWER is bumped past 2024-05-04
// (FastAPI 0.111 is only available from this date onwards)
.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 run` is experimental and may change without warning
Resolved 35 packages in [TIME]
Prepared 35 packages in [TIME]
Installed 35 packages in [TIME]
+ annotated-types==0.6.0
+ anyio==4.3.0
+ certifi==2024.2.2
+ click==8.1.7
+ dnspython==2.6.1
+ email-validator==2.1.1
+ fastapi==0.111.0
+ fastapi-cli==0.0.2
+ h11==0.14.0
+ httpcore==1.0.5
+ httptools==0.6.1
+ httpx==0.27.0
+ idna==3.7
+ jinja2==3.1.3
+ markdown-it-py==3.0.0
+ markupsafe==2.1.5
+ mdurl==0.1.2
+ orjson==3.10.3
+ pydantic==2.7.1
+ pydantic-core==2.18.2
+ pygments==2.17.2
+ python-dotenv==1.0.1
+ python-multipart==0.0.9
+ pyyaml==6.0.1
+ rich==13.7.1
+ shellingham==1.5.4
+ sniffio==1.3.1
+ starlette==0.37.2
+ typer==0.12.3
+ typing-extensions==4.11.0
+ ujson==5.9.0
+ uvicorn==0.29.0
+ watchfiles==0.21.0
+ websockets==12.0
warning: An executable named `fastapi` is not provided by package `fastapi` but is available via the dependency `fastapi-cli`. Consider using `uv tool run --from fastapi-cli fastapi` instead.
"###);
}
#[test]
fn tool_run_from_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` at a specific version.
context
.tool_install()
.arg("black==24.1.0")
.env("UV_TOOL_DIR", tool_dir.as_os_str())
.env("XDG_BIN_HOME", bin_dir.as_os_str())
.assert()
.success();
// Verify that `tool run black` uses the already-installed version.
uv_snapshot!(context.filters(), context.tool_run()
.arg("black")
.arg("--version")
.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 -----
black, 24.1.0 (compiled: yes)
Python (CPython) 3.12.[X]
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
"###);
// Verify that `--isolated` uses an isolated environment.
uv_snapshot!(context.filters(), context.tool_run()
.arg("--isolated")
.arg("black")
.arg("--version")
.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 -----
black, 24.3.0 (compiled: yes)
Python (CPython) 3.12.[X]
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved 6 packages in [TIME]
Prepared 1 package 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
"###);
// Verify that `tool run black` at a different version installs the new version.
uv_snapshot!(context.filters(), context.tool_run()
.arg("black@24.1.1")
.arg("--version")
.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 -----
black, 24.1.1 (compiled: yes)
Python (CPython) 3.12.[X]
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved 6 packages in [TIME]
Prepared 1 package in [TIME]
Installed 6 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
"###);
// Verify that `--with` installs a new version.
// TODO(charlie): This could (in theory) layer the `--with` requirements on top of the existing
// environment.
uv_snapshot!(context.filters(), context.tool_run()
.arg("--with")
.arg("iniconfig")
.arg("black")
.arg("--version")
.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 -----
black, 24.3.0 (compiled: yes)
Python (CPython) 3.12.[X]
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved 7 packages in [TIME]
Prepared 1 package in [TIME]
Installed 7 packages in [TIME]
+ black==24.3.0
+ click==8.1.7
+ iniconfig==2.0.0
+ mypy-extensions==1.0.0
+ packaging==24.0
+ pathspec==0.12.1
+ platformdirs==4.2.0
"###);
// Verify that `tool run black` at a different version (via `--from`) installs the new version.
uv_snapshot!(context.filters(), context.tool_run()
.arg("--from")
.arg("black==24.2.0")
.arg("black")
.arg("--version")
.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 -----
black, 24.2.0 (compiled: yes)
Python (CPython) 3.12.[X]
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved 6 packages in [TIME]
Prepared 1 package 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
"###);
}
#[test]
fn tool_run_cache() {
let context = TestContext::new_with_versions(&["3.11", "3.12"]).with_filtered_counts();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");
// Verify that `tool run black` installs the latest version.
uv_snapshot!(context.filters(), context.tool_run()
.arg("-p")
.arg("3.12")
.arg("black")
.arg("--version")
.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 -----
black, 24.3.0 (compiled: yes)
Python (CPython) 3.12.[X]
----- stderr -----
warning: `uv tool run` 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
"###);
// Verify that `tool run black` uses the cached version.
uv_snapshot!(context.filters(), context.tool_run()
.arg("-p")
.arg("3.12")
.arg("black")
.arg("--version")
.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 -----
black, 24.3.0 (compiled: yes)
Python (CPython) 3.12.[X]
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved [N] packages in [TIME]
"###);
// Verify that varying the interpreter leads to a fresh environment.
uv_snapshot!(context.filters(), context.tool_run()
.arg("-p")
.arg("3.11")
.arg("black")
.arg("--version")
.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 -----
black, 24.3.0 (compiled: yes)
Python (CPython) 3.11.[X]
----- stderr -----
warning: `uv tool run` 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
"###);
// But that re-invoking with the previous interpreter retains the cached version.
uv_snapshot!(context.filters(), context.tool_run()
.arg("-p")
.arg("3.12")
.arg("black")
.arg("--version")
.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 -----
black, 24.3.0 (compiled: yes)
Python (CPython) 3.12.[X]
----- stderr -----
warning: `uv tool run` is experimental and may change without warning
Resolved [N] packages in [TIME]
"###);
// Verify that `--with` leads to a fresh environment.
uv_snapshot!(context.filters(), context.tool_run()
.arg("-p")
.arg("3.12")
.arg("--with")
.arg("iniconfig")
.arg("black")
.arg("--version")
.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 -----
black, 24.3.0 (compiled: yes)
Python (CPython) 3.12.[X]
----- stderr -----
warning: `uv tool run` 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
+ iniconfig==2.0.0
+ mypy-extensions==1.0.0
+ packaging==24.0
+ pathspec==0.12.1
+ platformdirs==4.2.0
"###);
}
#[test]
fn tool_run_url() {
let context = TestContext::new("3.12").with_filtered_counts();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");
uv_snapshot!(context.filters(), context.tool_run()
.arg("--from")
.arg("flask @ https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl")
.arg("flask")
.arg("--version")
.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 -----
Python 3.12.[X]
Flask 3.0.3
Werkzeug 3.0.1
----- stderr -----
warning: `uv tool run` 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.3 (from https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl)
+ itsdangerous==2.1.2
+ jinja2==3.1.3
+ markupsafe==2.1.5
+ werkzeug==3.0.1
"###);
}