uv/crates/puffin/tests/pip_install.rs

963 lines
28 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![cfg(all(feature = "python", feature = "pypi"))]
use std::process::Command;
use anyhow::Result;
use assert_cmd::prelude::*;
use assert_fs::prelude::*;
use indoc::indoc;
use url::Url;
use common::{puffin_snapshot, TestContext, EXCLUDE_NEWER, INSTA_FILTERS};
use crate::common::get_bin;
mod common;
/// Create a `pip install` command with options shared across scenarios.
fn command(context: &TestContext) -> Command {
let mut command = Command::new(get_bin());
command
.arg("pip")
.arg("install")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--exclude-newer")
.arg(EXCLUDE_NEWER)
.env("VIRTUAL_ENV", context.venv.as_os_str())
.current_dir(&context.temp_dir);
command
}
#[test]
fn missing_requirements_txt() {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
puffin_snapshot!(command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: failed to open file `requirements.txt`
Caused by: No such file or directory (os error 2)
"###
);
requirements_txt.assert(predicates::path::missing());
}
#[test]
fn no_solution() {
let context = TestContext::new("3.12");
puffin_snapshot!(command(&context)
.arg("flask>=3.0.0")
.arg("WerkZeug<1.0.0")
.arg("--strict"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only flask<=3.0.0 is available and flask==3.0.0 depends
on werkzeug>=3.0.0, we can conclude that flask>=3.0.0 depends on
werkzeug>=3.0.0.
And because you require flask>=3.0.0 and you require werkzeug<1.0.0, we
can conclude that the requirements are unsatisfiable.
"###);
}
/// Install a package from the command line into a virtual environment.
#[test]
fn install_package() {
let context = TestContext::new("3.12");
// Install Flask.
puffin_snapshot!(command(&context)
.arg("Flask")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ blinker==1.7.0
+ click==8.1.7
+ flask==3.0.0
+ itsdangerous==2.1.2
+ jinja2==3.1.2
+ markupsafe==2.1.3
+ werkzeug==3.0.1
"###
);
context.assert_command("import flask").success();
}
/// Install a package from a `requirements.txt` into a virtual environment.
#[test]
fn install_requirements_txt() -> Result<()> {
let context = TestContext::new("3.12");
// Install Flask.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("Flask")?;
puffin_snapshot!(command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ blinker==1.7.0
+ click==8.1.7
+ flask==3.0.0
+ itsdangerous==2.1.2
+ jinja2==3.1.2
+ markupsafe==2.1.3
+ werkzeug==3.0.1
"###
);
context.assert_command("import flask").success();
// Install Jinja2 (which should already be installed, but shouldn't remove other packages).
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("Jinja2")?;
puffin_snapshot!(command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Audited 1 package in [TIME]
"###
);
context.assert_command("import flask").success();
Ok(())
}
/// Respect installed versions when resolving.
#[test]
fn respect_installed() -> Result<()> {
let context = TestContext::new("3.12");
// Install Flask.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;
requirements_txt.write_str("Flask==2.3.2")?;
puffin_snapshot!(command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ blinker==1.7.0
+ click==8.1.7
+ flask==2.3.2
+ itsdangerous==2.1.2
+ jinja2==3.1.2
+ markupsafe==2.1.3
+ werkzeug==3.0.1
"###
);
context.assert_command("import flask").success();
// Re-install Flask. We should respect the existing version.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;
requirements_txt.write_str("Flask")?;
puffin_snapshot!(command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Audited 1 package in [TIME]
"###
);
context.assert_command("import flask").success();
// Install a newer version of Flask. We should upgrade it.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;
requirements_txt.write_str("Flask==2.3.3")?;
let filters = if cfg!(windows) {
// Remove the colorama count on windows
INSTA_FILTERS
.iter()
.copied()
.chain([("Resolved 8 packages", "Resolved 7 packages")])
.collect()
} else {
INSTA_FILTERS.to_vec()
};
puffin_snapshot!(filters, command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
- flask==2.3.2
+ flask==2.3.3
"###
);
// Re-install Flask. We should upgrade it.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;
requirements_txt.write_str("Flask")?;
puffin_snapshot!(filters, command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--reinstall-package")
.arg("Flask")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
- flask==2.3.3
+ flask==3.0.0
"###
);
Ok(())
}
/// Like `pip`, we (unfortunately) allow incompatible environments.
#[test]
fn allow_incompatibilities() -> Result<()> {
let context = TestContext::new("3.12");
// Install Flask, which relies on `Werkzeug>=3.0.0`.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;
requirements_txt.write_str("Flask")?;
puffin_snapshot!(command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ blinker==1.7.0
+ click==8.1.7
+ flask==3.0.0
+ itsdangerous==2.1.2
+ jinja2==3.1.2
+ markupsafe==2.1.3
+ werkzeug==3.0.1
"###
);
context.assert_command("import flask").success();
// Install an incompatible version of Jinja2.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;
requirements_txt.write_str("jinja2==2.11.3")?;
puffin_snapshot!(command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
- jinja2==3.1.2
+ jinja2==2.11.3
warning: The package `flask` requires `jinja2 >=3.1.2`, but `2.11.3` is installed.
"###
);
// This no longer works, since we have an incompatible version of Jinja2.
context.assert_command("import flask").failure();
Ok(())
}
#[test]
#[cfg(feature = "maturin")]
fn install_editable() -> Result<()> {
let context = TestContext::new("3.12");
let current_dir = std::env::current_dir()?;
let workspace_dir = regex::escape(
Url::from_directory_path(current_dir.join("..").join("..").canonicalize()?)
.unwrap()
.as_str(),
);
let filters = [(workspace_dir.as_str(), "file://[WORKSPACE_DIR]/")]
.into_iter()
.chain(INSTA_FILTERS.to_vec())
.collect::<Vec<_>>();
// Install the editable package.
puffin_snapshot!(filters, Command::new(get_bin())
.arg("pip")
.arg("install")
.arg("-e")
.arg("../../scripts/editable-installs/poetry_editable")
.arg("--strict")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--exclude-newer")
.arg(EXCLUDE_NEWER)
.env("VIRTUAL_ENV", context.venv.as_os_str())
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Built 1 editable in [TIME]
Resolved 2 packages in [TIME]
Downloaded 1 package in [TIME]
Installed 2 packages in [TIME]
+ numpy==1.26.2
+ poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable)
"###
);
// Install it again (no-op).
puffin_snapshot!(filters, Command::new(get_bin())
.arg("pip")
.arg("install")
.arg("-e")
.arg("../../scripts/editable-installs/poetry_editable")
.arg("--strict")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--exclude-newer")
.arg(EXCLUDE_NEWER)
.env("VIRTUAL_ENV", context.venv.as_os_str())
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Audited 1 package in [TIME]
"###
);
// Add another, non-editable dependency.
puffin_snapshot!(filters, Command::new(get_bin())
.arg("pip")
.arg("install")
.arg("-e")
.arg("../../scripts/editable-installs/poetry_editable")
.arg("black")
.arg("--strict")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--exclude-newer")
.arg(EXCLUDE_NEWER)
.env("VIRTUAL_ENV", context.venv.as_os_str())
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Built 1 editable in [TIME]
Resolved 8 packages in [TIME]
Downloaded 6 packages in [TIME]
Installed 7 packages in [TIME]
+ black==23.11.0
+ click==8.1.7
+ mypy-extensions==1.0.0
+ packaging==23.2
+ pathspec==0.11.2
+ platformdirs==4.0.0
- poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable)
+ poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable)
"###
);
Ok(())
}
#[test]
fn install_editable_and_registry() -> Result<()> {
let context = TestContext::new("3.12");
let current_dir = std::env::current_dir()?;
let workspace_dir = regex::escape(
Url::from_directory_path(current_dir.join("..").join("..").canonicalize()?)
.unwrap()
.as_str(),
);
let filters: Vec<_> = [(workspace_dir.as_str(), "file://[WORKSPACE_DIR]/")]
.into_iter()
.chain(INSTA_FILTERS.to_vec())
.collect();
// Install the registry-based version of Black.
puffin_snapshot!(filters, Command::new(get_bin())
.arg("pip")
.arg("install")
.arg("black")
.arg("--strict")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--exclude-newer")
.arg(EXCLUDE_NEWER)
.env("VIRTUAL_ENV", context.venv.as_os_str())
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Downloaded 6 packages in [TIME]
Installed 6 packages in [TIME]
+ black==23.11.0
+ click==8.1.7
+ mypy-extensions==1.0.0
+ packaging==23.2
+ pathspec==0.11.2
+ platformdirs==4.0.0
"###
);
// Install the editable version of Black. This should remove the registry-based version.
puffin_snapshot!(filters, Command::new(get_bin())
.arg("pip")
.arg("install")
.arg("-e")
.arg("../../scripts/editable-installs/black_editable")
.arg("--strict")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--exclude-newer")
.arg(EXCLUDE_NEWER)
.env("VIRTUAL_ENV", context.venv.as_os_str())
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Built 1 editable in [TIME]
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
- black==23.11.0
+ black==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/black_editable)
"###
);
// Re-install the registry-based version of Black. This should be a no-op, since we have a
// version of Black installed (the editable version) that satisfies the requirements.
puffin_snapshot!(filters, Command::new(get_bin())
.arg("pip")
.arg("install")
.arg("black")
.arg("--strict")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--exclude-newer")
.arg(EXCLUDE_NEWER)
.env("VIRTUAL_ENV", context.venv.as_os_str())
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Audited 1 package in [TIME]
"###
);
let filters2: Vec<_> = filters
.into_iter()
.chain([
// Remove colorama
("Resolved 7 packages", "Resolved 6 packages"),
])
.collect();
// Re-install Black at a specific version. This should replace the editable version.
puffin_snapshot!(filters2, Command::new(get_bin())
.arg("pip")
.arg("install")
.arg("black==23.10.0")
.arg("--strict")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.arg("--exclude-newer")
.arg(EXCLUDE_NEWER)
.env("VIRTUAL_ENV", context.venv.as_os_str())
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
- black==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/black_editable)
+ black==23.10.0
"###
);
Ok(())
}
/// Install a source distribution that uses the `flit` build system, along with `flit`
/// at the top-level, along with `--reinstall` to force a re-download after resolution, to ensure
/// that the `flit` install and the source distribution build don't conflict.
#[test]
fn reinstall_build_system() -> Result<()> {
let context = TestContext::new("3.12");
// Install devpi.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! {r"
flit_core<4.0.0
flask @ https://files.pythonhosted.org/packages/d8/09/c1a7354d3925a3c6c8cfdebf4245bae67d633ffda1ba415add06ffc839c5/flask-3.0.0.tar.gz
"
})?;
puffin_snapshot!(command(&context)
.arg("--reinstall")
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 8 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 8 packages in [TIME]
+ blinker==1.7.0
+ click==8.1.7
+ flask==3.0.0 (from https://files.pythonhosted.org/packages/d8/09/c1a7354d3925a3c6c8cfdebf4245bae67d633ffda1ba415add06ffc839c5/flask-3.0.0.tar.gz)
+ flit-core==3.9.0
+ itsdangerous==2.1.2
+ jinja2==3.1.2
+ markupsafe==2.1.3
+ werkzeug==3.0.1
"###
);
Ok(())
}
/// Install a package without using the remote index
#[test]
fn install_no_index() {
let context = TestContext::new("3.12");
puffin_snapshot!(command(&context)
.arg("Flask")
.arg("--no-index"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because flask was not found in the provided package locations and you
require flask, we can conclude that the requirements are unsatisfiable.
hint: Packages were unavailable because index lookups were disabled
and no additional package locations were provided (try: `--find-links
<uri>`)
"###
);
context.assert_command("import flask").failure();
}
/// Install a package without using the remote index
/// Covers a case where the user requests a version which should be included in the error
#[test]
fn install_no_index_version() {
let context = TestContext::new("3.12");
puffin_snapshot!(command(&context)
.arg("Flask==3.0.0")
.arg("--no-index"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because flask==3.0.0 was not found in the provided package locations
and you require flask==3.0.0, we can conclude that the requirements
are unsatisfiable.
hint: Packages were unavailable because index lookups were disabled
and no additional package locations were provided (try: `--find-links
<uri>`)
"###
);
context.assert_command("import flask").failure();
}
/// Install a package without using pre-built wheels.
#[test]
fn install_no_binary() {
let context = TestContext::new("3.12");
let mut command = command(&context);
command.arg("Flask").arg("--no-binary").arg("--strict");
if cfg!(all(windows, debug_assertions)) {
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
// default windows stack of 1MB
command.env("PUFFIN_STACK_SIZE", (2 * 1024 * 1024).to_string());
}
puffin_snapshot!(command, @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ blinker==1.7.0
+ click==8.1.7
+ flask==3.0.0
+ itsdangerous==2.1.2
+ jinja2==3.1.2
+ markupsafe==2.1.3
+ werkzeug==3.0.1
"###
);
context.assert_command("import flask").success();
}
/// Install a package without using pre-built wheels for a subset of packages.
#[test]
fn install_no_binary_subset() {
let context = TestContext::new("3.12");
puffin_snapshot!(command(&context)
.arg("Flask")
.arg("--no-binary-package")
.arg("click")
.arg("--no-binary-package")
.arg("flask")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ blinker==1.7.0
+ click==8.1.7
+ flask==3.0.0
+ itsdangerous==2.1.2
+ jinja2==3.1.2
+ markupsafe==2.1.3
+ werkzeug==3.0.1
"###
);
context.assert_command("import flask").success();
}
/// Install a package without using pre-built wheels.
#[test]
fn reinstall_no_binary() {
let context = TestContext::new("3.12");
// The first installation should use a pre-built wheel
let mut command = command(&context);
command.arg("Flask").arg("--strict");
if cfg!(all(windows, debug_assertions)) {
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
// default windows stack of 1MB
command.env("PUFFIN_STACK_SIZE", (2 * 1024 * 1024).to_string());
}
puffin_snapshot!(command, @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ blinker==1.7.0
+ click==8.1.7
+ flask==3.0.0
+ itsdangerous==2.1.2
+ jinja2==3.1.2
+ markupsafe==2.1.3
+ werkzeug==3.0.1
"###
);
context.assert_command("import flask").success();
// Running installation again with `--no-binary` should be a no-op
// The first installation should use a pre-built wheel
let mut command = crate::command(&context);
command.arg("Flask").arg("--no-binary").arg("--strict");
if cfg!(all(windows, debug_assertions)) {
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
// default windows stack of 1MB
command.env("PUFFIN_STACK_SIZE", (2 * 1024 * 1024).to_string());
}
puffin_snapshot!(command, @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Audited 1 package in [TIME]
"###
);
context.assert_command("import flask").success();
// With `--reinstall`, `--no-binary` should have an affect
let filters = if cfg!(windows) {
// Remove the colorama count on windows
INSTA_FILTERS
.iter()
.copied()
.chain([("Resolved 8 packages", "Resolved 7 packages")])
.collect()
} else {
INSTA_FILTERS.to_vec()
};
let mut command = crate::command(&context);
command
.arg("Flask")
.arg("--no-binary")
.arg("--reinstall-package")
.arg("Flask")
.arg("--strict");
if cfg!(all(windows, debug_assertions)) {
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
// default windows stack of 1MB
command.env("PUFFIN_STACK_SIZE", (2 * 1024 * 1024).to_string());
}
puffin_snapshot!(filters, command, @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Installed 1 package in [TIME]
- flask==3.0.0
+ flask==3.0.0
"###
);
context.assert_command("import flask").success();
}
/// Install a package into a virtual environment, and ensuring that the executable permissions
/// are retained.
///
/// This test uses the default link semantics. (On macOS, this is `clone`.)
#[test]
fn install_executable() {
let context = TestContext::new("3.12");
puffin_snapshot!(command(&context)
.arg("pylint==3.0.0"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ astroid==3.0.1
+ dill==0.3.7
+ isort==5.12.0
+ mccabe==0.7.0
+ platformdirs==4.0.0
+ pylint==3.0.0
+ tomlkit==0.12.3
"###
);
// Verify that `pylint` is executable.
let executable = context
.venv
.join(if cfg!(windows) { "Scripts" } else { "bin" })
.join(format!("pylint{}", std::env::consts::EXE_SUFFIX));
Command::new(executable).arg("--version").assert().success();
}
/// Install a package into a virtual environment using copy semantics, and ensure that the
/// executable permissions are retained.
#[test]
fn install_executable_copy() {
let context = TestContext::new("3.12");
puffin_snapshot!(command(&context)
.arg("pylint==3.0.0")
.arg("--link-mode")
.arg("copy"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ astroid==3.0.1
+ dill==0.3.7
+ isort==5.12.0
+ mccabe==0.7.0
+ platformdirs==4.0.0
+ pylint==3.0.0
+ tomlkit==0.12.3
"###
);
// Verify that `pylint` is executable.
let executable = context
.venv
.join(if cfg!(windows) { "Scripts" } else { "bin" })
.join(format!("pylint{}", std::env::consts::EXE_SUFFIX));
Command::new(executable).arg("--version").assert().success();
}
/// Install a package into a virtual environment using hardlink semantics, and ensure that the
/// executable permissions are retained.
#[test]
fn install_executable_hardlink() {
let context = TestContext::new("3.12");
puffin_snapshot!(command(&context)
.arg("pylint==3.0.0")
.arg("--link-mode")
.arg("hardlink"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 7 packages in [TIME]
Installed 7 packages in [TIME]
+ astroid==3.0.1
+ dill==0.3.7
+ isort==5.12.0
+ mccabe==0.7.0
+ platformdirs==4.0.0
+ pylint==3.0.0
+ tomlkit==0.12.3
"###
);
// Verify that `pylint` is executable.
let executable = context
.venv
.join(if cfg!(windows) { "Scripts" } else { "bin" })
.join(format!("pylint{}", std::env::consts::EXE_SUFFIX));
Command::new(executable).arg("--version").assert().success();
}
/// Install a package from the command line into a virtual environment, ignoring its dependencies.
#[test]
fn no_deps() {
let context = TestContext::new("3.12");
// Install Flask.
puffin_snapshot!(command(&context)
.arg("Flask")
.arg("--no-deps")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
+ flask==3.0.0
warning: The package `flask` requires `werkzeug >=3.0.0`, but it's not installed.
warning: The package `flask` requires `jinja2 >=3.1.2`, but it's not installed.
warning: The package `flask` requires `itsdangerous >=2.1.2`, but it's not installed.
warning: The package `flask` requires `click >=8.1.3`, but it's not installed.
warning: The package `flask` requires `blinker >=1.6.2`, but it's not installed.
"###
);
context.assert_command("import flask").failure();
}