//! Tests for the `cargo install` command.

use std::env;
use std::fs::{self, OpenOptions};
use std::io::prelude::*;
use std::path::Path;
use std::path::PathBuf;
use std::thread;

use cargo_test_support::compare::assert_e2e;
use cargo_test_support::cross_compile;
use cargo_test_support::git;
use cargo_test_support::prelude::*;
use cargo_test_support::registry::{self, Package};
use cargo_test_support::str;
use cargo_test_support::{
    basic_manifest, cargo_process, project, project_in, symlink_supported, t,
};
use cargo_util::{ProcessBuilder, ProcessError};

use cargo_test_support::install::{assert_has_installed_exe, assert_has_not_installed_exe, exe};
use cargo_test_support::paths;

fn pkg(name: &str, vers: &str) {
    Package::new(name, vers)
        .file("src/lib.rs", "")
        .file(
            "src/main.rs",
            &format!("extern crate {}; fn main() {{}}", name),
        )
        .publish();
}

#[cargo_test]
fn simple() {
    pkg("foo", "0.0.1");

    cargo_process("install foo").with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[INSTALLING] foo v0.0.1
[COMPILING] foo v0.0.1
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]]).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");

    cargo_process("uninstall foo")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn install_the_same_version_twice() {
    pkg("foo", "0.0.1");

    cargo_process("install foo foo")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[INSTALLING] foo v0.0.1
[COMPILING] foo v0.0.1
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn toolchain() {
    pkg("foo", "0.0.1");

    cargo_process("install +nightly")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] invalid character `+` in package name: `+nightly`
    Use `cargo +nightly install` if you meant to use the `nightly` toolchain.

"#]])
        .run();
}

#[cargo_test]
fn url() {
    pkg("foo", "0.0.1");
    cargo_process("install https://github.com/bar/foo")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] invalid package name: `https://github.com/bar/foo`
    Use `cargo install --git https://github.com/bar/foo` if you meant to install from a git repository.

"#]])
        .run();
}

#[cargo_test]
fn simple_with_message_format() {
    pkg("foo", "0.0.1");

    cargo_process("install foo --message-format=json")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[INSTALLING] foo v0.0.1
[COMPILING] foo v0.0.1
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries

"#]])
        .with_stdout_data(
            str![[r#"
[
  {
    "executable": null,
    "features": [],
    "filenames": "{...}",
    "fresh": false,
    "manifest_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/foo-0.0.1/Cargo.toml",
    "package_id": "registry+https://github.com/rust-lang/crates.io-index#foo@0.0.1",
    "profile": "{...}",
    "reason": "compiler-artifact",
    "target": {
      "crate_types": [
        "lib"
      ],
      "doc": true,
      "doctest": true,
      "edition": "2015",
      "kind": [
        "lib"
      ],
      "name": "foo",
      "src_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/foo-0.0.1/src/lib.rs",
      "test": true
    }
  },
  {
    "executable": "[..]",
    "features": [],
    "filenames": "{...}",
    "fresh": false,
    "manifest_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/foo-0.0.1/Cargo.toml",
    "package_id": "registry+https://github.com/rust-lang/crates.io-index#foo@0.0.1",
    "profile": "{...}",
    "reason": "compiler-artifact",
    "target": {
      "crate_types": [
        "bin"
      ],
      "doc": true,
      "doctest": false,
      "edition": "2015",
      "kind": [
        "bin"
      ],
      "name": "foo",
      "src_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/foo-0.0.1/src/main.rs",
      "test": true
    }
  },
  {
    "reason": "build-finished",
    "success": true
  }
]
"#]]
            .is_json()
            .against_jsonlines(),
        )
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn with_index() {
    let registry = registry::init();
    pkg("foo", "0.0.1");

    cargo_process("install foo --index")
        .arg(registry.index_url().as_str())
        .with_stderr_data(str![[r#"
[UPDATING] `[ROOT]/registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `[ROOT]/registry`)
[INSTALLING] foo v0.0.1 (registry `[ROOT]/registry`)
[COMPILING] foo v0.0.1 (registry `[ROOT]/registry`)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1 (registry `[ROOT]/registry`)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");

    cargo_process("uninstall foo")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn multiple_pkgs() {
    pkg("foo", "0.0.1");
    pkg("bar", "0.0.2");

    cargo_process("install foo bar baz")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.2 (registry `dummy-registry`)
[ERROR] could not find `baz` in registry `crates-io` with version `*`
[INSTALLING] foo v0.0.1
[COMPILING] foo v0.0.1
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
[INSTALLING] bar v0.0.2
[COMPILING] bar v0.0.2
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/bar[EXE]
[INSTALLED] package `bar v0.0.2` (executable `bar[EXE]`)
[SUMMARY] Successfully installed foo, bar! Failed to install baz (see error(s) above).
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries
[ERROR] some crates failed to install

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
    assert_has_installed_exe(paths::cargo_home(), "bar");

    cargo_process("uninstall foo bar")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]
[REMOVING] [ROOT]/home/.cargo/bin/bar[EXE]
[SUMMARY] Successfully uninstalled foo, bar!

"#]])
        .run();

    assert_has_not_installed_exe(paths::cargo_home(), "foo");
    assert_has_not_installed_exe(paths::cargo_home(), "bar");
}

fn path() -> Vec<PathBuf> {
    env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect()
}

#[cargo_test]
fn multiple_pkgs_path_set() {
    // confirm partial failure results in 101 status code and does not have the
    //      '[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries'
    //  even if CARGO_HOME/bin is in the PATH
    pkg("foo", "0.0.1");
    pkg("bar", "0.0.2");

    // add CARGO_HOME/bin to path
    let mut path = path();
    path.push(paths::cargo_home().join("bin"));
    let new_path = env::join_paths(path).unwrap();
    cargo_process("install foo bar baz")
        .env("PATH", new_path)
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.2 (registry `dummy-registry`)
[ERROR] could not find `baz` in registry `crates-io` with version `*`
[INSTALLING] foo v0.0.1
[COMPILING] foo v0.0.1
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
[INSTALLING] bar v0.0.2
[COMPILING] bar v0.0.2
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/bar[EXE]
[INSTALLED] package `bar v0.0.2` (executable `bar[EXE]`)
[SUMMARY] Successfully installed foo, bar! Failed to install baz (see error(s) above).
[ERROR] some crates failed to install

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
    assert_has_installed_exe(paths::cargo_home(), "bar");

    cargo_process("uninstall foo bar")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]
[REMOVING] [ROOT]/home/.cargo/bin/bar[EXE]
[SUMMARY] Successfully uninstalled foo, bar!

"#]])
        .run();

    assert_has_not_installed_exe(paths::cargo_home(), "foo");
    assert_has_not_installed_exe(paths::cargo_home(), "bar");
}

#[cargo_test]
fn pick_max_version() {
    pkg("foo", "0.1.0");
    pkg("foo", "0.2.0");
    pkg("foo", "0.2.1");
    pkg("foo", "0.2.1-pre.1");
    pkg("foo", "0.3.0-pre.2");

    cargo_process("install foo").with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.2.1 (registry `dummy-registry`)
[INSTALLING] foo v0.2.1
[COMPILING] foo v0.2.1
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.2.1` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]]).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn installs_beta_version_by_explicit_name_from_git() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.3.0-beta.1"))
        .file("src/main.rs", "fn main() {}")
        .build();

    cargo_process("install --git")
        .arg(p.url().to_string())
        .arg("foo")
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn missing() {
    pkg("foo", "0.0.1");
    cargo_process("install bar")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] could not find `bar` in registry `crates-io` with version `*`

"#]])
        .run();
}

#[cargo_test]
fn missing_current_working_directory() {
    cargo_process("install .")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] To install the binaries for the package in current working directory use `cargo install --path .`. 
Use `cargo build` if you want to simply build the package.

"#]])
        .run();
}

#[cargo_test]
fn bad_version() {
    pkg("foo", "0.0.1");
    cargo_process("install foo --version=0.2.0")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] could not find `foo` in registry `crates-io` with version `=0.2.0`

"#]])
        .run();
}

#[cargo_test]
fn bad_paths() {
    cargo_process("install")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] `[ROOT]` is not a crate root; specify a crate to install from crates.io, or use --path or --git to specify an alternate source

"#]])
        .run();

    cargo_process("install --path .")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] `[ROOT]` does not contain a Cargo.toml file. --path must point to a directory containing a Cargo.toml file.

