cargo/tests/testsuite/install_upgrade.rs
Ed Page 320c279f43 Port cargo from toml-rs to toml_edit
Benefits:
- A TOML 1.0 compliant parser
- Unblock future work
  - Have `cargo init` add the current crate to the workspace, rather
    than error
  - #5586: Upstream `cargo-add`
2022-01-13 09:27:27 -06:00

863 lines
28 KiB
Rust

//! 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 toml_edit::easy as toml;
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 `--vers` 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();
}