summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/tests/testsuite/install_upgrade.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/cargo/tests/testsuite/install_upgrade.rs')
-rw-r--r--src/tools/cargo/tests/testsuite/install_upgrade.rs862
1 files changed, 862 insertions, 0 deletions
diff --git a/src/tools/cargo/tests/testsuite/install_upgrade.rs b/src/tools/cargo/tests/testsuite/install_upgrade.rs
new file mode 100644
index 000000000..ae641ba98
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/install_upgrade.rs
@@ -0,0 +1,862 @@
+//! Tests for `cargo install` where it upgrades a package if it is out-of-date.
+
+use cargo::core::PackageId;
+use std::collections::BTreeSet;
+use std::env;
+use std::fs;
+use std::path::PathBuf;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use cargo_test_support::install::{cargo_home, exe};
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::{self, Package};
+use cargo_test_support::{
+ basic_manifest, cargo_process, cross_compile, execs, git, process, project, Execs,
+};
+
+fn pkg_maybe_yanked(name: &str, vers: &str, yanked: bool) {
+ Package::new(name, vers)
+ .yanked(yanked)
+ .file(
+ "src/main.rs",
+ r#"fn main() { println!("{}", env!("CARGO_PKG_VERSION")) }"#,
+ )
+ .publish();
+}
+
+// Helper for publishing a package.
+fn pkg(name: &str, vers: &str) {
+ pkg_maybe_yanked(name, vers, false)
+}
+
+fn v1_path() -> PathBuf {
+ cargo_home().join(".crates.toml")
+}
+
+fn v2_path() -> PathBuf {
+ cargo_home().join(".crates2.json")
+}
+
+fn load_crates1() -> toml::Value {
+ toml::from_str(&fs::read_to_string(v1_path()).unwrap()).unwrap()
+}
+
+fn load_crates2() -> serde_json::Value {
+ serde_json::from_str(&fs::read_to_string(v2_path()).unwrap()).unwrap()
+}
+
+fn installed_exe(name: &str) -> PathBuf {
+ cargo_home().join("bin").join(exe(name))
+}
+
+/// Helper for executing binaries installed by cargo.
+fn installed_process(name: &str) -> Execs {
+ static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
+ thread_local!(static UNIQUE_ID: usize = NEXT_ID.fetch_add(1, Ordering::SeqCst));
+
+ // This copies the executable to a unique name so that it may be safely
+ // replaced on Windows. See Project::rename_run for details.
+ let src = installed_exe(name);
+ let dst = installed_exe(&UNIQUE_ID.with(|my_id| format!("{}-{}", name, my_id)));
+ // Note: Cannot use copy. On Linux, file descriptors may be left open to
+ // the executable as other tests in other threads are constantly spawning
+ // new processes (see https://github.com/rust-lang/cargo/pull/5557 for
+ // more).
+ fs::rename(&src, &dst)
+ .unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e));
+ // Leave behind a fake file so that reinstall duplicate check works.
+ fs::write(src, "").unwrap();
+ let p = process(dst);
+ execs().with_process_builder(p)
+}
+
+/// Check that the given package name/version has the following bins listed in
+/// the trackers. Also verifies that both trackers are in sync and valid.
+/// Pass in an empty `bins` list to assert that the package is *not* installed.
+fn validate_trackers(name: &str, version: &str, bins: &[&str]) {
+ let v1 = load_crates1();
+ let v1_table = v1.get("v1").unwrap().as_table().unwrap();
+ let v2 = load_crates2();
+ let v2_table = v2["installs"].as_object().unwrap();
+ assert_eq!(v1_table.len(), v2_table.len());
+ // Convert `bins` to a BTreeSet.
+ let bins: BTreeSet<String> = bins
+ .iter()
+ .map(|b| format!("{}{}", b, env::consts::EXE_SUFFIX))
+ .collect();
+ // Check every entry matches between v1 and v2.
+ for (pkg_id_str, v1_bins) in v1_table {
+ let pkg_id: PackageId = toml::Value::from(pkg_id_str.to_string())
+ .try_into()
+ .unwrap();
+ let v1_bins: BTreeSet<String> = v1_bins
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|b| b.as_str().unwrap().to_string())
+ .collect();
+ if pkg_id.name().as_str() == name && pkg_id.version().to_string() == version {
+ if bins.is_empty() {
+ panic!(
+ "Expected {} to not be installed, but found: {:?}",
+ name, v1_bins
+ );
+ } else {
+ assert_eq!(bins, v1_bins);
+ }
+ }
+ let pkg_id_value = serde_json::to_value(&pkg_id).unwrap();
+ let pkg_id_str = pkg_id_value.as_str().unwrap();
+ let v2_info = v2_table
+ .get(pkg_id_str)
+ .expect("v2 missing v1 pkg")
+ .as_object()
+ .unwrap();
+ let v2_bins = v2_info["bins"].as_array().unwrap();
+ let v2_bins: BTreeSet<String> = v2_bins
+ .iter()
+ .map(|b| b.as_str().unwrap().to_string())
+ .collect();
+ assert_eq!(v1_bins, v2_bins);
+ }
+}
+
+#[cargo_test]
+fn registry_upgrade() {
+ // Installing and upgrading from a registry.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v1.0.0 (registry [..])
+[INSTALLING] foo v1.0.0
+[COMPILING] foo v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v1.0.0` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+ installed_process("foo").with_stdout("1.0.0").run();
+ validate_trackers("foo", "1.0.0", &["foo"]);
+
+ cargo_process("install foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[IGNORED] package `foo v1.0.0` is already installed[..]
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+
+ pkg("foo", "1.0.1");
+
+ cargo_process("install foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v1.0.1 (registry [..])
+[INSTALLING] foo v1.0.1
+[COMPILING] foo v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v1.0.0` with `foo v1.0.1` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+
+ installed_process("foo").with_stdout("1.0.1").run();
+ validate_trackers("foo", "1.0.1", &["foo"]);
+
+ cargo_process("install foo --version=1.0.0")
+ .with_stderr_contains("[COMPILING] foo v1.0.0")
+ .run();
+ installed_process("foo").with_stdout("1.0.0").run();
+ validate_trackers("foo", "1.0.0", &["foo"]);
+
+ cargo_process("install foo --version=^1.0")
+ .with_stderr_contains("[COMPILING] foo v1.0.1")
+ .run();
+ installed_process("foo").with_stdout("1.0.1").run();
+ validate_trackers("foo", "1.0.1", &["foo"]);
+
+ cargo_process("install foo --version=^1.0")
+ .with_stderr_contains("[IGNORED] package `foo v1.0.1` is already installed[..]")
+ .run();
+}
+
+#[cargo_test]
+fn uninstall() {
+ // Basic uninstall test.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ cargo_process("uninstall foo").run();
+ let data = load_crates2();
+ assert_eq!(data["installs"].as_object().unwrap().len(), 0);
+ let v1_table = load_crates1();
+ assert_eq!(v1_table.get("v1").unwrap().as_table().unwrap().len(), 0);
+}
+
+#[cargo_test]
+fn upgrade_force() {
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ cargo_process("install foo --force")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[INSTALLING] foo v1.0.0
+[COMPILING] foo v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [..]/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v1.0.0` with `foo v1.0.0` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+ validate_trackers("foo", "1.0.0", &["foo"]);
+}
+
+#[cargo_test]
+fn ambiguous_version_no_longer_allowed() {
+ // Non-semver-requirement is not allowed for `--version`.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo --version=1.0")
+ .with_stderr(
+ "\
+[ERROR] the `--version` provided, `1.0`, is not a valid semver version: cannot parse '1.0' as a semver
+
+if you want to specify semver range, add an explicit qualifier, like ^1.0
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn path_is_always_dirty() {
+ // --path should always reinstall.
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("install --path .").run();
+ p.cargo("install --path .")
+ .with_stderr_contains("[REPLACING] [..]/foo[EXE]")
+ .run();
+}
+
+#[cargo_test]
+fn fails_for_conflicts_unknown() {
+ // If an untracked file is in the way, it should fail.
+ pkg("foo", "1.0.0");
+ let exe = installed_exe("foo");
+ exe.parent().unwrap().mkdir_p();
+ fs::write(exe, "").unwrap();
+ cargo_process("install foo")
+ .with_stderr_contains("[ERROR] binary `foo[EXE]` already exists in destination")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn fails_for_conflicts_known() {
+ // If the same binary exists in another package, it should fail.
+ pkg("foo", "1.0.0");
+ Package::new("bar", "1.0.0")
+ .file("src/bin/foo.rs", "fn main() {}")
+ .publish();
+ cargo_process("install foo").run();
+ cargo_process("install bar")
+ .with_stderr_contains(
+ "[ERROR] binary `foo[EXE]` already exists in destination as part of `foo v1.0.0`",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn supports_multiple_binary_names() {
+ // Can individually install with --bin or --example
+ Package::new("foo", "1.0.0")
+ .file("src/main.rs", r#"fn main() { println!("foo"); }"#)
+ .file("src/bin/a.rs", r#"fn main() { println!("a"); }"#)
+ .file("examples/ex1.rs", r#"fn main() { println!("ex1"); }"#)
+ .publish();
+ cargo_process("install foo --bin foo").run();
+ installed_process("foo").with_stdout("foo").run();
+ assert!(!installed_exe("a").exists());
+ assert!(!installed_exe("ex1").exists());
+ validate_trackers("foo", "1.0.0", &["foo"]);
+ cargo_process("install foo --bin a").run();
+ installed_process("a").with_stdout("a").run();
+ assert!(!installed_exe("ex1").exists());
+ validate_trackers("foo", "1.0.0", &["a", "foo"]);
+ cargo_process("install foo --example ex1").run();
+ installed_process("ex1").with_stdout("ex1").run();
+ validate_trackers("foo", "1.0.0", &["a", "ex1", "foo"]);
+ cargo_process("uninstall foo --bin foo").run();
+ assert!(!installed_exe("foo").exists());
+ assert!(installed_exe("ex1").exists());
+ validate_trackers("foo", "1.0.0", &["a", "ex1"]);
+ cargo_process("uninstall foo").run();
+ assert!(!installed_exe("ex1").exists());
+ assert!(!installed_exe("a").exists());
+}
+
+#[cargo_test]
+fn v1_already_installed_fresh() {
+ // Install with v1, then try to install again with v2.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ cargo_process("install foo")
+ .with_stderr_contains("[IGNORED] package `foo v1.0.0` is already installed[..]")
+ .run();
+}
+
+#[cargo_test]
+fn v1_already_installed_dirty() {
+ // Install with v1, then install a new version with v2.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ pkg("foo", "1.0.1");
+ cargo_process("install foo")
+ .with_stderr_contains("[COMPILING] foo v1.0.1")
+ .with_stderr_contains("[REPLACING] [..]/foo[EXE]")
+ .run();
+ validate_trackers("foo", "1.0.1", &["foo"]);
+}
+
+#[cargo_test]
+fn change_features_rebuilds() {
+ Package::new("foo", "1.0.0")
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature = "f1") {
+ println!("f1");
+ }
+ if cfg!(feature = "f2") {
+ println!("f2");
+ }
+ }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [features]
+ f1 = []
+ f2 = []
+ default = ["f1"]
+ "#,
+ )
+ .publish();
+ cargo_process("install foo").run();
+ installed_process("foo").with_stdout("f1").run();
+ cargo_process("install foo --no-default-features").run();
+ installed_process("foo").with_stdout("").run();
+ cargo_process("install foo --all-features").run();
+ installed_process("foo").with_stdout("f1\nf2").run();
+ cargo_process("install foo --no-default-features --features=f1").run();
+ installed_process("foo").with_stdout("f1").run();
+}
+
+#[cargo_test]
+fn change_profile_rebuilds() {
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ cargo_process("install foo --debug")
+ .with_stderr_contains("[COMPILING] foo v1.0.0")
+ .with_stderr_contains("[REPLACING] [..]foo[EXE]")
+ .run();
+ cargo_process("install foo --debug")
+ .with_stderr_contains("[IGNORED] package `foo v1.0.0` is already installed[..]")
+ .run();
+}
+
+#[cargo_test]
+fn change_target_rebuilds() {
+ if cross_compile::disabled() {
+ return;
+ }
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ let target = cross_compile::alternate();
+ cargo_process("install foo -v --target")
+ .arg(&target)
+ .with_stderr_contains("[COMPILING] foo v1.0.0")
+ .with_stderr_contains("[REPLACING] [..]foo[EXE]")
+ .with_stderr_contains(&format!("[..]--target {}[..]", target))
+ .run();
+}
+
+#[cargo_test]
+fn change_bin_sets_rebuilds() {
+ // Changing which bins in a multi-bin project should reinstall.
+ Package::new("foo", "1.0.0")
+ .file("src/main.rs", "fn main() { }")
+ .file("src/bin/x.rs", "fn main() { }")
+ .file("src/bin/y.rs", "fn main() { }")
+ .publish();
+ cargo_process("install foo --bin x").run();
+ assert!(installed_exe("x").exists());
+ assert!(!installed_exe("y").exists());
+ assert!(!installed_exe("foo").exists());
+ validate_trackers("foo", "1.0.0", &["x"]);
+ cargo_process("install foo --bin y")
+ .with_stderr_contains("[INSTALLED] package `foo v1.0.0` (executable `y[EXE]`)")
+ .run();
+ assert!(installed_exe("x").exists());
+ assert!(installed_exe("y").exists());
+ assert!(!installed_exe("foo").exists());
+ validate_trackers("foo", "1.0.0", &["x", "y"]);
+ cargo_process("install foo")
+ .with_stderr_contains("[INSTALLED] package `foo v1.0.0` (executable `foo[EXE]`)")
+ .with_stderr_contains(
+ "[REPLACED] package `foo v1.0.0` with `foo v1.0.0` (executables `x[EXE]`, `y[EXE]`)",
+ )
+ .run();
+ assert!(installed_exe("x").exists());
+ assert!(installed_exe("y").exists());
+ assert!(installed_exe("foo").exists());
+ validate_trackers("foo", "1.0.0", &["foo", "x", "y"]);
+}
+
+#[cargo_test]
+fn forwards_compatible() {
+ // Unknown fields should be preserved.
+ pkg("foo", "1.0.0");
+ pkg("bar", "1.0.0");
+ cargo_process("install foo").run();
+ let key = "foo 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)";
+ let v2 = cargo_home().join(".crates2.json");
+ let mut data = load_crates2();
+ data["newfield"] = serde_json::Value::Bool(true);
+ data["installs"][key]["moreinfo"] = serde_json::Value::String("shazam".to_string());
+ fs::write(&v2, serde_json::to_string(&data).unwrap()).unwrap();
+ cargo_process("install bar").run();
+ let data: serde_json::Value = serde_json::from_str(&fs::read_to_string(&v2).unwrap()).unwrap();
+ assert_eq!(data["newfield"].as_bool().unwrap(), true);
+ assert_eq!(
+ data["installs"][key]["moreinfo"].as_str().unwrap(),
+ "shazam"
+ );
+}
+
+#[cargo_test]
+fn v2_syncs() {
+ // V2 inherits the installs from V1.
+ pkg("one", "1.0.0");
+ pkg("two", "1.0.0");
+ pkg("three", "1.0.0");
+ let p = project()
+ .file("src/bin/x.rs", "fn main() {}")
+ .file("src/bin/y.rs", "fn main() {}")
+ .build();
+ cargo_process("install one").run();
+ validate_trackers("one", "1.0.0", &["one"]);
+ p.cargo("install --path .").run();
+ validate_trackers("foo", "1.0.0", &["x", "y"]);
+ // v1 add/remove
+ cargo_process("install two").run();
+ cargo_process("uninstall one").run();
+ // This should pick up that `two` was added, `one` was removed.
+ cargo_process("install three").run();
+ validate_trackers("three", "1.0.0", &["three"]);
+ cargo_process("install --list")
+ .with_stdout(
+ "\
+foo v0.0.1 ([..]/foo):
+ x[EXE]
+ y[EXE]
+three v1.0.0:
+ three[EXE]
+two v1.0.0:
+ two[EXE]
+",
+ )
+ .run();
+ cargo_process("install one").run();
+ installed_process("one").with_stdout("1.0.0").run();
+ validate_trackers("one", "1.0.0", &["one"]);
+ cargo_process("install two")
+ .with_stderr_contains("[IGNORED] package `two v1.0.0` is already installed[..]")
+ .run();
+ // v1 remove
+ p.cargo("uninstall --bin x").run();
+ pkg("x", "1.0.0");
+ pkg("y", "1.0.0");
+ // This should succeed because `x` was removed in V1.
+ cargo_process("install x").run();
+ validate_trackers("x", "1.0.0", &["x"]);
+ // This should fail because `y` still exists in a different package.
+ cargo_process("install y")
+ .with_stderr_contains(
+ "[ERROR] binary `y[EXE]` already exists in destination \
+ as part of `foo v0.0.1 ([..])`",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn upgrade_git() {
+ let git_project = git::new("foo", |project| project.file("src/main.rs", "fn main() {}"));
+ // install
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .run();
+ // Check install stays fresh.
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .with_stderr_contains(
+ "[IGNORED] package `foo v0.0.1 (file://[..]/foo#[..])` is \
+ already installed,[..]",
+ )
+ .run();
+ // Modify a file.
+ let repo = git2::Repository::open(git_project.root()).unwrap();
+ git_project.change_file("src/main.rs", r#"fn main() {println!("onomatopoeia");}"#);
+ git::add(&repo);
+ git::commit(&repo);
+ // Install should reinstall.
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .with_stderr_contains("[COMPILING] foo v0.0.1 ([..])")
+ .with_stderr_contains("[REPLACING] [..]/foo[EXE]")
+ .run();
+ installed_process("foo").with_stdout("onomatopoeia").run();
+ // Check install stays fresh.
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .with_stderr_contains(
+ "[IGNORED] package `foo v0.0.1 (file://[..]/foo#[..])` is \
+ already installed,[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn switch_sources() {
+ // Installing what appears to be the same thing, but from different
+ // sources should reinstall.
+ registry::alt_init();
+ pkg("foo", "1.0.0");
+ Package::new("foo", "1.0.0")
+ .file("src/main.rs", r#"fn main() { println!("alt"); }"#)
+ .alternative(true)
+ .publish();
+ let p = project()
+ .at("foo-local") // so it doesn't use the same directory as the git project
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", r#"fn main() { println!("local"); }"#)
+ .build();
+ let git_project = git::new("foo", |project| {
+ project.file("src/main.rs", r#"fn main() { println!("git"); }"#)
+ });
+
+ cargo_process("install foo").run();
+ installed_process("foo").with_stdout("1.0.0").run();
+ cargo_process("install foo --registry alternative").run();
+ installed_process("foo").with_stdout("alt").run();
+ p.cargo("install --path .").run();
+ installed_process("foo").with_stdout("local").run();
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .run();
+ installed_process("foo").with_stdout("git").run();
+}
+
+#[cargo_test]
+fn multiple_report() {
+ // Testing the full output that indicates installed/ignored/replaced/summary.
+ pkg("one", "1.0.0");
+ pkg("two", "1.0.0");
+ fn three(vers: &str) {
+ Package::new("three", vers)
+ .file("src/main.rs", "fn main() { }")
+ .file("src/bin/x.rs", "fn main() { }")
+ .file("src/bin/y.rs", "fn main() { }")
+ .publish();
+ }
+ three("1.0.0");
+ cargo_process("install one two three")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] one v1.0.0 (registry `[..]`)
+[DOWNLOADING] crates ...
+[DOWNLOADED] two v1.0.0 (registry `[..]`)
+[DOWNLOADING] crates ...
+[DOWNLOADED] three v1.0.0 (registry `[..]`)
+[INSTALLING] one v1.0.0
+[COMPILING] one v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/one[EXE]
+[INSTALLED] package `one v1.0.0` (executable `one[EXE]`)
+[INSTALLING] two v1.0.0
+[COMPILING] two v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/two[EXE]
+[INSTALLED] package `two v1.0.0` (executable `two[EXE]`)
+[INSTALLING] three v1.0.0
+[COMPILING] three v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/three[EXE]
+[INSTALLING] [..]/.cargo/bin/x[EXE]
+[INSTALLING] [..]/.cargo/bin/y[EXE]
+[INSTALLED] package `three v1.0.0` (executables `three[EXE]`, `x[EXE]`, `y[EXE]`)
+[SUMMARY] Successfully installed one, two, three!
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+ pkg("foo", "1.0.1");
+ pkg("bar", "1.0.1");
+ three("1.0.1");
+ cargo_process("install one two three")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[IGNORED] package `one v1.0.0` is already installed, use --force to override
+[IGNORED] package `two v1.0.0` is already installed, use --force to override
+[DOWNLOADING] crates ...
+[DOWNLOADED] three v1.0.1 (registry `[..]`)
+[INSTALLING] three v1.0.1
+[COMPILING] three v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [..]/.cargo/bin/three[EXE]
+[REPLACING] [..]/.cargo/bin/x[EXE]
+[REPLACING] [..]/.cargo/bin/y[EXE]
+[REPLACED] package `three v1.0.0` with `three v1.0.1` (executables `three[EXE]`, `x[EXE]`, `y[EXE]`)
+[SUMMARY] Successfully installed one, two, three!
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+ cargo_process("uninstall three")
+ .with_stderr(
+ "\
+[REMOVING] [..]/.cargo/bin/three[EXE]
+[REMOVING] [..]/.cargo/bin/x[EXE]
+[REMOVING] [..]/.cargo/bin/y[EXE]
+",
+ )
+ .run();
+ cargo_process("install three --bin x")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[INSTALLING] three v1.0.1
+[COMPILING] three v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/x[EXE]
+[INSTALLED] package `three v1.0.1` (executable `x[EXE]`)
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+ cargo_process("install three")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[INSTALLING] three v1.0.1
+[COMPILING] three v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/three[EXE]
+[INSTALLING] [..]/.cargo/bin/y[EXE]
+[REPLACING] [..]/.cargo/bin/x[EXE]
+[INSTALLED] package `three v1.0.1` (executables `three[EXE]`, `y[EXE]`)
+[REPLACED] package `three v1.0.1` with `three v1.0.1` (executable `x[EXE]`)
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_track() {
+ pkg("foo", "1.0.0");
+ cargo_process("install --no-track foo").run();
+ assert!(!v1_path().exists());
+ assert!(!v2_path().exists());
+ cargo_process("install --no-track foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[ERROR] binary `foo[EXE]` already exists in destination `[..]/.cargo/bin/foo[EXE]`
+Add --force to overwrite
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn deletes_orphaned() {
+ // When an executable is removed from a project, upgrading should remove it.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("src/bin/other.rs", "fn main() {}")
+ .file("examples/ex1.rs", "fn main() {}")
+ .build();
+ p.cargo("install --path . --bins --examples").run();
+ assert!(installed_exe("other").exists());
+
+ // Remove a binary, add a new one, and bump the version.
+ fs::remove_file(p.root().join("src/bin/other.rs")).unwrap();
+ p.change_file("examples/ex2.rs", "fn main() {}");
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.2.0"
+ "#,
+ );
+ p.cargo("install --path . --bins --examples")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.2.0 [..]
+[COMPILING] foo v0.2.0 [..]
+[FINISHED] release [..]
+[INSTALLING] [..]/.cargo/bin/ex2[EXE]
+[REPLACING] [..]/.cargo/bin/ex1[EXE]
+[REPLACING] [..]/.cargo/bin/foo[EXE]
+[REMOVING] executable `[..]/.cargo/bin/other[EXE]` from previous version foo v0.1.0 [..]
+[INSTALLED] package `foo v0.2.0 [..]` (executable `ex2[EXE]`)
+[REPLACED] package `foo v0.1.0 [..]` with `foo v0.2.0 [..]` (executables `ex1[EXE]`, `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+ assert!(!installed_exe("other").exists());
+ validate_trackers("foo", "0.2.0", &["foo", "ex1", "ex2"]);
+ // 0.1.0 should not have any entries.
+ validate_trackers("foo", "0.1.0", &[]);
+}
+
+#[cargo_test]
+fn already_installed_exact_does_not_update() {
+ pkg("foo", "1.0.0");
+ cargo_process("install foo --version=1.0.0").run();
+ cargo_process("install foo --version=1.0.0")
+ .with_stderr(
+ "\
+[IGNORED] package `foo v1.0.0` is already installed[..]
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+
+ cargo_process("install foo --version=>=1.0.0")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[IGNORED] package `foo v1.0.0` is already installed[..]
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+ pkg("foo", "1.0.1");
+ cargo_process("install foo --version=>=1.0.0")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v1.0.1 (registry [..])
+[INSTALLING] foo v1.0.1
+[COMPILING] foo v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v1.0.0` with `foo v1.0.1` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn already_installed_updates_yank_status_on_upgrade() {
+ pkg("foo", "1.0.0");
+ pkg_maybe_yanked("foo", "1.0.1", true);
+ cargo_process("install foo --version=1.0.0").run();
+
+ cargo_process("install foo --version=1.0.1")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[ERROR] cannot install package `foo`, it has been yanked from registry `crates-io`
+",
+ )
+ .run();
+
+ pkg_maybe_yanked("foo", "1.0.1", false);
+
+ pkg("foo", "1.0.1");
+ cargo_process("install foo --version=1.0.1")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v1.0.1 (registry [..])
+[INSTALLING] foo v1.0.1
+[COMPILING] foo v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v1.0.0` with `foo v1.0.1` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn partially_already_installed_does_one_update() {
+ pkg("foo", "1.0.0");
+ cargo_process("install foo --version=1.0.0").run();
+ pkg("bar", "1.0.0");
+ pkg("baz", "1.0.0");
+ cargo_process("install foo bar baz --version=1.0.0")
+ .with_stderr(
+ "\
+[IGNORED] package `foo v1.0.0` is already installed[..]
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 (registry [..])
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v1.0.0 (registry [..])
+[INSTALLING] bar v1.0.0
+[COMPILING] bar v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/bar[EXE]
+[INSTALLED] package `bar v1.0.0` (executable `bar[EXE]`)
+[INSTALLING] baz v1.0.0
+[COMPILING] baz v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/baz[EXE]
+[INSTALLED] package `baz v1.0.0` (executable `baz[EXE]`)
+[SUMMARY] Successfully installed foo, bar, baz!
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}