uv/crates/uv/tests/it/pip_tree.rs

1288 lines
30 KiB
Rust

#![cfg(not(windows))]
use assert_cmd::assert::OutputAssertExt;
use std::process::Command;
use assert_fs::fixture::FileTouch;
use assert_fs::fixture::FileWriteStr;
use assert_fs::fixture::PathChild;
use assert_fs::fixture::PathCreateDir;
use indoc::indoc;
use uv_static::EnvVars;
use crate::common::get_bin;
use crate::common::{TestContext, uv_snapshot};
#[test]
fn no_package() {
let context = TestContext::new("3.12");
uv_snapshot!(context.filters(), context.pip_tree(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn prune_last_in_the_subgroup() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==2.31.0").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 5 packages in [TIME]
Prepared 5 packages in [TIME]
Installed 5 packages in [TIME]
+ certifi==2024.2.2
+ charset-normalizer==3.3.2
+ idna==3.6
+ requests==2.31.0
+ urllib3==2.2.1
"###
);
context.assert_command("import requests").success();
uv_snapshot!(context.filters(), context.pip_tree().arg("--prune").arg("certifi"), @r###"
success: true
exit_code: 0
----- stdout -----
requests v2.31.0
├── charset-normalizer v3.3.2
├── idna v3.6
└── urllib3 v2.2.1
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn single_package() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==2.31.0").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 5 packages in [TIME]
Prepared 5 packages in [TIME]
Installed 5 packages in [TIME]
+ certifi==2024.2.2
+ charset-normalizer==3.3.2
+ idna==3.6
+ requests==2.31.0
+ urllib3==2.2.1
"###
);
context.assert_command("import requests").success();
uv_snapshot!(context.filters(), context.pip_tree(), @r###"
success: true
exit_code: 0
----- stdout -----
requests v2.31.0
├── certifi v2024.2.2
├── charset-normalizer v3.3.2
├── idna v3.6
└── urllib3 v2.2.1
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn nested_dependencies() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("flask").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 7 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
"###
);
uv_snapshot!(context.filters(), context.pip_tree(), @r###"
success: true
exit_code: 0
----- stdout -----
flask v3.0.2
├── blinker v1.7.0
├── click v8.1.7
├── itsdangerous v2.1.2
├── jinja2 v3.1.3
│ └── markupsafe v2.1.5
└── werkzeug v3.0.1
└── markupsafe v2.1.5
----- stderr -----
"###
);
}
/// Identical test as `invert` since `--reverse` is simply an alias for `--invert`.
#[test]
#[cfg(feature = "pypi")]
fn reverse() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("flask").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 7 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
"###
);
uv_snapshot!(context.filters(), context.pip_tree().arg("--reverse"), @r###"
success: true
exit_code: 0
----- stdout -----
blinker v1.7.0
└── flask v3.0.2
click v8.1.7
└── flask v3.0.2
itsdangerous v2.1.2
└── flask v3.0.2
markupsafe v2.1.5
├── jinja2 v3.1.3
│ └── flask v3.0.2
└── werkzeug v3.0.1
└── flask v3.0.2
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn invert() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("flask").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 7 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
"###
);
uv_snapshot!(context.filters(), context.pip_tree().arg("--invert"), @r###"
success: true
exit_code: 0
----- stdout -----
blinker v1.7.0
└── flask v3.0.2
click v8.1.7
└── flask v3.0.2
itsdangerous v2.1.2
└── flask v3.0.2
markupsafe v2.1.5
├── jinja2 v3.1.3
│ └── flask v3.0.2
└── werkzeug v3.0.1
└── flask v3.0.2
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn depth() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("flask").unwrap();
uv_snapshot!(context.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 7 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
"###
);
uv_snapshot!(context.filters(), Command::new(get_bin())
.arg("pip")
.arg("tree")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--depth")
.arg("0")
.env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str())
.env(EnvVars::UV_NO_WRAP, "1")
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
----- stdout -----
flask v3.0.2
----- stderr -----
"###
);
uv_snapshot!(context.filters(), Command::new(get_bin())
.arg("pip")
.arg("tree")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--depth")
.arg("1")
.env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str())
.env(EnvVars::UV_NO_WRAP, "1")
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
----- stdout -----
flask v3.0.2
├── blinker v1.7.0
├── click v8.1.7
├── itsdangerous v2.1.2
├── jinja2 v3.1.3
└── werkzeug v3.0.1
----- stderr -----
"###
);
uv_snapshot!(context.filters(), Command::new(get_bin())
.arg("pip")
.arg("tree")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--depth")
.arg("2")
.env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str())
.env(EnvVars::UV_NO_WRAP, "1")
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
----- stdout -----
flask v3.0.2
├── blinker v1.7.0
├── click v8.1.7
├── itsdangerous v2.1.2
├── jinja2 v3.1.3
│ └── markupsafe v2.1.5
└── werkzeug v3.0.1
└── markupsafe v2.1.5
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn prune() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("flask").unwrap();
uv_snapshot!(context.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 7 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
"###
);
uv_snapshot!(context.filters(), Command::new(get_bin())
.arg("pip")
.arg("tree")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--prune")
.arg("werkzeug")
.env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str())
.env(EnvVars::UV_NO_WRAP, "1")
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
----- stdout -----
flask v3.0.2
├── blinker v1.7.0
├── click v8.1.7
├── itsdangerous v2.1.2
└── jinja2 v3.1.3
└── markupsafe v2.1.5
----- stderr -----
"###
);
uv_snapshot!(context.filters(), Command::new(get_bin())
.arg("pip")
.arg("tree")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--prune")
.arg("werkzeug")
.arg("--prune")
.arg("jinja2")
.env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str())
.env(EnvVars::UV_NO_WRAP, "1")
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
----- stdout -----
flask v3.0.2
├── blinker v1.7.0
├── click v8.1.7
└── itsdangerous v2.1.2
markupsafe v2.1.5
----- stderr -----
"###
);
uv_snapshot!(context.filters(), Command::new(get_bin())
.arg("pip")
.arg("tree")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--prune")
.arg("werkzeug")
.env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str())
.env(EnvVars::UV_NO_WRAP, "1")
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
----- stdout -----
flask v3.0.2
├── blinker v1.7.0
├── click v8.1.7
├── itsdangerous v2.1.2
└── jinja2 v3.1.3
└── markupsafe v2.1.5
----- stderr -----
"###
);
}
/// Ensure `pip tree` behaves correctly after a package has been removed.
#[test]
#[cfg(feature = "pypi")]
fn removed_dependency() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==2.31.0").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 5 packages in [TIME]
Prepared 5 packages in [TIME]
Installed 5 packages in [TIME]
+ certifi==2024.2.2
+ charset-normalizer==3.3.2
+ idna==3.6
+ requests==2.31.0
+ urllib3==2.2.1
"###
);
uv_snapshot!(context.filters(), context
.pip_uninstall()
.arg("requests"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Uninstalled 1 package in [TIME]
- requests==2.31.0
"###
);
uv_snapshot!(context.filters(), context.pip_tree(), @r###"
success: true
exit_code: 0
----- stdout -----
certifi v2024.2.2
charset-normalizer v3.3.2
idna v3.6
urllib3 v2.2.1
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn multiple_packages() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(
r"
requests==2.31.0
click==8.1.7
",
)
.unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Prepared 6 packages in [TIME]
Installed 6 packages in [TIME]
+ certifi==2024.2.2
+ charset-normalizer==3.3.2
+ click==8.1.7
+ idna==3.6
+ requests==2.31.0
+ urllib3==2.2.1
"###
);
context.assert_command("import requests").success();
uv_snapshot!(context.filters(), context.pip_tree(), @r###"
success: true
exit_code: 0
----- stdout -----
click v8.1.7
requests v2.31.0
├── certifi v2024.2.2
├── charset-normalizer v3.3.2
├── idna v3.6
└── urllib3 v2.2.1
----- stderr -----
"###
);
}
/// Show the installed tree in the presence of a cycle.
#[test]
#[cfg(feature = "pypi")]
fn cycle() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(
r"
testtools==2.3.0
fixtures==3.0.0
",
)
.unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 10 packages in [TIME]
Prepared 10 packages in [TIME]
Installed 10 packages in [TIME]
+ argparse==1.4.0
+ extras==1.0.0
+ fixtures==3.0.0
+ linecache2==1.0.0
+ pbr==6.0.0
+ python-mimeparse==1.6.0
+ six==1.16.0
+ testtools==2.3.0
+ traceback2==1.4.0
+ unittest2==1.1.0
"###
);
uv_snapshot!(context.filters(), context.pip_tree(), @r###"
success: true
exit_code: 0
----- stdout -----
testtools v2.3.0
├── extras v1.0.0
├── fixtures v3.0.0
│ ├── pbr v6.0.0
│ ├── six v1.16.0
│ └── testtools v2.3.0 (*)
├── pbr v6.0.0
├── python-mimeparse v1.6.0
├── six v1.16.0
├── traceback2 v1.4.0
│ └── linecache2 v1.0.0
└── unittest2 v1.1.0
├── argparse v1.4.0
├── six v1.16.0
└── traceback2 v1.4.0 (*)
(*) Package tree already displayed
----- stderr -----
"###
);
}
/// Both `pendulum` and `boto3` depend on `python-dateutil`.
#[test]
#[cfg(feature = "pypi")]
fn multiple_packages_shared_descendant() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(
r"
pendulum
time-machine
",
)
.unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 5 packages in [TIME]
Prepared 5 packages in [TIME]
Installed 5 packages in [TIME]
+ pendulum==3.0.0
+ python-dateutil==2.9.0.post0
+ six==1.16.0
+ time-machine==2.14.1
+ tzdata==2024.1
"###
);
uv_snapshot!(context.filters(), context.pip_tree(), @r###"
success: true
exit_code: 0
----- stdout -----
pendulum v3.0.0
├── python-dateutil v2.9.0.post0
│ └── six v1.16.0
├── time-machine v2.14.1
│ └── python-dateutil v2.9.0.post0 (*)
└── tzdata v2024.1
(*) Package tree already displayed
----- stderr -----
"###
);
}
/// Test the interaction between `--no-dedupe` and `--invert`.
#[test]
#[cfg(feature = "pypi")]
fn no_dedupe_and_invert() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(
r"
pendulum
time-machine
",
)
.unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 5 packages in [TIME]
Prepared 5 packages in [TIME]
Installed 5 packages in [TIME]
+ pendulum==3.0.0
+ python-dateutil==2.9.0.post0
+ six==1.16.0
+ time-machine==2.14.1
+ tzdata==2024.1
"###
);
uv_snapshot!(context.filters(), context.pip_tree().arg("--no-dedupe").arg("--invert"), @r###"
success: true
exit_code: 0
----- stdout -----
six v1.16.0
└── python-dateutil v2.9.0.post0
├── pendulum v3.0.0
└── time-machine v2.14.1
└── pendulum v3.0.0
tzdata v2024.1
└── pendulum v3.0.0
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn no_dedupe() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(
r"
pendulum
time-machine
",
)
.unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 5 packages in [TIME]
Prepared 5 packages in [TIME]
Installed 5 packages in [TIME]
+ pendulum==3.0.0
+ python-dateutil==2.9.0.post0
+ six==1.16.0
+ time-machine==2.14.1
+ tzdata==2024.1
"###
);
uv_snapshot!(context.filters(), context.pip_tree()
.arg("--no-dedupe"), @r###"
success: true
exit_code: 0
----- stdout -----
pendulum v3.0.0
├── python-dateutil v2.9.0.post0
│ └── six v1.16.0
├── time-machine v2.14.1
│ └── python-dateutil v2.9.0.post0
│ └── six v1.16.0
└── tzdata v2024.1
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "git")]
fn with_editable() {
let context = TestContext::new("3.12");
// Install the editable package.
uv_snapshot!(context.filters(), context
.pip_install()
.arg("-e")
.arg(context.workspace_root.join("test/packages/hatchling_editable")), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ hatchling-editable==0.1.0 (from file://[WORKSPACE]/test/packages/hatchling_editable)
+ iniconfig==2.0.1.dev6+g9cae431 (from git+https://github.com/pytest-dev/iniconfig@9cae43103df70bac6fde7b9f35ad11a9f1be0cb4)
"###
);
let filters = context
.filters()
.into_iter()
.chain(vec![(r"\-\-\-\-\-\-+.*", "[UNDERLINE]"), (" +", " ")])
.collect::<Vec<_>>();
uv_snapshot!(filters, context.pip_tree(), @r###"
success: true
exit_code: 0
----- stdout -----
hatchling-editable v0.1.0
└── iniconfig v2.0.1.dev6+g9cae431
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn package_flag() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("flask").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 7 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
"###
);
uv_snapshot!(
context.filters(),
context.pip_tree()
.arg("--package")
.arg("werkzeug"),
@r###"
success: true
exit_code: 0
----- stdout -----
werkzeug v3.0.1
└── markupsafe v2.1.5
----- stderr -----
"###
);
uv_snapshot!(
context.filters(),
context.pip_tree()
.arg("--package")
.arg("werkzeug")
.arg("--package")
.arg("jinja2"),
@r###"
success: true
exit_code: 0
----- stdout -----
jinja2 v3.1.3
└── markupsafe v2.1.5
werkzeug v3.0.1
└── markupsafe v2.1.5
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn show_version_specifiers_simple() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==2.31.0").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 5 packages in [TIME]
Prepared 5 packages in [TIME]
Installed 5 packages in [TIME]
+ certifi==2024.2.2
+ charset-normalizer==3.3.2
+ idna==3.6
+ requests==2.31.0
+ urllib3==2.2.1
"###
);
uv_snapshot!(context.filters(), context.pip_tree().arg("--show-version-specifiers"), @r###"
success: true
exit_code: 0
----- stdout -----
requests v2.31.0
├── certifi v2024.2.2 [required: >=2017.4.17]
├── charset-normalizer v3.3.2 [required: >=2, <4]
├── idna v3.6 [required: >=2.5, <4]
└── urllib3 v2.2.1 [required: >=1.21.1, <3]
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn show_version_specifiers_with_invert() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("flask").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 7 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
"###
);
uv_snapshot!(
context.filters(),
context.pip_tree()
.arg("--show-version-specifiers")
.arg("--invert"), @r###"
success: true
exit_code: 0
----- stdout -----
blinker v1.7.0
└── flask v3.0.2 [requires: blinker >=1.6.2]
click v8.1.7
└── flask v3.0.2 [requires: click >=8.1.3]
itsdangerous v2.1.2
└── flask v3.0.2 [requires: itsdangerous >=2.1.2]
markupsafe v2.1.5
├── jinja2 v3.1.3 [requires: markupsafe >=2.0]
│ └── flask v3.0.2 [requires: jinja2 >=3.1.2]
└── werkzeug v3.0.1 [requires: markupsafe >=2.1.1]
└── flask v3.0.2 [requires: werkzeug >=3.0.0]
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn show_version_specifiers_with_package() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("flask").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 7 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
"###
);
uv_snapshot!(
context.filters(),
context.pip_tree()
.arg("--show-version-specifiers")
.arg("--package")
.arg("werkzeug"), @r###"
success: true
exit_code: 0
----- stdout -----
werkzeug v3.0.1
└── markupsafe v2.1.5 [required: >=2.1.1]
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn print_output_even_with_quite_flag() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==2.31.0").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 5 packages in [TIME]
Prepared 5 packages in [TIME]
Installed 5 packages in [TIME]
+ certifi==2024.2.2
+ charset-normalizer==3.3.2
+ idna==3.6
+ requests==2.31.0
+ urllib3==2.2.1
"###
);
context.assert_command("import requests").success();
uv_snapshot!(context.filters(), context.pip_tree().arg("--quiet"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
"###
);
}
#[test]
#[cfg(feature = "pypi")]
fn outdated() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("flask==2.0.0").unwrap();
uv_snapshot!(context
.pip_install()
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Prepared 6 packages in [TIME]
Installed 6 packages in [TIME]
+ click==8.1.7
+ flask==2.0.0
+ itsdangerous==2.1.2
+ jinja2==3.1.3
+ markupsafe==2.1.5
+ werkzeug==3.0.1
"###
);
uv_snapshot!(
context.filters(),
context.pip_tree().arg("--outdated"), @r###"
success: true
exit_code: 0
----- stdout -----
flask v2.0.0 (latest: v3.0.2)
├── click v8.1.7
├── itsdangerous v2.1.2
├── jinja2 v3.1.3
│ └── markupsafe v2.1.5
└── werkzeug v3.0.1
└── markupsafe v2.1.5
----- stderr -----
"###
);
}
/// Test that dependencies with multiple marker-specific requirements
/// are only displayed once in the tree.
#[test]
#[cfg(feature = "pypi")]
fn no_duplicate_dependencies_with_markers() {
const PY_PROJECT: &str = indoc! {r#"
[project]
name = "debug"
version = "0.1.0"
requires-python = ">=3.12.0"
dependencies = [
"sniffio>=1.0.0; python_version >= '3.11'",
"sniffio>=1.0.1; python_version >= '3.12'",
"sniffio>=1.0.2; python_version >= '3.13'",
]
[build-system]
requires = ["uv_build>=0.8.22,<10000"]
build-backend = "uv_build"
"#};
let context = TestContext::new_with_versions(&["3.12", "3.13"]).with_filtered_counts();
let project = context.temp_dir.child("debug");
project.create_dir_all().unwrap();
project.child("src/debug").create_dir_all().unwrap();
project.child("src/debug/__init__.py").touch().unwrap();
project
.child("pyproject.toml")
.write_str(PY_PROJECT)
.unwrap();
context.reset_venv();
uv_snapshot!(context.filters(), context
.pip_install()
.arg(project.path())
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
+ debug==0.1.0 (from file://[TEMP_DIR]/debug)
+ sniffio==1.3.1
"###
);
// Ensure that the dependency is only listed once, even though `debug` declares multiple
// marker-specific requirements for the same dependency.
uv_snapshot!(context.filters(), context.pip_tree(), @r###"
success: true
exit_code: 0
----- stdout -----
debug v0.1.0
└── sniffio v1.3.1
----- stderr -----
"###
);
uv_snapshot!(
context.filters(),
context.pip_tree().arg("--show-version-specifiers"),
@r###"
success: true
exit_code: 0
----- stdout -----
debug v0.1.0
└── sniffio v1.3.1 [required: >=1.0.1]
----- stderr -----
"###
);
context
.venv()
.arg("--clear")
.arg("--python")
.arg("3.13")
.assert()
.success();
uv_snapshot!(context.filters(), context
.pip_install()
.arg(project.path())
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
+ debug==0.1.0 (from file://[TEMP_DIR]/debug)
+ sniffio==1.3.1
"###
);
uv_snapshot!(
context.filters(),
context.pip_tree().arg("--show-version-specifiers"),
@r###"
success: true
exit_code: 0
----- stdout -----
debug v0.1.0
└── sniffio v1.3.1 [required: >=1.0.2]
----- stderr -----
"###
);
}