"#]])
        .run();

    let toml = paths::root().join("Cargo.toml");
    fs::write(toml, "").unwrap();
    cargo_process("install --path Cargo.toml")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] `[ROOT]/Cargo.toml` is not a directory. --path must point to a directory containing a Cargo.toml file.

"#]])
        .run();

    cargo_process("install --path .")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] failed to parse manifest at `[ROOT]/Cargo.toml`
...
"#]])
        .run();
}

#[cargo_test]
fn install_location_precedence() {
    pkg("foo", "0.0.1");

    let root = paths::root();
    let t1 = root.join("t1");
    let t2 = root.join("t2");
    let t3 = root.join("t3");
    let t4 = paths::cargo_home();

    fs::create_dir(root.join(".cargo")).unwrap();
    fs::write(
        root.join(".cargo/config.toml"),
        &format!(
            "[install]
             root = '{}'
            ",
            t3.display()
        ),
    )
    .unwrap();

    println!("install --root");

    cargo_process("install foo --root")
        .arg(&t1)
        .env("CARGO_INSTALL_ROOT", &t2)
        .run();
    assert_has_installed_exe(&t1, "foo");
    assert_has_not_installed_exe(&t2, "foo");

    println!("install CARGO_INSTALL_ROOT");

    cargo_process("install foo")
        .env("CARGO_INSTALL_ROOT", &t2)
        .run();
    assert_has_installed_exe(&t2, "foo");
    assert_has_not_installed_exe(&t3, "foo");

    println!("install install.root");

    cargo_process("install foo").run();
    assert_has_installed_exe(&t3, "foo");
    assert_has_not_installed_exe(&t4, "foo");

    fs::remove_file(root.join(".cargo/config.toml")).unwrap();

    println!("install cargo home");

    cargo_process("install foo").run();
    assert_has_installed_exe(&t4, "foo");
}

#[cargo_test]
fn install_path() {
    let p = project().file("src/main.rs", "fn main() {}").build();

    cargo_process("install --path").arg(p.root()).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
    // path-style installs force a reinstall
    p.cargo("install --path .").with_stderr_data(str![[r#"
[INSTALLING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[REPLACING] [ROOT]/home/.cargo/bin/foo[EXE]
[REPLACED] package `foo v0.0.1 ([ROOT]/foo)` with `foo v0.0.1 ([ROOT]/foo)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]]).run();
}

#[cargo_test]
fn install_target_dir() {
    let p = project().file("src/main.rs", "fn main() {}").build();

    p.cargo("install --target-dir td_test")
        .with_stderr_data(str![[r#"
[WARNING] Using `cargo install` to install the binaries from the package in current working directory is deprecated, use `cargo install --path .` instead. Use `cargo build` if you want to simply build the package.
[INSTALLING] foo v0.0.1 ([ROOT]/foo)
[COMPILING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1 ([ROOT]/foo)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();

    let mut path = p.root();
    path.push("td_test");
    assert!(path.exists());

    #[cfg(not(windows))]
    path.push("release/foo");
    #[cfg(windows)]
    path.push("release/foo.exe");
    assert!(path.exists());
}

#[cargo_test]
#[cfg(target_os = "linux")]
fn install_path_with_lowercase_cargo_toml() {
    let toml = paths::root().join("cargo.toml");
    fs::write(toml, "").unwrap();

    cargo_process("install --path .")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] `[ROOT]` does not contain a Cargo.toml file, but found cargo.toml please try to rename it to Cargo.toml. --path must point to a directory containing a Cargo.toml file.

"#]]
        )
        .run();
}

#[cargo_test]
fn install_relative_path_outside_current_ws() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.1.0"
                authors = []

                [workspace]
                members = ["baz"]
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file(
            "baz/Cargo.toml",
            r#"
                [package]
                name = "baz"
                version = "0.1.0"
                authors = []
                edition = "2021"

                [dependencies]
                foo = "1"
            "#,
        )
        .file("baz/src/lib.rs", "")
        .build();

    let _bin_project = project_in("bar")
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("install --path ../bar/foo")
        .with_stderr_data(str![[r#"
[INSTALLING] foo v0.0.1 ([ROOT]/bar/foo)
[COMPILING] foo v0.0.1 ([ROOT]/bar/foo)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1 ([ROOT]/bar/foo)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();

    // Validate the workspace error message to display available targets.
    p.cargo("install --path ../bar/foo --bin")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] "--bin" takes one argument.
Available binaries:
    foo


"#]])
        .run();
}

#[cargo_test]
fn multiple_packages_containing_binaries() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/main.rs", "fn main() {}")
        .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("a/src/main.rs", "fn main() {}")
        .build();

    cargo_process("install --git")
        .arg(p.url().to_string())
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] git repository `[ROOTURL]/foo`
[ERROR] multiple packages with binaries found: bar, foo. When installing a git repository, cargo will always search the entire repo for any Cargo.toml.
Please specify a package, e.g. `cargo install --git [ROOTURL]/foo bar`.

"#]])
        .run();
}

#[cargo_test]
fn multiple_packages_matching_example() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/lib.rs", "")
        .file("examples/ex1.rs", "fn main() {}")
        .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("bar/src/lib.rs", "")
        .file("bar/examples/ex1.rs", "fn main() {}")
        .build();

    cargo_process("install --example ex1 --git")
        .arg(p.url().to_string())
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] git repository `[ROOTURL]/foo`
[ERROR] multiple packages with examples found: bar, foo. When installing a git repository, cargo will always search the entire repo for any Cargo.toml.
Please specify a package, e.g. `cargo install --git [ROOTURL]/foo bar`.

"#]])
        .run();
}

#[cargo_test]
fn multiple_binaries_deep_select_uses_package_name() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/main.rs", "fn main() {}")
        .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
        .file("bar/baz/src/main.rs", "fn main() {}")
        .build();

    cargo_process("install --git")
        .arg(p.url().to_string())
        .arg("baz")
        .run();
}

#[cargo_test]
fn multiple_binaries_in_selected_package_installs_all() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/lib.rs", "")
        .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("bar/src/bin/bin1.rs", "fn main() {}")
        .file("bar/src/bin/bin2.rs", "fn main() {}")
        .build();

    cargo_process("install --git")
        .arg(p.url().to_string())
        .arg("bar")
        .run();

    let cargo_home = paths::cargo_home();
    assert_has_installed_exe(&cargo_home, "bin1");
    assert_has_installed_exe(&cargo_home, "bin2");
}

#[cargo_test]
fn multiple_binaries_in_selected_package_with_bin_option_installs_only_one() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/lib.rs", "")
        .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("bar/src/bin/bin1.rs", "fn main() {}")
        .file("bar/src/bin/bin2.rs", "fn main() {}")
        .build();

    cargo_process("install --bin bin1 --git")
        .arg(p.url().to_string())
        .arg("bar")
        .run();

    let cargo_home = paths::cargo_home();
    assert_has_installed_exe(&cargo_home, "bin1");
    assert_has_not_installed_exe(&cargo_home, "bin2");
}

#[cargo_test]
fn multiple_crates_select() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/main.rs", "fn main() {}")
        .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("a/src/main.rs", "fn main() {}")
        .build();

    cargo_process("install --git")
        .arg(p.url().to_string())
        .arg("foo")
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
    assert_has_not_installed_exe(paths::cargo_home(), "bar");

    cargo_process("install --git")
        .arg(p.url().to_string())
        .arg("bar")
        .run();
    assert_has_installed_exe(paths::cargo_home(), "bar");
}

#[cargo_test]
fn multiple_crates_git_all() {
    let p = git::repo(&paths::root().join("foo"))
        .file(
            "Cargo.toml",
            r#"
            [workspace]
            members = ["bin1", "bin2"]
            "#,
        )
        .file("bin1/Cargo.toml", &basic_manifest("bin1", "0.1.0"))
        .file("bin2/Cargo.toml", &basic_manifest("bin2", "0.1.0"))
        .file(
            "bin1/src/main.rs",
            r#"fn main() { println!("Hello, world!"); }"#,
        )
        .file(
            "bin2/src/main.rs",
            r#"fn main() { println!("Hello, world!"); }"#,
        )
        .build();

    cargo_process(&format!("install --git {} bin1 bin2", p.url().to_string())).run();
}

#[cargo_test]
fn multiple_crates_auto_binaries() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                authors = []

                [dependencies]
                bar = { path = "a" }
            "#,
        )
        .file("src/main.rs", "extern crate bar; fn main() {}")
        .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("a/src/lib.rs", "")
        .build();

    cargo_process("install --path").arg(p.root()).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn multiple_crates_auto_examples() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                authors = []

                [dependencies]
                bar = { path = "a" }
            "#,
        )
        .file("src/lib.rs", "extern crate bar;")
        .file(
            "examples/foo.rs",
            "
            extern crate bar;
            extern crate foo;
            fn main() {}
        ",
        )
        .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("a/src/lib.rs", "")
        .build();

    cargo_process("install --path")
        .arg(p.root())
        .arg("--example=foo")
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn no_binaries_or_examples() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                authors = []

                [dependencies]
                bar = { path = "a" }
            "#,
        )
        .file("src/lib.rs", "")
        .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("a/src/lib.rs", "")
        .build();

    cargo_process("install --path")
        .arg(p.root())
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] no packages found with binaries or examples

"#]])
        .run();
}

#[cargo_test]
fn no_binaries() {
    let p = project()
        .file("src/lib.rs", "")
        .file("examples/foo.rs", "fn main() {}")
        .build();

    cargo_process("install --path")
        .arg(p.root())
        .arg("foo")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] there is nothing to install in `foo v0.0.1 ([ROOT]/foo)`, because it has no binaries
`cargo install` is only for installing programs, and can't be used with libraries.
To use a library crate, add it as a dependency to a Cargo project with `cargo add`.

"#]])
        .run();
}

#[cargo_test]
fn examples() {
    let p = project()
        .file("src/lib.rs", "")
        .file("examples/foo.rs", "extern crate foo; fn main() {}")
        .build();

    cargo_process("install --path")
        .arg(p.root())
        .arg("--example=foo")
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn install_force() {
    let p = project().file("src/main.rs", "fn main() {}").build();

    cargo_process("install --path").arg(p.root()).run();

    let p = project()
        .at("foo2")
        .file("Cargo.toml", &basic_manifest("foo", "0.2.0"))
        .file("src/main.rs", "fn main() {}")
        .build();

    cargo_process("install --force --path")
        .arg(p.root())
        .with_stderr_data(str![[r#"
[INSTALLING] foo v0.2.0 ([ROOT]/foo2)
[COMPILING] foo v0.2.0 ([ROOT]/foo2)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[REPLACING] [ROOT]/home/.cargo/bin/foo[EXE]
[REPLACED] package `foo v0.0.1 ([ROOT]/foo)` with `foo v0.2.0 ([ROOT]/foo2)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();

    cargo_process("install --list")
        .with_stdout_data(str![[r#"
foo v0.2.0 ([ROOT]/foo2):
    foo[EXE]

"#]])
        .run();
}

#[cargo_test]
fn install_force_partial_overlap() {
    let p = project()
        .file("src/bin/foo-bin1.rs", "fn main() {}")
        .file("src/bin/foo-bin2.rs", "fn main() {}")
        .build();

    cargo_process("install --path").arg(p.root()).run();

    let p = project()
        .at("foo2")
        .file("Cargo.toml", &basic_manifest("foo", "0.2.0"))
        .file("src/bin/foo-bin2.rs", "fn main() {}")
        .file("src/bin/foo-bin3.rs", "fn main() {}")
        .build();

    cargo_process("install --force --path")
        .arg(p.root())
        .with_stderr_data(str![[r#"
[INSTALLING] foo v0.2.0 ([ROOT]/foo2)
[COMPILING] foo v0.2.0 ([ROOT]/foo2)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo-bin3[EXE]
[REPLACING] [ROOT]/home/.cargo/bin/foo-bin2[EXE]
[REMOVING] executable `[ROOT]/home/.cargo/bin/foo-bin1[EXE]` from previous version foo v0.0.1 ([ROOT]/foo)
[INSTALLED] package `foo v0.2.0 ([ROOT]/foo2)` (executable `foo-bin3[EXE]`)
[REPLACED] package `foo v0.0.1 ([ROOT]/foo)` with `foo v0.2.0 ([ROOT]/foo2)` (executable `foo-bin2[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();

    cargo_process("install --list")
        .with_stdout_data(str![[r#"
foo v0.2.0 ([ROOT]/foo2):
    foo-bin2[EXE]
    foo-bin3[EXE]

"#]])
        .run();
}

#[cargo_test]
fn install_force_bin() {
    let p = project()
        .file("src/bin/foo-bin1.rs", "fn main() {}")
        .file("src/bin/foo-bin2.rs", "fn main() {}")
        .build();

    cargo_process("install --path").arg(p.root()).run();

    let p = project()
        .at("foo2")
        .file("Cargo.toml", &basic_manifest("foo", "0.2.0"))
        .file("src/bin/foo-bin1.rs", "fn main() {}")
        .file("src/bin/foo-bin2.rs", "fn main() {}")
        .build();

    cargo_process("install --force --bin foo-bin2 --path")
        .arg(p.root())
        .with_stderr_data(str![[r#"
[INSTALLING] foo v0.2.0 ([ROOT]/foo2)
[COMPILING] foo v0.2.0 ([ROOT]/foo2)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[REPLACING] [ROOT]/home/.cargo/bin/foo-bin2[EXE]
[REPLACED] package `foo v0.0.1 ([ROOT]/foo)` with `foo v0.2.0 ([ROOT]/foo2)` (executable `foo-bin2[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();

    cargo_process("install --list")
        .with_stdout_data(str![[r#"
foo v0.0.1 ([ROOT]/foo):
    foo-bin1[EXE]
foo v0.2.0 ([ROOT]/foo2):
    foo-bin2[EXE]

"#]])
        .run();
}

#[cargo_test]
fn compile_failure() {
    let p = project().file("src/main.rs", "").build();

    cargo_process("install --path")
        .arg(p.root())
        .with_status(101)
        .with_stderr_data(str![[r#"
...
[ERROR] could not compile `foo` (bin "foo") due to 1 previous error
[ERROR] failed to compile `foo v0.0.1 ([ROOT]/foo)`, intermediate artifacts can be found at `[ROOT]/foo/target`.
To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path.
...
"#]])
        .run();
}

#[cargo_test]
fn git_repo() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/main.rs", "fn main() {}")
        .build();

    // Use `--locked` to test that we don't even try to write a lock file.
    cargo_process("install --locked --git")
        .arg(p.url().to_string())
        .with_stderr_data(str![[r#"
[UPDATING] git repository `[ROOTURL]/foo`
[WARNING] no Cargo.lock file published in foo v0.1.0 ([ROOTURL]/foo#[..])
[INSTALLING] foo v0.1.0 ([ROOTURL]/foo#[..])
[COMPILING] foo v0.1.0 ([ROOT]/home/.cargo/git/checkouts/foo-[HASH]/[..])
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.1.0 ([ROOTURL]/foo#[..])` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
#[cfg(target_os = "linux")]
fn git_repo_with_lowercase_cargo_toml() {
    let p = git::repo(&paths::root().join("foo"))
        .file("cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/main.rs", "fn main() {}")
        .build();

    cargo_process("install --git")
        .arg(p.url().to_string())
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] git repository [..]
[ERROR] Could not find Cargo.toml in `[..]`, but found cargo.toml please try to rename it to Cargo.toml

"#]]
        )
        .run();
}

#[cargo_test]
fn list() {
    pkg("foo", "0.0.1");
    pkg("bar", "0.2.1");
    pkg("bar", "0.2.2");

    cargo_process("install --list").with_stdout_data("").run();

    cargo_process("install bar --version =0.2.1").run();
    cargo_process("install foo").run();
    cargo_process("install --list")
        .with_stdout_data(str![[r#"
bar v0.2.1:
    bar[EXE]
foo v0.0.1:
    foo[EXE]

"#]])
        .run();
}

#[cargo_test]
fn list_error() {
    pkg("foo", "0.0.1");
    cargo_process("install foo").run();
    cargo_process("install --list")
        .with_stdout_data(str![[r#"
foo v0.0.1:
    foo[EXE]

"#]])
        .run();
    let mut worldfile_path = paths::cargo_home();
    worldfile_path.push(".crates.toml");
    let mut worldfile = OpenOptions::new()
        .write(true)
        .open(worldfile_path)
        .expect(".crates.toml should be there");
    worldfile.write_all(b"\x00").unwrap();
    drop(worldfile);
    cargo_process("install --list --verbose")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] failed to parse crate metadata at `[ROOT]/home/.cargo/.crates.toml`

Caused by:
  invalid TOML found for metadata

Caused by:
  TOML parse error at line 1, column 1
    |
  1 | v1]
    | ^
  invalid key

"#]])
        .run();
}

#[cargo_test]
fn uninstall_pkg_does_not_exist() {
    cargo_process("uninstall foo")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] package ID specification `foo` did not match any packages

"#]])
        .run();
}

#[cargo_test]
fn uninstall_bin_does_not_exist() {
    pkg("foo", "0.0.1");

    cargo_process("install foo").run();
    cargo_process("uninstall foo --bin=bar")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] binary `bar[EXE]` not installed as part of `foo v0.0.1`

"#]])
        .run();
}

#[cargo_test]
fn uninstall_piecemeal() {
    let p = project()
        .file("src/bin/foo.rs", "fn main() {}")
        .file("src/bin/bar.rs", "fn main() {}")
        .build();

    cargo_process("install --path").arg(p.root()).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
    assert_has_installed_exe(paths::cargo_home(), "bar");

    cargo_process("uninstall foo --bin=bar")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/bar[EXE]

"#]])
        .run();

    assert_has_installed_exe(paths::cargo_home(), "foo");
    assert_has_not_installed_exe(paths::cargo_home(), "bar");

    cargo_process("uninstall foo --bin=foo")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "foo");

    cargo_process("uninstall foo")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] package ID specification `foo` did not match any packages

"#]])
        .run();
}

#[cargo_test]
fn subcommand_works_out_of_the_box() {
    Package::new("cargo-foo", "1.0.0")
        .file("src/main.rs", r#"fn main() { println!("bar"); }"#)
        .publish();
    cargo_process("install cargo-foo").run();
    cargo_process("foo")
        .with_stdout_data(str![[r#"
bar

"#]])
        .run();
    cargo_process("--list")
        .with_stdout_data(str![[r#"
...
    foo
...
"#]])
        .run();
}

#[cargo_test]
fn installs_from_cwd_by_default() {
    let p = project().file("src/main.rs", "fn main() {}").build();

    p.cargo("install").with_stderr_data(str![[r#"
[WARNING] Using `cargo install` to install the binaries from the package in current working directory is deprecated, use `cargo install --path .` instead. Use `cargo build` if you want to simply build the package.
...
"#]]).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn installs_from_cwd_with_2018_warnings() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                authors = []
                edition = "2018"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("install")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] Using `cargo install` to install the binaries from the package in current working directory is no longer supported, use `cargo install --path .` instead. Use `cargo build` if you want to simply build the package.

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn uninstall_cwd() {
    let p = project().file("src/main.rs", "fn main() {}").build();
    p.cargo("install --path .").with_stderr_data(str![[r#"
[INSTALLING] foo v0.0.1 ([ROOT]/foo)
[COMPILING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1 ([ROOT]/foo)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]]).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");

    p.cargo("uninstall")
        .with_stdout_data("")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn uninstall_cwd_not_installed() {
    let p = project().file("src/main.rs", "fn main() {}").build();
    p.cargo("uninstall")
        .with_status(101)
        .with_stdout_data("")
        .with_stderr_data(str![[r#"
[ERROR] package `foo v0.0.1 ([ROOT]/foo)` is not installed

"#]])
        .run();
}

#[cargo_test]
fn uninstall_cwd_no_project() {
    cargo_process("uninstall")
        .with_status(101)
        .with_stdout_data("")
        .with_stderr_data(str![[r#"
[ERROR] failed to read `[ROOT]/Cargo.toml`

Caused by:
  [NOT_FOUND]

"#]])
        .run();
}

#[cargo_test]
fn do_not_rebuilds_on_local_install() {
    let p = project().file("src/main.rs", "fn main() {}").build();

    p.cargo("build --release").run();
    cargo_process("install --path")
        .arg(p.root())
        .with_stderr_data(str![[r#"
[INSTALLING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1 ([ROOT]/foo)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();

    assert!(p.build_dir().exists());
    assert!(p.release_bin("foo").exists());
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn reports_unsuccessful_subcommand_result() {
    Package::new("cargo-fail", "1.0.0")
        .file("src/main.rs", r#"fn main() { panic!("EXPLICIT PANIC!"); }"#)
        .publish();
    cargo_process("install cargo-fail").run();
    cargo_process("--list")
        .with_stdout_data(str![[r#"
...
    fail
...
"#]])
        .run();
    cargo_process("fail")
        .with_status(101)
        .with_stderr_data("...\n[..]EXPLICIT PANIC![..]\n...")
        .run();
}

#[cargo_test]
fn git_with_lockfile() {
    let p = git::repo(&paths::root().join("foo"))
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                authors = []

                [dependencies]
                bar = { path = "bar" }
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("bar/src/lib.rs", "fn main() {}")
        .file(
            "Cargo.lock",
            r#"
                [[package]]
                name = "foo"
                version = "0.1.0"
                dependencies = [ "bar 0.1.0" ]

                [[package]]
                name = "bar"
                version = "0.1.0"
            "#,
        )
        .build();

    cargo_process("install --git")
        .arg(p.url().to_string())
        .run();
}

#[cargo_test]
fn q_silences_warnings() {
    let p = project().file("src/main.rs", "fn main() {}").build();

    cargo_process("install -q --path")
        .arg(p.root())
        .with_stderr_data("")
        .run();
}

#[cargo_test]
fn readonly_dir() {
    pkg("foo", "0.0.1");

    let root = paths::root();
    let dir = &root.join("readonly");
    fs::create_dir(root.join("readonly")).unwrap();
    let mut perms = fs::metadata(dir).unwrap().permissions();
    perms.set_readonly(true);
    fs::set_permissions(dir, perms).unwrap();

    cargo_process("install foo").cwd(dir).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn use_path_workspace() {
    Package::new("foo", "1.0.0").publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.1.0"
                authors = []

                [workspace]
                members = ["baz"]
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file(
            "baz/Cargo.toml",
            r#"
                [package]
                name = "baz"
                version = "0.1.0"
                authors = []

                [dependencies]
                foo = "1"
            "#,
        )
        .file("baz/src/lib.rs", "")
        .build();

    p.cargo("build").run();
    let lock = p.read_lockfile();
    p.cargo("install").run();
    let lock2 = p.read_lockfile();
    assert_eq!(lock, lock2, "different lockfiles");
}

#[cargo_test]
fn path_install_workspace_root_despite_default_members() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "ws-root"
                version = "0.1.0"
                authors = []

                [workspace]
                members = ["ws-member"]
                default-members = ["ws-member"]
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file(
            "ws-member/Cargo.toml",
            r#"
                [package]
                name = "ws-member"
                version = "0.1.0"
                authors = []
            "#,
        )
        .file("ws-member/src/main.rs", "fn main() {}")
        .build();

    p.cargo("install --path")
        .arg(p.root())
        .arg("ws-root")
        .with_stderr_data(str![[r#"
[INSTALLING] ws-root v0.1.0 ([ROOT]/foo)
[COMPILING] ws-root v0.1.0 ([ROOT]/foo)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/ws-root[EXE]
[INSTALLED] package `ws-root v0.1.0 ([ROOT]/foo)` (executable `ws-root[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        // Particularly avoid "Installed package `ws-root v0.1.0 ([..]])` (executable `ws-member`)":
        .with_stderr_does_not_contain("ws-member")
        .run();
}

#[cargo_test]
fn git_install_workspace_root_despite_default_members() {
    let p = git::repo(&paths::root().join("foo"))
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "ws-root"
                version = "0.1.0"
                authors = []

                [workspace]
                members = ["ws-member"]
                default-members = ["ws-member"]
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file(
            "ws-member/Cargo.toml",
            r#"
                [package]
                name = "ws-member"
                version = "0.1.0"
                authors = []
            "#,
        )
        .file("ws-member/src/main.rs", "fn main() {}")
        .build();

    cargo_process("install --git")
        .arg(p.url().to_string())
        .arg("ws-root")
        .with_stderr_data(str![[r#"
...
[INSTALLED] package `ws-root v0.1.0 ([ROOTURL]/foo#[..])` (executable `ws-root[EXE]`)
...
"#]])
        // Particularly avoid "Installed package `ws-root v0.1.0 ([..]])` (executable `ws-member`)":
        .with_stderr_does_not_contain("ws-member")
        .run();
}

#[cargo_test]
fn dev_dependencies_no_check() {
    Package::new("foo", "1.0.0").publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.1.0"
                authors = []

                [dev-dependencies]
                baz = "1.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("build")
        .with_status(101)
        .with_stderr_data(str![[r#"
...
[ERROR] no matching package named `baz` found
...
"#]])
        .run();
    p.cargo("install").run();
}

#[cargo_test]
fn dev_dependencies_lock_file_untouched() {
    Package::new("foo", "1.0.0").publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                authors = []

                [dev-dependencies]
                bar = { path = "a" }
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("a/src/lib.rs", "")
        .build();

    p.cargo("build").run();
    let lock = p.read_lockfile();
    p.cargo("install").run();
    let lock2 = p.read_lockfile();
    assert!(lock == lock2, "different lockfiles");
}

#[cargo_test]
fn install_target_native() {
    pkg("foo", "0.1.0");

    cargo_process("install foo --target")
        .arg(cargo_test_support::rustc_host())
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn install_target_foreign() {
    if cross_compile::disabled() {
        return;
    }

    pkg("foo", "0.1.0");

    cargo_process("install foo --target")
        .arg(cross_compile::alternate())
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn vers_precise() {
    pkg("foo", "0.1.1");
    pkg("foo", "0.1.2");

    cargo_process("install foo --vers 0.1.1")
        .with_stderr_data(str![[r#"
...
[DOWNLOADED] foo v0.1.1 (registry `dummy-registry`)
...
"#]])
        .run();
}

#[cargo_test]
fn version_precise() {
    pkg("foo", "0.1.1");
    pkg("foo", "0.1.2");

    cargo_process("install foo --version 0.1.1")
        .with_stderr_data(str![[r#"
...
[DOWNLOADED] foo v0.1.1 (registry `dummy-registry`)
...
"#]])
        .run();
}

#[cargo_test]
fn inline_version_precise() {
    pkg("foo", "0.1.1");
    pkg("foo", "0.1.2");

    cargo_process("install foo@0.1.1")
        .with_stderr_data(str![[r#"
...
[DOWNLOADED] foo v0.1.1 (registry `dummy-registry`)
...
"#]])
        .run();
}

#[cargo_test]
fn inline_version_multiple() {
    pkg("foo", "0.1.0");
    pkg("foo", "0.1.1");
    pkg("foo", "0.1.2");
    pkg("bar", "0.2.0");
    pkg("bar", "0.2.1");
    pkg("bar", "0.2.2");

    cargo_process("install foo@0.1.1 bar@0.2.1")
        .with_stderr_data(str![[r#"
...
[DOWNLOADED] foo v0.1.1 (registry `dummy-registry`)
...
[DOWNLOADED] bar v0.2.1 (registry `dummy-registry`)
...
"#]])
        .run();
}

#[cargo_test]
fn inline_version_without_name() {
    pkg("foo", "0.1.1");
    pkg("foo", "0.1.2");

    cargo_process("install @0.1.1")
        .with_status(1)
        .with_stderr_data(str![[r#"
[ERROR] invalid value '@0.1.1' for '[CRATE[@<VER>]]...': missing crate name before '@'

For more information, try '--help'.

"#]])
        .run();
}

#[cargo_test]
fn inline_and_explicit_version() {
    pkg("foo", "0.1.1");
    pkg("foo", "0.1.2");

    cargo_process("install foo@0.1.1 --version 0.1.1")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] cannot specify both `@<VERSION>` and `--version <VERSION>`

"#]])
        .run();
}

#[cargo_test]
fn not_both_vers_and_version() {
    pkg("foo", "0.1.1");
    pkg("foo", "0.1.2");

    cargo_process("install foo --version 0.1.1 --vers 0.1.2")
        .with_status(1)
        .with_stderr_data(str![[r#"
[ERROR] the argument '--version <VERSION>' cannot be used multiple times

Usage: cargo[EXE] install [OPTIONS] [CRATE[@<VER>]]...

For more information, try '--help'.

"#]])
        .run();
}

#[cargo_test]
fn test_install_git_cannot_be_a_base_url() {
    cargo_process("install --git github.com:rust-lang/rustfmt.git")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] invalid url `github.com:rust-lang/rustfmt.git`: cannot-be-a-base-URLs are not supported

"#]])
        .run();
}

#[cargo_test]
fn uninstall_multiple_and_specifying_bin() {
    cargo_process("uninstall foo bar --bin baz")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] A binary can only be associated with a single installed package, specifying multiple specs with --bin is redundant.

"#]])
        .run();
}

#[cargo_test]
fn uninstall_with_empty_package_option() {
    cargo_process("uninstall -p")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] "--package <SPEC>" requires a SPEC format value.
Run `cargo help pkgid` for more information about SPEC format.

"#]])
        .run();
}

#[cargo_test]
fn uninstall_multiple_and_some_pkg_does_not_exist() {
    pkg("foo", "0.0.1");

    cargo_process("install foo").run();

    cargo_process("uninstall foo bar")
        .with_status(101)
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]
[ERROR] package ID specification `bar` did not match any packages
[SUMMARY] Successfully uninstalled foo! Failed to uninstall bar (see error(s) above).
[ERROR] some packages failed to uninstall

"#]])
        .run();

    assert_has_not_installed_exe(paths::cargo_home(), "foo");
    assert_has_not_installed_exe(paths::cargo_home(), "bar");
}

#[cargo_test]
fn custom_target_dir_for_git_source() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/main.rs", "fn main() {}")
        .build();

    cargo_process("install --git")
        .arg(p.url().to_string())
        .run();
    assert!(!paths::root().join("target/release").is_dir());

    cargo_process("install --force --git")
        .arg(p.url().to_string())
        .env("CARGO_TARGET_DIR", "target")
        .run();
    assert!(paths::root().join("target/release").is_dir());
}

#[cargo_test]
fn install_respects_lock_file() {
    // `cargo install` now requires --locked to use a Cargo.lock.
    Package::new("bar", "0.1.0").publish();
    Package::new("bar", "0.1.1")
        .file("src/lib.rs", "not rust")
        .publish();
    Package::new("foo", "0.1.0")
        .dep("bar", "0.1")
        .file("src/lib.rs", "")
        .file(
            "src/main.rs",
            "extern crate foo; extern crate bar; fn main() {}",
        )
        .file(
            "Cargo.lock",
            r#"
[[package]]
name = "bar"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "foo"
version = "0.1.0"
dependencies = [
 "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
"#,
        )
        .publish();

    cargo_process("install foo")
        .with_stderr_data(str![[r#"
...
[..]not rust[..]
...
"#]])
        .with_status(101)
        .run();
    cargo_process("install --locked foo").run();
}

#[cargo_test]
fn install_path_respects_lock_file() {
    // --path version of install_path_respects_lock_file, --locked is required
    // to use Cargo.lock.
    Package::new("bar", "0.1.0").publish();
    Package::new("bar", "0.1.1")
        .file("src/lib.rs", "not rust")
        .publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.1.0"

            [dependencies]
            bar = "0.1"
            "#,
        )
        .file("src/main.rs", "extern crate bar; fn main() {}")
        .file(
            "Cargo.lock",
            r#"
[[package]]
name = "bar"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "foo"
version = "0.1.0"
dependencies = [
 "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
"#,
        )
        .build();

    p.cargo("install --path .")
        .with_stderr_data(str![[r#"
...
[..]not rust[..]
...
"#]])
        .with_status(101)
        .run();
    p.cargo("install --path . --locked").run();
}

#[cargo_test]
fn lock_file_path_deps_ok() {
    Package::new("bar", "0.1.0").publish();

    Package::new("foo", "0.1.0")
        .dep("bar", "0.1")
        .file("src/lib.rs", "")
        .file(
            "src/main.rs",
            "extern crate foo; extern crate bar; fn main() {}",
        )
        .file(
            "Cargo.lock",
            r#"
            [[package]]
            name = "bar"
            version = "0.1.0"

            [[package]]
            name = "foo"
            version = "0.1.0"
            dependencies = [
             "bar 0.1.0",
            ]
            "#,
        )
        .publish();

    cargo_process("install foo").run();
}

#[cargo_test]
fn install_empty_argument() {
    // Bug 5229
    cargo_process("install")
        .arg("")
        .with_status(1)
        .with_stderr_data(str![[r#"
[ERROR] invalid value '' for '[CRATE[@<VER>]]...': crate name is empty

For more information, try '--help'.

"#]])
        .run();
}

#[cargo_test]
fn git_repo_replace() {
    let p = git::repo(&paths::root().join("foo"))
        .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
        .file("src/main.rs", "fn main() {}")
        .build();
    let repo = git2::Repository::open(&p.root()).unwrap();
    let old_rev = repo.revparse_single("HEAD").unwrap().id();
    cargo_process("install --git")
        .arg(p.url().to_string())
        .run();
    git::commit(&repo);
    let new_rev = repo.revparse_single("HEAD").unwrap().id();
    let mut path = paths::home();
    path.push(".cargo/.crates.toml");

    assert_ne!(old_rev, new_rev);
    assert!(fs::read_to_string(path.clone())
        .unwrap()
        .contains(&format!("{}", old_rev)));
    cargo_process("install --force --git")
        .arg(p.url().to_string())
        .run();
    assert!(fs::read_to_string(path)
        .unwrap()
        .contains(&format!("{}", new_rev)));
}

#[cargo_test]
fn workspace_uses_workspace_target_dir() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                authors = []

                [workspace]

                [dependencies]
                bar = { path = 'bar' }
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("bar/src/main.rs", "fn main() {}")
        .build();

    p.cargo("build --release").cwd("bar").run();
    cargo_process("install --path")
        .arg(p.root().join("bar"))
        .with_stderr_data(str![[r#"
[INSTALLING] bar v0.1.0 ([ROOT]/foo/bar)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/bar[EXE]
[INSTALLED] package `bar v0.1.0 ([ROOT]/foo/bar)` (executable `bar[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
}

#[cargo_test]
fn install_ignores_local_cargo_config() {
    pkg("bar", "0.0.1");

    let p = project()
        .file(
            ".cargo/config.toml",
            r#"
                [build]
                target = "non-existing-target"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("install bar").run();
    assert_has_installed_exe(paths::cargo_home(), "bar");
}

#[cargo_test]
fn install_ignores_unstable_table_in_local_cargo_config() {
    pkg("bar", "0.0.1");

    let p = project()
        .file(
            ".cargo/config.toml",
            r#"
                [unstable]
                build-std = ["core"]
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("install bar")
        .masquerade_as_nightly_cargo(&["build-std"])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "bar");
}

#[cargo_test]
fn install_global_cargo_config() {
    pkg("bar", "0.0.1");

    let config = paths::cargo_home().join("config.toml");
    let mut toml = fs::read_to_string(&config).unwrap_or_default();

    toml.push_str(
        r#"
            [build]
            target = 'nonexistent'
        "#,
    );
    fs::write(&config, toml).unwrap();

    cargo_process("install bar")
        .with_status(101)
        .with_stderr_data(
            str![[r#"
[INSTALLING] bar v0.0.1
Caused by:
  process didn't exit successfully: `rustc [..]--target nonexistent[..]` ([EXIT_STATUS]: 1)
...
"#]]
            .unordered(),
        )
        .run();
}

#[cargo_test]
fn install_path_config() {
    project()
        .file(
            ".cargo/config.toml",
            r#"
            [build]
            target = 'nonexistent'
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();
    cargo_process("install --path foo")
        .with_status(101)
        .with_stderr_data(str![[r#"
...
  process didn't exit successfully: `rustc [..]--target nonexistent[..]` ([EXIT_STATUS]: 1)
...
"#]])
        .run();
}

#[cargo_test]
fn install_version_req() {
    // Try using a few versionreq styles.
    pkg("foo", "0.0.3");
    pkg("foo", "1.0.4");
    pkg("foo", "1.0.5");
    cargo_process("install foo --version=*")
        .with_stderr_does_not_contain("[WARNING][..]is not a valid semver[..]")
        .with_stderr_data(str![[r#"
...
[INSTALLING] foo v1.0.5
...
"#]])
        .run();
    cargo_process("uninstall foo").run();
    cargo_process("install foo --version=^1.0")
        .with_stderr_does_not_contain("[WARNING][..]is not a valid semver[..]")
        .with_stderr_data(str![[r#"
...
[INSTALLING] foo v1.0.5
...
"#]])
        .run();
    cargo_process("uninstall foo").run();
    cargo_process("install foo --version=0.0.*")
        .with_stderr_does_not_contain("[WARNING][..]is not a valid semver[..]")
        .with_stderr_data(str![[r#"
...
[INSTALLING] foo v0.0.3
...
"#]])
        .run();
}

#[cargo_test]
fn git_install_reads_workspace_manifest() {
    let p = git::repo(&paths::root().join("foo"))
        .file(
            "Cargo.toml",
            r#"
            [workspace]
            members = ["bin1"]

            [profile.release]
            incremental = 3
            "#,
        )
        .file("bin1/Cargo.toml", &basic_manifest("bin1", "0.1.0"))
        .file(
            "bin1/src/main.rs",
            r#"fn main() { println!("Hello, world!"); }"#,
        )
        .build();

    cargo_process(&format!("install --git {}", p.url().to_string()))
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] git repository `[ROOTURL]/foo`
[ERROR] invalid type: integer `3`, expected a boolean
 --> home/.cargo/git/checkouts/foo-[HASH]/[..]/Cargo.toml:6:27
  |
6 |             incremental = 3
  |                           ^
  |
[ERROR] invalid type: integer `3`, expected a boolean
 --> home/.cargo/git/checkouts/foo-[HASH]/[..]/Cargo.toml:6:27
  |
6 |             incremental = 3
  |                           ^
  |

"#]])
        .run();
}

#[cargo_test]
fn install_git_with_symlink_home() {
    // Ensure that `cargo install` with a git repo is OK when CARGO_HOME is a
    // symlink, and uses an build script.
    if !symlink_supported() {
        return;
    }
    let p = git::new("foo", |p| {
        p.file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
            .file("src/main.rs", "fn main() {}")
            // This triggers discover_git_and_list_files for detecting changed files.
            .file("build.rs", "fn main() {}")
    });
    #[cfg(unix)]
    use std::os::unix::fs::symlink;
    #[cfg(windows)]
    use std::os::windows::fs::symlink_dir as symlink;

    let actual = paths::root().join("actual-home");
    t!(std::fs::create_dir(&actual));
    t!(symlink(&actual, paths::home().join(".cargo")));
    cargo_process("install --git")
        .arg(p.url().to_string())
        .with_stderr_data(str![[r#"
[UPDATING] git repository `[ROOTURL]/foo`
[INSTALLING] foo v1.0.0 ([ROOTURL]/foo#[..])
[COMPILING] foo v1.0.0 ([ROOT]/home/.cargo/git/checkouts/foo-[HASH]/[..])
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v1.0.0 ([ROOTURL]/foo#[..])` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
}

#[cargo_test]
fn install_yanked_cargo_package() {
    Package::new("baz", "0.0.1").yanked(true).publish();
    cargo_process("install baz --version 0.0.1")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] cannot install package `baz`, it has been yanked from registry `crates-io`

"#]])
        .run();
}

#[cargo_test]
fn install_cargo_package_in_a_patched_workspace() {
    pkg("foo", "0.1.0");
    pkg("fizz", "1.0.0");

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.1.0"
                authors = []

                [workspace]
                members = ["baz"]
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file(
            "baz/Cargo.toml",
            r#"
                [package]
                name = "baz"
                version = "0.1.0"
                authors = []

                [dependencies]
                fizz = "1"

                [patch.crates-io]
                fizz = { version = "=1.0.0" }
            "#,
        )
        .file("baz/src/lib.rs", "")
        .build();

    p.cargo("check")
        .with_stderr_data(str![[r#"
[WARNING] patch for the non root package will be ignored, specify patch at the workspace root:
package:   [ROOT]/foo/baz/Cargo.toml
workspace: [ROOT]/foo/Cargo.toml
...
"#]])
        .run();

    // A crate installation must not emit any message from a workspace under
    // current working directory.
    // See https://github.com/rust-lang/cargo/issues/8619
    p.cargo("install foo").with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.1.0 (registry `dummy-registry`)
[INSTALLING] foo v0.1.0
[COMPILING] foo v0.1.0
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.1.0` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]]).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn locked_install_without_published_lockfile() {
    Package::new("foo", "0.1.0")
        .file("src/main.rs", "//! Some docs\nfn main() {}")
        .publish();

    cargo_process("install foo --locked")
        .with_stderr_data(str![[r#"
...
[WARNING] no Cargo.lock file published in foo v0.1.0
...
"#]])
        .run();
}

#[cargo_test]
fn install_semver_metadata() {
    // Check trying to install a package that uses semver metadata.
    // This uses alt registry because the bug this is exercising doesn't
    // trigger with a replaced source.
    registry::alt_init();
    Package::new("foo", "1.0.0+abc")
        .alternative(true)
        .file("src/main.rs", "fn main() {}")
        .publish();

    cargo_process("install foo --registry alternative --version 1.0.0+abc").run();
    cargo_process("install foo --registry alternative")
        .with_stderr_data(str![[r#"
[UPDATING] `alternative` index
[IGNORED] package `foo v1.0.0+abc (registry `alternative`)` is already installed, use --force to override
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    // "Updating" is not displayed here due to the --version fast-path.
    cargo_process("install foo --registry alternative --version 1.0.0+abc")
        .with_stderr_data(str![[r#"
[IGNORED] package `foo v1.0.0+abc (registry `alternative`)` is already installed, use --force to override
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    cargo_process("install foo --registry alternative --version 1.0.0 --force")
        .with_stderr_data(str![[r#"
[UPDATING] `alternative` index
[INSTALLING] foo v1.0.0+abc (registry `alternative`)
[COMPILING] foo v1.0.0+abc (registry `alternative`)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[REPLACING] [ROOT]/home/.cargo/bin/foo[EXE]
[REPLACED] package `foo v1.0.0+abc (registry `alternative`)` with `foo v1.0.0+abc (registry `alternative`)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    // Check that from a fresh cache will work without metadata, too.
    paths::home().join(".cargo/registry").rm_rf();
    paths::home().join(".cargo/bin").rm_rf();
    cargo_process("install foo --registry alternative --version 1.0.0")
        .with_stderr_data(str![[r#"
[UPDATING] `alternative` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v1.0.0+abc (registry `alternative`)
[INSTALLING] foo v1.0.0+abc (registry `alternative`)
[COMPILING] foo v1.0.0+abc (registry `alternative`)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v1.0.0+abc (registry `alternative`)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
}

#[cargo_test]
fn no_auto_fix_note() {
    Package::new("auto_fix", "0.0.1")
        .file("src/lib.rs", "use std::io;")
        .file(
            "src/main.rs",
            &format!("extern crate {}; use std::io; fn main() {{}}", "auto_fix"),
        )
        .publish();

    // This should not contain a suggestion to run `cargo fix`
    //
    // This is checked by matching the full output as `with_stderr_does_not_contain`
    // can be brittle
    cargo_process("install auto_fix")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] auto_fix v0.0.1 (registry `dummy-registry`)
[INSTALLING] auto_fix v0.0.1
[COMPILING] auto_fix v0.0.1
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/auto_fix[EXE]
[INSTALLED] package `auto_fix v0.0.1` (executable `auto_fix[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "auto_fix");

    cargo_process("uninstall auto_fix")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/auto_fix[EXE]

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "auto_fix");
}

#[cargo_test]
fn failed_install_retains_temp_directory() {
    // Verifies that the temporary directory persists after a build failure.
    Package::new("foo", "0.0.1")
        .file("src/main.rs", "x")
        .publish();
    let err = cargo_process("install foo").exec_with_output().unwrap_err();
    let err = err.downcast::<ProcessError>().unwrap();
    let stderr = String::from_utf8(err.stderr.unwrap()).unwrap();
    assert_e2e().eq(&stderr, str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[INSTALLING] foo v0.0.1
[COMPILING] foo v0.0.1
[ERROR] expected one of `!` or `::`, found `<eof>`
 --> [ROOT]/home/.cargo/registry/src/-[..]/foo-0.0.1/src/main.rs:1:1
  |
1 | x
  | ^ expected one of `!` or `::`

[ERROR] could not compile `foo` (bin "foo") due to 1 previous error
[ERROR] failed to compile `foo v0.0.1`, intermediate artifacts can be found at `[..]`.
To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path.

"#]]);

    // Find the path in the output.
    let stderr = stderr.split_once("found at `").unwrap().1;
    let end = stderr.find('.').unwrap() - 1;
    let path = Path::new(&stderr[..end]);
    assert!(path.exists());
    assert!(path.join("release/deps").exists());
}

#[cargo_test]
fn sparse_install() {
    // Checks for an issue where uninstalling something corrupted
    // the SourceIds of sparse registries.
    // See https://github.com/rust-lang/cargo/issues/11751
    let _registry = registry::RegistryBuilder::new().http_index().build();

    pkg("foo", "0.0.1");
    pkg("bar", "0.0.1");

    cargo_process("install foo --registry dummy-registry")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[INSTALLING] foo v0.0.1 (registry `dummy-registry`)
[UPDATING] `dummy-registry` index
[COMPILING] foo v0.0.1 (registry `dummy-registry`)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1 (registry `dummy-registry`)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
    let assert_v1 = |expected| {
        let v1 = fs::read_to_string(paths::home().join(".cargo/.crates.toml")).unwrap();
        assert_e2e().eq(&v1, expected);
    };
    assert_v1(str![[r#"
[v1]
"foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]

"#]]);
    cargo_process("install bar").run();
    assert_has_installed_exe(paths::cargo_home(), "bar");
    assert_v1(str![[r#"
[v1]
"bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = ["bar[EXE]"]
"foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]

"#]]);

    cargo_process("uninstall bar")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/bar[EXE]

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "bar");
    assert_v1(str![[r#"
[v1]
"foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]

"#]]);
    cargo_process("uninstall foo")
        .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "foo");
    assert_v1(str![[r#"
[v1]

"#]]);
}

#[cargo_test]
fn self_referential() {
    // Some packages build-dep on prior versions of themselves.
    Package::new("foo", "0.0.1")
        .file("src/lib.rs", "fn hello() {}")
        .file("src/main.rs", "fn main() {}")
        .file("build.rs", "fn main() {}")
        .publish();
    Package::new("foo", "0.0.2")
        .file("src/lib.rs", "fn hello() {}")
        .file("src/main.rs", "fn main() {}")
        .file("build.rs", "fn main() {}")
        .build_dep("foo", "0.0.1")
        .publish();

    cargo_process("install foo").with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.2 (registry `dummy-registry`)
[INSTALLING] foo v0.0.2
[LOCKING] 1 package to latest compatible version
[ADDING] foo v0.0.1 (available: v0.0.2)
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[COMPILING] foo v0.0.1
[COMPILING] foo v0.0.2
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.2` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]]).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn ambiguous_registry_vs_local_package() {
    // Correctly install 'foo' from a local package, even if that package also
    // depends on a registry dependency named 'foo'.
    Package::new("foo", "0.0.1")
        .file("src/lib.rs", "fn hello() {}")
        .publish();

    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file(
            "Cargo.toml",
            r#"
        [package]
        name = "foo"
        version = "0.1.0"
        authors = []
        edition = "2021"

        [dependencies]
        foo = "0.0.1"
    "#,
        )
        .build();

    cargo_process("install --path")
        .arg(p.root())
        .with_stderr_data(str![[r#"
[INSTALLING] foo v0.1.0 ([ROOT]/foo)
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[COMPILING] foo v0.0.1
[COMPILING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.1.0 ([ROOT]/foo)` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn install_with_redundant_default_mode() {
    pkg("foo", "0.0.1");

    cargo_process("install foo --release")
        .with_stderr_data(str![[r#"
[ERROR] unexpected argument '--release' found

  tip: `--release` is the default for `cargo install`; instead `--debug` is supported

Usage: cargo[EXE] install [OPTIONS] [CRATE[@<VER>]]...

For more information, try '--help'.

"#]])
        .with_status(1)
        .run();
}

#[cargo_test]
fn install_incompat_msrv() {
    Package::new("foo", "0.1.0")
        .file("src/main.rs", "fn main() {}")
        .rust_version("1.30")
        .publish();
    Package::new("foo", "0.2.0")
        .file("src/main.rs", "fn main() {}")
        .rust_version("1.9876.0")
        .publish();

    cargo_process("install foo")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] cannot install package `foo 0.2.0`, it requires rustc 1.9876.0 or newer, while the currently active rustc version is [..]
`foo 0.1.0` supports rustc 1.30

"#]])
        .with_status(101)
        .run();
}

fn assert_tracker_noexistence(key: &str) {
    let v1_data: toml::Value =
        toml::from_str(&fs::read_to_string(paths::cargo_home().join(".crates.toml")).unwrap())
            .unwrap();
    let v2_data: serde_json::Value = serde_json::from_str(
        &fs::read_to_string(paths::cargo_home().join(".crates2.json")).unwrap(),
    )
    .unwrap();

    assert!(v1_data["v1"].get(key).is_none());
    assert!(v2_data["installs"][key].is_null());
}

#[cargo_test]
fn uninstall_running_binary() {
    use std::io::Write;

    Package::new("foo", "0.0.1")
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
            "#,
        )
        .file(
            "src/main.rs",
            r#"
                use std::net::TcpStream;
                use std::env::var;
                use std::io::Read;
                fn main() {
                    for i in 0..2 {
                        TcpStream::connect(&var("__ADDR__").unwrap()[..])
                            .unwrap()
                            .read_to_end(&mut Vec::new())
                            .unwrap();
                    }
                }
            "#,
        )
        .publish();

    cargo_process("install foo").with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[INSTALLING] foo v0.0.1
[COMPILING] foo v0.0.1
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]]).run();
    assert_has_installed_exe(paths::cargo_home(), "foo");

    let foo_bin = paths::cargo_home().join("bin").join(exe("foo"));
    let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let addr = l.local_addr().unwrap().to_string();
    let t = thread::spawn(move || {
        ProcessBuilder::new(foo_bin)
            .env("__ADDR__", addr)
            .exec()
            .unwrap();
    });
    let key = "foo 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)";

    #[cfg(windows)]
    {
        // Ensure foo is running before the first `cargo uninstall` call
        l.accept().unwrap().0.write_all(&[1]).unwrap();
        cargo_process("uninstall foo")
            .with_status(101)
            .with_stderr_data(str![[r#"
...
[ERROR] failed to remove file `[ROOT]/home/.cargo/bin/foo[EXE]`
...
"#]])
            .run();
        // Ensure foo is stopped before the second `cargo uninstall` call
        l.accept().unwrap().0.write_all(&[1]).unwrap();
        t.join().unwrap();
        cargo_process("uninstall foo")
            .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]

"#]])
            .run();
    };

    #[cfg(not(windows))]
    {
        // Ensure foo is running before the first `cargo uninstall` call
        l.accept().unwrap().0.write_all(&[1]).unwrap();
        cargo_process("uninstall foo")
            .with_stderr_data(str![[r#"
[REMOVING] [ROOT]/home/.cargo/bin/foo[EXE]

"#]])
            .run();
        l.accept().unwrap().0.write_all(&[1]).unwrap();
        t.join().unwrap();
    };

    assert_has_not_installed_exe(paths::cargo_home(), "foo");
    assert_tracker_noexistence(key);

    cargo_process("install foo").with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[INSTALLING] foo v0.0.1
[COMPILING] foo v0.0.1
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]]).run();
}

#[cargo_test]
fn dry_run() {
    pkg("foo", "0.0.1");

    cargo_process("-Z unstable-options install --dry-run foo")
        .masquerade_as_nightly_cargo(&["install::dry-run"])
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
[INSTALLING] foo v0.0.1
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
[WARNING] aborting install due to dry run
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn dry_run_incompatible_package() {
    Package::new("some-package-from-the-distant-future", "0.0.1")
        .rust_version("1.2345.0")
        .file("src/main.rs", "fn main() {}")
        .publish();

    cargo_process("-Z unstable-options install --dry-run some-package-from-the-distant-future")
        .masquerade_as_nightly_cargo(&["install::dry-run"])
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] cannot install package `some-package-from-the-distant-future 0.0.1`, it requires rustc 1.2345.0 or newer, while the currently active rustc version is [..]

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "some-package-from-the-distant-future");
}

#[cargo_test]
fn dry_run_incompatible_package_dependecy() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                authors = []

                [dependencies]
                some-package-from-the-distant-future = { path = "a" }
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file(
            "a/Cargo.toml",
            r#"
                [package]
                name = "some-package-from-the-distant-future"
                version = "0.1.0"
                authors = []
                rust-version = "1.2345.0"
            "#,
        )
        .file("a/src/lib.rs", "")
        .build();

    cargo_process("-Z unstable-options install --dry-run --path")
        .arg(p.root())
        .arg("foo")
        .masquerade_as_nightly_cargo(&["install::dry-run"])
        .with_status(101)
        .with_stderr_data(str![[r#"
[INSTALLING] foo v0.1.0 ([ROOT]/foo)
[LOCKING] 1 package to latest compatible version
[ERROR] failed to compile `foo v0.1.0 ([ROOT]/foo)`, intermediate artifacts can be found at `[ROOT]/foo/target`.
To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path.

Caused by:
  rustc [..] is not supported by the following package:
    some-package-from-the-distant-future@0.1.0 requires rustc 1.2345.0

"#]])
        .run();
    assert_has_not_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn dry_run_upgrade() {
    pkg("foo", "0.0.1");
    cargo_process("install foo").run();
    assert_has_installed_exe(paths::cargo_home(), "foo");

    pkg("foo", "0.0.2");
    cargo_process("-Z unstable-options install --dry-run foo")
        .masquerade_as_nightly_cargo(&["install::dry-run"])
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.0.2 (registry `dummy-registry`)
[INSTALLING] foo v0.0.2
[REPLACING] [ROOT]/home/.cargo/bin/foo[EXE]
[WARNING] aborting install due to dry run
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "foo");
}

#[cargo_test]
fn dry_run_remove_orphan() {
    Package::new("bar", "1.0.0")
        .file("src/bin/client.rs", "fn main() {}")
        .file("src/bin/server.rs", "fn main() {}")
        .publish();

    cargo_process("install bar")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)
[INSTALLING] bar v1.0.0
[COMPILING] bar v1.0.0
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
[INSTALLING] [ROOT]/home/.cargo/bin/client[EXE]
[INSTALLING] [ROOT]/home/.cargo/bin/server[EXE]
[INSTALLED] package `bar v1.0.0` (executables `client[EXE]`, `server[EXE]`)
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "client");
    assert_has_installed_exe(paths::cargo_home(), "server");

    Package::new("bar", "2.0.0")
        .file("src/bin/client.rs", "fn main() {}")
        .publish();

    cargo_process("-Z unstable-options install --dry-run bar")
        .masquerade_as_nightly_cargo(&["install::dry-run"])
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] bar v2.0.0 (registry `dummy-registry`)
[INSTALLING] bar v2.0.0
[REPLACING] [ROOT]/home/.cargo/bin/client[EXE]
[REMOVING] executable `[ROOT]/home/.cargo/bin/server[EXE]` from previous version bar v1.0.0
[WARNING] aborting install due to dry run
[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries

"#]])
        .run();
    assert_has_installed_exe(paths::cargo_home(), "client");
    // Ensure server is still installed after the dry run
    assert_has_installed_exe(paths::cargo_home(), "server");
}
