| //! Tests for last-use tracking and auto-gc. |
| //! |
| //! Cargo supports an environment variable called `__CARGO_TEST_LAST_USE_NOW` |
| //! to have cargo pretend that the current time is the given time (in seconds |
| //! since the unix epoch). This is used throughout these tests to simulate |
| //! what happens when time passes. The [`days_ago_unix`] and |
| //! [`months_ago_unix`] functions help with setting this value. |
| |
| use super::config::GlobalContextBuilder; |
| use cargo::core::global_cache_tracker::{self, DeferredGlobalLastUse, GlobalCacheTracker}; |
| use cargo::util::cache_lock::CacheLockMode; |
| use cargo::util::interning::InternedString; |
| use cargo::GlobalContext; |
| use cargo_test_support::paths::{self, CargoPathExt}; |
| use cargo_test_support::registry::{Package, RegistryBuilder}; |
| use cargo_test_support::{ |
| basic_manifest, cargo_process, execs, git, process, project, retry, sleep_ms, |
| thread_wait_timeout, Execs, Project, |
| }; |
| use itertools::Itertools; |
| use std::fmt::Write; |
| use std::path::Path; |
| use std::path::PathBuf; |
| use std::process::Stdio; |
| use std::time::{Duration, SystemTime}; |
| |
| /// Helper to create a simple `foo` project which depends on a registry |
| /// dependency called `bar`. |
| fn basic_foo_bar_project() -> Project { |
| Package::new("bar", "1.0.0").publish(); |
| project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| bar = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build() |
| } |
| |
| /// Helper to get the names of files in a directory as strings. |
| fn get_names(glob: &str) -> Vec<String> { |
| let mut names: Vec<_> = glob::glob(paths::home().join(glob).to_str().unwrap()) |
| .unwrap() |
| .map(|p| p.unwrap().file_name().unwrap().to_str().unwrap().to_owned()) |
| .collect(); |
| names.sort(); |
| names |
| } |
| |
| fn get_registry_names(which: &str) -> Vec<String> { |
| get_names(&format!(".cargo/registry/{which}/*/*")) |
| } |
| |
| fn get_index_names() -> Vec<String> { |
| get_names(&format!(".cargo/registry/index/*")) |
| } |
| |
| fn get_git_db_names() -> Vec<String> { |
| get_names(&format!(".cargo/git/db/*")) |
| } |
| |
| fn get_git_checkout_names(db_name: &str) -> Vec<String> { |
| get_names(&format!(".cargo/git/checkouts/{db_name}/*")) |
| } |
| |
| fn days_ago(n: u64) -> SystemTime { |
| SystemTime::now() - Duration::from_secs(60 * 60 * 24 * n) |
| } |
| |
| /// Helper for simulating running cargo in the past. Use with the |
| /// __CARGO_TEST_LAST_USE_NOW environment variable. |
| fn days_ago_unix(n: u64) -> String { |
| days_ago(n) |
| .duration_since(SystemTime::UNIX_EPOCH) |
| .unwrap() |
| .as_secs() |
| .to_string() |
| } |
| |
| /// Helper for simulating running cargo in the past. Use with the |
| /// __CARGO_TEST_LAST_USE_NOW environment variable. |
| fn months_ago_unix(n: u64) -> String { |
| days_ago_unix(n * 30) |
| } |
| |
| /// Populates last-use database and the cache files. |
| /// |
| /// This makes it easier to more accurately specify exact sizes. Creating |
| /// specific sizes with `Package` is too difficult. |
| fn populate_cache( |
| gctx: &GlobalContext, |
| test_crates: &[(&str, u64, u64, u64)], |
| ) -> (PathBuf, PathBuf) { |
| let cache_dir = paths::home().join(".cargo/registry/cache/example.com-a6c4a5adcb232b9a"); |
| let src_dir = paths::home().join(".cargo/registry/src/example.com-a6c4a5adcb232b9a"); |
| |
| GlobalCacheTracker::db_path(&gctx) |
| .into_path_unlocked() |
| .rm_rf(); |
| |
| let _lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let mut tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| let mut deferred = DeferredGlobalLastUse::new(); |
| |
| cache_dir.rm_rf(); |
| cache_dir.mkdir_p(); |
| src_dir.rm_rf(); |
| src_dir.mkdir_p(); |
| paths::home() |
| .join(".cargo/registry/index/example.com-a6c4a5adcb232b9a") |
| .mkdir_p(); |
| let mut create = |name: &str, age, crate_size: u64, src_size: u64| { |
| let crate_filename = InternedString::new(&format!("{name}.crate")); |
| deferred.mark_registry_crate_used_stamp( |
| global_cache_tracker::RegistryCrate { |
| encoded_registry_name: "example.com-a6c4a5adcb232b9a".into(), |
| crate_filename, |
| size: crate_size, |
| }, |
| Some(&days_ago(age)), |
| ); |
| deferred.mark_registry_src_used_stamp( |
| global_cache_tracker::RegistrySrc { |
| encoded_registry_name: "example.com-a6c4a5adcb232b9a".into(), |
| package_dir: name.into(), |
| size: Some(src_size), |
| }, |
| Some(&days_ago(age)), |
| ); |
| std::fs::write( |
| cache_dir.join(crate_filename), |
| "x".repeat(crate_size as usize), |
| ) |
| .unwrap(); |
| let path = src_dir.join(name); |
| path.mkdir_p(); |
| std::fs::write(path.join("data"), "x".repeat(src_size as usize)).unwrap() |
| }; |
| |
| for (name, age, crate_size, src_size) in test_crates { |
| create(name, *age, *crate_size, *src_size); |
| } |
| deferred.save(&mut tracker).unwrap(); |
| |
| (cache_dir, src_dir) |
| } |
| |
| fn rustup_cargo() -> Execs { |
| // Get the path to the rustup cargo wrapper. This is necessary because |
| // cargo adds the "deps" directory into PATH on Windows, which points to |
| // the wrong cargo. |
| let rustup_cargo = Path::new(&std::env::var_os("CARGO_HOME").unwrap()).join("bin/cargo"); |
| execs().with_process_builder(process(rustup_cargo)) |
| } |
| |
| #[cargo_test] |
| fn auto_gc_gated() { |
| // Requires -Zgc to run auto-gc. |
| let p = basic_foo_bar_project(); |
| p.cargo("check") |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| // Check that it created a database. |
| let gctx = GlobalContextBuilder::new().build(); |
| assert!(GlobalCacheTracker::db_path(&gctx) |
| .into_path_unlocked() |
| .exists()); |
| assert_eq!(get_index_names().len(), 1); |
| |
| // Again in the future, shouldn't auto-gc. |
| p.cargo("check").run(); |
| assert_eq!(get_index_names().len(), 1); |
| } |
| |
| #[cargo_test] |
| fn clean_gc_gated() { |
| cargo_process("clean gc") |
| .with_status(101) |
| .with_stderr( |
| "\ |
| error: the `cargo clean gc` command is unstable, and only available on the \ |
| nightly channel of Cargo, but this is the `stable` channel |
| See [..] |
| See [..] |
| ", |
| ) |
| .run(); |
| } |
| |
| #[cargo_test] |
| fn implies_source() { |
| // Checks that when a src, crate, or checkout is marked as used, the |
| // corresponding index or git db also gets marked as used. |
| let gctx = GlobalContextBuilder::new().build(); |
| let _lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let mut deferred = DeferredGlobalLastUse::new(); |
| let mut tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| |
| deferred.mark_registry_crate_used(global_cache_tracker::RegistryCrate { |
| encoded_registry_name: "example.com-a6c4a5adcb232b9a".into(), |
| crate_filename: "regex-1.8.4.crate".into(), |
| size: 123, |
| }); |
| deferred.mark_registry_src_used(global_cache_tracker::RegistrySrc { |
| encoded_registry_name: "index.crates.io-6f17d22bba15001f".into(), |
| package_dir: "rand-0.8.5".into(), |
| size: None, |
| }); |
| deferred.mark_git_checkout_used(global_cache_tracker::GitCheckout { |
| encoded_git_name: "cargo-e7ff1db891893a9e".into(), |
| short_name: "f0a4ee0".into(), |
| size: None, |
| }); |
| deferred.save(&mut tracker).unwrap(); |
| |
| let mut indexes = tracker.registry_index_all().unwrap(); |
| assert_eq!(indexes.len(), 2); |
| indexes.sort_by(|a, b| a.0.encoded_registry_name.cmp(&b.0.encoded_registry_name)); |
| assert_eq!( |
| indexes[0].0.encoded_registry_name, |
| "example.com-a6c4a5adcb232b9a" |
| ); |
| assert_eq!( |
| indexes[1].0.encoded_registry_name, |
| "index.crates.io-6f17d22bba15001f" |
| ); |
| |
| let dbs = tracker.git_db_all().unwrap(); |
| assert_eq!(dbs.len(), 1); |
| assert_eq!(dbs[0].0.encoded_git_name, "cargo-e7ff1db891893a9e"); |
| } |
| |
| #[cargo_test] |
| fn auto_gc_defaults() { |
| // Checks that the auto-gc deletes old entries, and leaves new ones intact. |
| Package::new("old", "1.0.0").publish(); |
| Package::new("new", "1.0.0").publish(); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| old = "1.0" |
| new = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| // Populate the last-use data. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| assert_eq!(get_registry_names("src"), ["new-1.0.0", "old-1.0.0"]); |
| assert_eq!( |
| get_registry_names("cache"), |
| ["new-1.0.0.crate", "old-1.0.0.crate"] |
| ); |
| |
| // Run again with just one package. Make sure the old src gets deleted, |
| // but .crate does not. |
| p.change_file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| new = "1.0" |
| "#, |
| ); |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(2)) |
| .run(); |
| assert_eq!(get_registry_names("src"), ["new-1.0.0"]); |
| assert_eq!( |
| get_registry_names("cache"), |
| ["new-1.0.0.crate", "old-1.0.0.crate"] |
| ); |
| |
| // Run again after the .crate should have aged out. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| assert_eq!(get_registry_names("src"), ["new-1.0.0"]); |
| assert_eq!(get_registry_names("cache"), ["new-1.0.0.crate"]); |
| } |
| |
| #[cargo_test] |
| fn auto_gc_config() { |
| // Can configure auto gc settings. |
| Package::new("old", "1.0.0").publish(); |
| Package::new("new", "1.0.0").publish(); |
| let p = project() |
| .file( |
| ".cargo/config.toml", |
| r#" |
| [gc.auto] |
| frequency = "always" |
| max-src-age = "1 day" |
| max-crate-age = "3 days" |
| max-index-age = "3 days" |
| max-git-co-age = "1 day" |
| max-git-db-age = "3 days" |
| "#, |
| ) |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| old = "1.0" |
| new = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| // Populate the last-use data. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", days_ago_unix(4)) |
| .run(); |
| assert_eq!(get_registry_names("src"), ["new-1.0.0", "old-1.0.0"]); |
| assert_eq!( |
| get_registry_names("cache"), |
| ["new-1.0.0.crate", "old-1.0.0.crate"] |
| ); |
| |
| // Run again with just one package. Make sure the old src gets deleted, |
| // but .crate does not. |
| p.change_file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| new = "1.0" |
| "#, |
| ); |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", days_ago_unix(2)) |
| .run(); |
| assert_eq!(get_registry_names("src"), ["new-1.0.0"]); |
| assert_eq!( |
| get_registry_names("cache"), |
| ["new-1.0.0.crate", "old-1.0.0.crate"] |
| ); |
| |
| // Run again after the .crate should have aged out. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| assert_eq!(get_registry_names("src"), ["new-1.0.0"]); |
| assert_eq!(get_registry_names("cache"), ["new-1.0.0.crate"]); |
| } |
| |
| #[cargo_test] |
| fn frequency() { |
| // gc.auto.frequency settings |
| let p = basic_foo_bar_project(); |
| p.change_file( |
| ".cargo/config.toml", |
| r#" |
| [gc.auto] |
| frequency = "never" |
| "#, |
| ); |
| // Populate data in the past. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| assert_eq!(get_index_names().len(), 1); |
| assert_eq!(get_registry_names("src"), ["bar-1.0.0"]); |
| assert_eq!(get_registry_names("cache"), ["bar-1.0.0.crate"]); |
| |
| p.change_file("Cargo.toml", &basic_manifest("foo", "0.2.0")); |
| |
| // Try after the default expiration time, with "never" it shouldn't gc. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| assert_eq!(get_index_names().len(), 1); |
| assert_eq!(get_registry_names("src"), ["bar-1.0.0"]); |
| assert_eq!(get_registry_names("cache"), ["bar-1.0.0.crate"]); |
| |
| // Try again with a setting that allows it to run. |
| p.cargo("check -Zgc") |
| .env("CARGO_GC_AUTO_FREQUENCY", "1 day") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| assert_eq!(get_index_names().len(), 0); |
| assert_eq!(get_registry_names("src").len(), 0); |
| assert_eq!(get_registry_names("cache").len(), 0); |
| } |
| |
| #[cargo_test] |
| fn auto_gc_index() { |
| // Deletes the index if it hasn't been used in a while. |
| let p = basic_foo_bar_project(); |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| assert_eq!(get_index_names().len(), 1); |
| |
| // Make sure it stays within the time frame. |
| p.change_file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| "#, |
| ); |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(2)) |
| .run(); |
| assert_eq!(get_index_names().len(), 1); |
| |
| // After it expires, it should be deleted. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| assert_eq!(get_index_names().len(), 0); |
| } |
| |
| #[cargo_test] |
| fn auto_gc_git() { |
| // auto-gc should delete git checkouts and dbs. |
| |
| // Returns the short git name of a checkout. |
| let short_id = |repo: &git2::Repository| -> String { |
| let head = repo.revparse_single("HEAD").unwrap(); |
| let short_id = head.short_id().unwrap(); |
| short_id.as_str().unwrap().to_owned() |
| }; |
| |
| // Set up a git dependency and fetch it and populate the database, |
| // 6 months in the past. |
| let (git_project, git_repo) = git::new_repo("bar", |p| { |
| p.file("Cargo.toml", &basic_manifest("bar", "1.0.0")) |
| .file("src/lib.rs", "") |
| }); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| &format!( |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| bar = {{ git = '{}' }} |
| "#, |
| git_project.url() |
| ), |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(6)) |
| .run(); |
| let db_names = get_git_db_names(); |
| assert_eq!(db_names.len(), 1); |
| let first_short_oid = short_id(&git_repo); |
| assert_eq!( |
| get_git_checkout_names(&db_names[0]), |
| [first_short_oid.clone()] |
| ); |
| |
| // Use a new git checkout, should keep both. |
| git_project.change_file("src/lib.rs", "// modified"); |
| git::add(&git_repo); |
| git::commit(&git_repo); |
| p.cargo("update -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(6)) |
| .run(); |
| assert_eq!(get_git_db_names().len(), 1); |
| let second_short_oid = short_id(&git_repo); |
| let mut both = vec![first_short_oid, second_short_oid.clone()]; |
| both.sort(); |
| assert_eq!(get_git_checkout_names(&db_names[0]), both); |
| |
| // In the future, using the second checkout should delete the first. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| assert_eq!(get_git_db_names().len(), 1); |
| assert_eq!( |
| get_git_checkout_names(&db_names[0]), |
| [second_short_oid.clone()] |
| ); |
| |
| // After three months, the db should get deleted. |
| p.change_file("Cargo.toml", &basic_manifest("foo", "0.2.0")); |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| assert_eq!(get_git_db_names().len(), 0); |
| assert_eq!(get_git_checkout_names(&db_names[0]).len(), 0); |
| } |
| |
| #[cargo_test] |
| fn auto_gc_various_commands() { |
| // Checks that auto gc works with a variety of commands. |
| // |
| // Auto-gc is only run on a subset of commands. Generally it is run on |
| // commands that are already doing a lot of work, or heavily involve the |
| // use of the registry. |
| Package::new("bar", "1.0.0").publish(); |
| let cmds = ["check", "fetch"]; |
| for cmd in cmds { |
| eprintln!("checking command {cmd}"); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| bar = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| // Populate the last-use data. |
| p.cargo(cmd) |
| .arg("-Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| let gctx = GlobalContextBuilder::new().build(); |
| let lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| let indexes = tracker.registry_index_all().unwrap(); |
| assert_eq!(indexes.len(), 1); |
| let crates = tracker.registry_crate_all().unwrap(); |
| assert_eq!(crates.len(), 1); |
| let srcs = tracker.registry_src_all().unwrap(); |
| assert_eq!(srcs.len(), 1); |
| drop(lock); |
| |
| // After everything is aged out, it should all be deleted. |
| p.change_file("Cargo.toml", &basic_manifest("foo", "0.2.0")); |
| p.cargo(cmd) |
| .arg("-Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| let lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let indexes = tracker.registry_index_all().unwrap(); |
| assert_eq!(indexes.len(), 0); |
| let crates = tracker.registry_crate_all().unwrap(); |
| assert_eq!(crates.len(), 0); |
| let srcs = tracker.registry_src_all().unwrap(); |
| assert_eq!(srcs.len(), 0); |
| drop(tracker); |
| drop(lock); |
| paths::home().join(".cargo/registry").rm_rf(); |
| GlobalCacheTracker::db_path(&gctx) |
| .into_path_unlocked() |
| .rm_rf(); |
| } |
| } |
| |
| #[cargo_test] |
| fn updates_last_use_various_commands() { |
| // Checks that last-use tracking is updated by various commands. |
| // |
| // Not *all* commands update the index tracking, even though they |
| // technically involve reading the index. There isn't a convenient place |
| // to ensure it gets saved while avoiding saving too often in other |
| // commands. For the most part, this should be fine, since these commands |
| // usually aren't run without running one of the commands that does save |
| // the tracking. Some of the commands are: |
| // |
| // - login, owner, yank, search |
| // - report future-incompatibilities |
| // - package --no-verify |
| // - fetch --locked |
| Package::new("bar", "1.0.0").publish(); |
| let cmds = [ |
| // name, expected_crates (0=doesn't download) |
| ("check", 1), |
| ("fetch", 1), |
| ("tree", 1), |
| ("generate-lockfile", 0), |
| ("update", 0), |
| ("metadata", 1), |
| ("vendor --respect-source-config", 1), |
| ]; |
| for (cmd, expected_crates) in cmds { |
| eprintln!("checking command {cmd}"); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| bar = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| // Populate the last-use data. |
| p.cargo(cmd) |
| .arg("-Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| let gctx = GlobalContextBuilder::new().build(); |
| let lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| let indexes = tracker.registry_index_all().unwrap(); |
| assert_eq!(indexes.len(), 1); |
| let crates = tracker.registry_crate_all().unwrap(); |
| assert_eq!(crates.len(), expected_crates); |
| let srcs = tracker.registry_src_all().unwrap(); |
| assert_eq!(srcs.len(), expected_crates); |
| drop(tracker); |
| drop(lock); |
| paths::home().join(".cargo/registry").rm_rf(); |
| GlobalCacheTracker::db_path(&gctx) |
| .into_path_unlocked() |
| .rm_rf(); |
| } |
| } |
| |
| #[cargo_test] |
| fn both_git_and_http_index_cleans() { |
| // Checks that either the git or http index cache gets cleaned. |
| let _crates_io = RegistryBuilder::new().build(); |
| let _alternative = RegistryBuilder::new().alternative().http_index().build(); |
| Package::new("from_git", "1.0.0").publish(); |
| Package::new("from_http", "1.0.0") |
| .alternative(true) |
| .publish(); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| from_git = "1.0" |
| from_http = { version = "1.0", registry = "alternative" } |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| |
| p.cargo("update -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| let gctx = GlobalContextBuilder::new().build(); |
| let lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| let indexes = tracker.registry_index_all().unwrap(); |
| assert_eq!(indexes.len(), 2); |
| assert_eq!(get_index_names().len(), 2); |
| drop(lock); |
| |
| // Running in the future without these indexes should delete them. |
| p.change_file("Cargo.toml", &basic_manifest("foo", "0.2.0")); |
| p.cargo("clean gc -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| let lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let indexes = tracker.registry_index_all().unwrap(); |
| assert_eq!(indexes.len(), 0); |
| assert_eq!(get_index_names().len(), 0); |
| drop(lock); |
| } |
| |
| #[cargo_test] |
| fn clean_gc_dry_run() { |
| // Basic `clean --gc --dry-run` test. |
| let p = basic_foo_bar_project(); |
| // Populate the last-use data. |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| |
| let registry_root = paths::home().join(".cargo/registry"); |
| let glob_registry = |name| -> PathBuf { |
| let mut paths: Vec<_> = glob::glob(registry_root.join(name).join("*").to_str().unwrap()) |
| .unwrap() |
| .map(|p| p.unwrap()) |
| .collect(); |
| assert_eq!(paths.len(), 1); |
| paths.pop().unwrap() |
| }; |
| let index = glob_registry("index").ls_r(); |
| let src = glob_registry("src").ls_r(); |
| let cache = glob_registry("cache").ls_r(); |
| let expected_files = index |
| .iter() |
| .chain(src.iter()) |
| .chain(cache.iter()) |
| .map(|p| p.to_str().unwrap()) |
| .join("\n"); |
| |
| p.cargo("clean gc --dry-run -v -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stdout_unordered(&expected_files) |
| .with_stderr( |
| "[SUMMARY] [..] files, [..] total\n\ |
| [WARNING] no files deleted due to --dry-run", |
| ) |
| .run(); |
| |
| // Again, make sure the information is still tracked. |
| p.cargo("clean gc --dry-run -v -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stdout_unordered(&expected_files) |
| .with_stderr( |
| "[SUMMARY] [..] files, [..] total\n\ |
| [WARNING] no files deleted due to --dry-run", |
| ) |
| .run(); |
| } |
| |
| #[cargo_test] |
| fn clean_default_gc() { |
| // `clean gc` without options should also gc |
| let p = basic_foo_bar_project(); |
| // Populate the last-use data. |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| p.cargo("clean gc -v -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr_unordered( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/registry/index/[..] |
| [REMOVING] [ROOT]/home/.cargo/registry/src/[..] |
| [REMOVING] [ROOT]/home/.cargo/registry/cache/[..] |
| [REMOVED] [..] files, [..] total |
| ", |
| ) |
| .run(); |
| } |
| |
| #[cargo_test] |
| fn tracks_sizes() { |
| // Checks that sizes are properly tracked in the db. |
| Package::new("dep1", "1.0.0") |
| .file("src/lib.rs", "") |
| .publish(); |
| Package::new("dep2", "1.0.0") |
| .file("src/lib.rs", "") |
| .file("data", &"abcdefghijklmnopqrstuvwxyz".repeat(1000)) |
| .publish(); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| dep1 = "1.0" |
| dep2 = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| |
| // Check that the crate sizes are the same as on disk. |
| let gctx = GlobalContextBuilder::new().build(); |
| let _lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| let mut crates = tracker.registry_crate_all().unwrap(); |
| crates.sort_by(|a, b| a.0.crate_filename.cmp(&b.0.crate_filename)); |
| let db_sizes: Vec<_> = crates.iter().map(|c| c.0.size).collect(); |
| |
| let mut actual: Vec<_> = p |
| .glob(paths::home().join(".cargo/registry/cache/*/*")) |
| .map(|p| p.unwrap()) |
| .collect(); |
| actual.sort(); |
| let actual_sizes: Vec<_> = actual |
| .iter() |
| .map(|path| std::fs::metadata(path).unwrap().len()) |
| .collect(); |
| assert_eq!(db_sizes, actual_sizes); |
| |
| // Also check the src sizes are computed. |
| let mut srcs = tracker.registry_src_all().unwrap(); |
| srcs.sort_by(|a, b| a.0.package_dir.cmp(&b.0.package_dir)); |
| let db_sizes: Vec<_> = srcs.iter().map(|c| c.0.size.unwrap()).collect(); |
| let mut actual: Vec<_> = p |
| .glob(paths::home().join(".cargo/registry/src/*/*")) |
| .map(|p| p.unwrap()) |
| .collect(); |
| actual.sort(); |
| // .cargo-ok is not tracked in the size. |
| actual.iter().for_each(|p| p.join(".cargo-ok").rm_rf()); |
| let actual_sizes: Vec<_> = actual |
| .iter() |
| .map(|path| cargo_util::du(path, &[]).unwrap()) |
| .collect(); |
| assert_eq!(db_sizes, actual_sizes); |
| assert!(db_sizes[1] > 26000); |
| } |
| |
| #[cargo_test] |
| fn max_size() { |
| // Checks --max-crate-size and --max-src-size with various cleaning thresholds. |
| let gctx = GlobalContextBuilder::new().build(); |
| |
| let test_crates = [ |
| // name, age, crate_size, src_size |
| ("a-1.0.0", 5, 1, 1), |
| ("b-1.0.0", 6, 2, 2), |
| ("c-1.0.0", 3, 3, 3), |
| ("d-1.0.0", 2, 4, 4), |
| ("e-1.0.0", 2, 5, 5), |
| ("f-1.0.0", 9, 6, 6), |
| ("g-1.0.0", 1, 1, 1), |
| ]; |
| |
| // Determine the order things get deleted so they can be verified. |
| let mut names_by_timestamp: Vec<_> = test_crates |
| .iter() |
| .map(|(name, age, _, _)| (days_ago_unix(*age), name)) |
| .collect(); |
| names_by_timestamp.sort(); |
| let names_by_timestamp: Vec<_> = names_by_timestamp |
| .into_iter() |
| .map(|(_, name)| name) |
| .collect(); |
| |
| // This exercises the different boundary conditions. |
| for (clean_size, files, bytes) in [ |
| (22, 0, 0), |
| (21, 1, 6), |
| (16, 1, 6), |
| (15, 2, 8), |
| (14, 2, 8), |
| (13, 3, 9), |
| (12, 4, 12), |
| (10, 4, 12), |
| (9, 5, 16), |
| (6, 5, 16), |
| (5, 6, 21), |
| (1, 6, 21), |
| (0, 7, 22), |
| ] { |
| let (removed, kept) = names_by_timestamp.split_at(files); |
| // --max-crate-size |
| let (cache_dir, src_dir) = populate_cache(&gctx, &test_crates); |
| let mut stderr = String::new(); |
| for name in removed { |
| writeln!(stderr, "[REMOVING] [..]{name}.crate").unwrap(); |
| } |
| let total_display = if removed.is_empty() { |
| String::new() |
| } else { |
| format!(", {bytes}B total") |
| }; |
| let files_display = if files == 1 { |
| format!("1 file") |
| } else { |
| format!("{files} files") |
| }; |
| write!(stderr, "[REMOVED] {files_display}{total_display}").unwrap(); |
| cargo_process(&format!("clean gc -Zgc -v --max-crate-size={clean_size}")) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr_unordered(&stderr) |
| .run(); |
| for name in kept { |
| assert!(cache_dir.join(format!("{name}.crate")).exists()); |
| } |
| for name in removed { |
| assert!(!cache_dir.join(format!("{name}.crate")).exists()); |
| } |
| |
| // --max-src-size |
| populate_cache(&gctx, &test_crates); |
| let mut stderr = String::new(); |
| for name in removed { |
| writeln!(stderr, "[REMOVING] [..]{name}").unwrap(); |
| } |
| let total_display = if files == 0 { |
| String::new() |
| } else { |
| format!(", {bytes}B total") |
| }; |
| write!(stderr, "[REMOVED] {files_display}{total_display}").unwrap(); |
| cargo_process(&format!("clean gc -Zgc -v --max-src-size={clean_size}")) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr_unordered(&stderr) |
| .run(); |
| for name in kept { |
| assert!(src_dir.join(name).exists()); |
| } |
| for name in removed { |
| assert!(!src_dir.join(name).exists()); |
| } |
| } |
| } |
| |
| #[cargo_test] |
| fn max_size_untracked_crate() { |
| // When a .crate file exists from an older version of cargo that did not |
| // track sizes, `clean --max-crate-size` should populate the db with the |
| // sizes. |
| let gctx = GlobalContextBuilder::new().build(); |
| let cache = paths::home().join(".cargo/registry/cache/example.com-a6c4a5adcb232b9a"); |
| cache.mkdir_p(); |
| paths::home() |
| .join(".cargo/registry/index/example.com-a6c4a5adcb232b9a") |
| .mkdir_p(); |
| // Create the `.crate files. |
| let test_crates = [ |
| // name, size |
| ("a-1.0.0.crate", 1234), |
| ("b-1.0.0.crate", 42), |
| ("c-1.0.0.crate", 0), |
| ]; |
| for (name, size) in test_crates { |
| std::fs::write(cache.join(name), "x".repeat(size as usize)).unwrap() |
| } |
| // This should scan the directory and populate the db with the size information. |
| cargo_process("clean gc -Zgc -v --max-crate-size=100000") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr("[REMOVED] 0 files") |
| .run(); |
| // Check that it stored the size data. |
| let _lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| let crates = tracker.registry_crate_all().unwrap(); |
| let mut actual: Vec<_> = crates |
| .iter() |
| .map(|(rc, _time)| (rc.crate_filename.as_str(), rc.size)) |
| .collect(); |
| actual.sort(); |
| assert_eq!(test_crates, actual.as_slice()); |
| } |
| |
| /// Helper to prepare the max-size test. |
| fn max_size_untracked_prepare() -> (GlobalContext, Project) { |
| // First, publish and download a dependency. |
| let p = basic_foo_bar_project(); |
| p.cargo("fetch").run(); |
| // Pretend it was an older version that did not track last-use. |
| let gctx = GlobalContextBuilder::new().build(); |
| GlobalCacheTracker::db_path(&gctx) |
| .into_path_unlocked() |
| .rm_rf(); |
| (gctx, p) |
| } |
| |
| /// Helper to verify the max-size test. |
| fn max_size_untracked_verify(gctx: &GlobalContext) { |
| let actual: Vec<_> = glob::glob( |
| paths::home() |
| .join(".cargo/registry/src/*/*") |
| .to_str() |
| .unwrap(), |
| ) |
| .unwrap() |
| .map(|p| p.unwrap()) |
| .collect(); |
| assert_eq!(actual.len(), 1); |
| let actual_size = cargo_util::du(&actual[0], &[]).unwrap(); |
| let lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| let srcs = tracker.registry_src_all().unwrap(); |
| assert_eq!(srcs.len(), 1); |
| assert_eq!(srcs[0].0.size, Some(actual_size)); |
| drop(lock); |
| } |
| |
| #[cargo_test] |
| fn max_size_untracked_src_from_use() { |
| // When a src directory exists from an older version of cargo that did not |
| // track sizes, doing a build should populate the db with an entry with an |
| // unknown size. `clean --max-src-size` should then fix the size. |
| let (gctx, p) = max_size_untracked_prepare(); |
| |
| // Run a command that will update the db with an unknown src size. |
| p.cargo("tree -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| // Check that it is None. |
| let lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| let srcs = tracker.registry_src_all().unwrap(); |
| assert_eq!(srcs.len(), 1); |
| assert_eq!(srcs[0].0.size, None); |
| drop(lock); |
| |
| // Fix the size. |
| p.cargo("clean gc -v --max-src-size=10000 -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr("[REMOVED] 0 files") |
| .run(); |
| max_size_untracked_verify(&gctx); |
| } |
| |
| #[cargo_test] |
| fn max_size_untracked_src_from_clean() { |
| // When a src directory exists from an older version of cargo that did not |
| // track sizes, `clean --max-src-size` should populate the db with the |
| // sizes. |
| let (gctx, p) = max_size_untracked_prepare(); |
| |
| // Clean should scan the src and update the db. |
| p.cargo("clean gc -v --max-src-size=10000 -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr("[REMOVED] 0 files") |
| .run(); |
| max_size_untracked_verify(&gctx); |
| } |
| |
| #[cargo_test] |
| fn max_download_size() { |
| // --max-download-size |
| // |
| // This creates some sample crates of specific sizes, and then tries |
| // deleting at various specific size thresholds that exercise different |
| // edge conditions. |
| let gctx = GlobalContextBuilder::new().build(); |
| |
| let test_crates = [ |
| // name, age, crate_size, src_size |
| ("d-1.0.0", 4, 4, 5), |
| ("c-1.0.0", 3, 3, 3), |
| ("a-1.0.0", 1, 2, 5), |
| ("b-1.0.0", 1, 1, 7), |
| ]; |
| |
| for (max_size, num_deleted, files_deleted, bytes) in [ |
| (30, 0, 0, 0), |
| (29, 1, 1, 5), |
| (24, 2, 2, 9), |
| (20, 3, 3, 12), |
| (1, 7, 7, 29), |
| (0, 8, 8, 30), |
| ] { |
| populate_cache(&gctx, &test_crates); |
| // Determine the order things will be deleted. |
| let delete_order: Vec<String> = test_crates |
| .iter() |
| .flat_map(|(name, _, _, _)| [name.to_string(), format!("{name}.crate")]) |
| .collect(); |
| let (removed, _kept) = delete_order.split_at(num_deleted); |
| let mut stderr = String::new(); |
| for name in removed { |
| writeln!(stderr, "[REMOVING] [..]{name}").unwrap(); |
| } |
| let files_display = if files_deleted == 1 { |
| format!("1 file") |
| } else { |
| format!("{files_deleted} files") |
| }; |
| let total_display = if removed.is_empty() { |
| String::new() |
| } else { |
| format!(", {bytes}B total") |
| }; |
| write!(stderr, "[REMOVED] {files_display}{total_display}",).unwrap(); |
| cargo_process(&format!("clean gc -Zgc -v --max-download-size={max_size}")) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr_unordered(&stderr) |
| .run(); |
| } |
| } |
| |
| #[cargo_test] |
| fn package_cache_lock_during_build() { |
| // Verifies that a shared lock is held during a build. Resolution and |
| // downloads should be OK while that is held, but mutation should block. |
| // |
| // This works by launching a build with a build script that will pause. |
| // Then it performs other cargo commands and verifies their behavior. |
| Package::new("bar", "1.0.0").publish(); |
| let p_foo = project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| bar = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .file( |
| "build.rs", |
| r#" |
| fn main() { |
| std::fs::write("blocking", "").unwrap(); |
| let path = std::path::Path::new("ready"); |
| loop { |
| if path.exists() { |
| break; |
| } else { |
| std::thread::sleep(std::time::Duration::from_millis(100)) |
| } |
| } |
| } |
| "#, |
| ) |
| .build(); |
| let p_foo2 = project() |
| .at("foo2") |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo2" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| bar = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| |
| // Start a build that will pause once the build starts. |
| let mut foo_child = p_foo |
| .cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .build_command() |
| .stdout(Stdio::piped()) |
| .stderr(Stdio::piped()) |
| .spawn() |
| .unwrap(); |
| |
| // Wait for it to enter build script. |
| retry(100, || p_foo.root().join("blocking").exists().then_some(())); |
| |
| // Start a build with a different target directory. It should not block, |
| // even though it gets a download lock, and then a shared lock. |
| // |
| // Also verify that auto-gc gets disabled. |
| p_foo2 |
| .cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("CARGO_GC_AUTO_FREQUENCY", "always") |
| .env("CARGO_LOG", "gc=debug") |
| .with_stderr_contains("[UPDATING] `dummy-registry` index") |
| .with_stderr_contains("[CHECKING] bar v1.0.0") |
| .with_stderr_contains("[CHECKING] foo2 v0.1.0 [..]") |
| .with_stderr_contains("[FINISHED] [..]") |
| .with_stderr_contains("[..]unable to acquire mutate lock, auto gc disabled") |
| .run(); |
| |
| // Ensure that the first build really blocked. |
| assert!(matches!(foo_child.try_wait(), Ok(None))); |
| |
| // Cleaning while a command is running should block. |
| let mut clean_cmd = p_foo2 |
| .cargo("clean gc --max-download-size=0 -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .build_command(); |
| clean_cmd.stderr(Stdio::piped()); |
| let mut clean_child = clean_cmd.spawn().unwrap(); |
| |
| // Give the clean command a chance to finish (it shouldn't). |
| sleep_ms(500); |
| // They should both still be running. |
| assert!(matches!(foo_child.try_wait(), Ok(None))); |
| assert!(matches!(clean_child.try_wait(), Ok(None))); |
| |
| // Let the original build finish. |
| p_foo.change_file("ready", ""); |
| |
| // Wait for clean to finish. |
| let thread = std::thread::spawn(|| clean_child.wait_with_output().unwrap()); |
| let output = thread_wait_timeout(100, thread); |
| assert!(output.status.success()); |
| // Validate the output of the clean. |
| execs() |
| .with_stderr( |
| "\ |
| [BLOCKING] waiting for file lock on package cache mutation |
| [REMOVED] [..] |
| ", |
| ) |
| .run_output(&output); |
| } |
| |
| #[cargo_test] |
| fn read_only_locking_auto_gc() { |
| // Tests the behavior for auto-gc on a read-only directory. |
| let p = basic_foo_bar_project(); |
| // Populate cache. |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| let cargo_home = paths::home().join(".cargo"); |
| let mut perms = std::fs::metadata(&cargo_home).unwrap().permissions(); |
| // Test when it can't update auto-gc db. |
| perms.set_readonly(true); |
| std::fs::set_permissions(&cargo_home, perms.clone()).unwrap(); |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [CHECKING] bar v1.0.0 |
| [CHECKING] foo v0.1.0 [..] |
| [FINISHED] [..] |
| ", |
| ) |
| .run(); |
| // Try again without the last-use existing (such as if the cache was |
| // populated by an older version of cargo). |
| perms.set_readonly(false); |
| std::fs::set_permissions(&cargo_home, perms.clone()).unwrap(); |
| let gctx = GlobalContextBuilder::new().build(); |
| GlobalCacheTracker::db_path(&gctx) |
| .into_path_unlocked() |
| .rm_rf(); |
| perms.set_readonly(true); |
| std::fs::set_permissions(&cargo_home, perms.clone()).unwrap(); |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr("[FINISHED] [..]") |
| .run(); |
| perms.set_readonly(false); |
| std::fs::set_permissions(&cargo_home, perms).unwrap(); |
| } |
| |
| #[cargo_test] |
| fn delete_index_also_deletes_crates() { |
| // Checks that when an index is delete that src and cache directories also get deleted. |
| let p = basic_foo_bar_project(); |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| |
| assert_eq!(get_registry_names("src"), ["bar-1.0.0"]); |
| assert_eq!(get_registry_names("cache"), ["bar-1.0.0.crate"]); |
| |
| p.cargo("clean gc") |
| .arg("--max-index-age=0 days") |
| .arg("-Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr("[REMOVED] [..]") |
| .run(); |
| |
| assert_eq!(get_registry_names("src").len(), 0); |
| assert_eq!(get_registry_names("cache").len(), 0); |
| } |
| |
| #[cargo_test] |
| fn clean_syncs_missing_files() { |
| // When files go missing in the cache, clean operations that need to track |
| // the size should also remove them from the database. |
| Package::new("bar", "1.0.0").publish(); |
| Package::new("baz", "1.0.0").publish(); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| bar = "1.0" |
| baz = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| |
| // Verify things are tracked. |
| let gctx = GlobalContextBuilder::new().build(); |
| let lock = gctx |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let tracker = GlobalCacheTracker::new(&gctx).unwrap(); |
| let crates = tracker.registry_crate_all().unwrap(); |
| assert_eq!(crates.len(), 2); |
| let srcs = tracker.registry_src_all().unwrap(); |
| assert_eq!(srcs.len(), 2); |
| drop(lock); |
| |
| // Remove the files. |
| for pattern in [ |
| ".cargo/registry/cache/*/bar-1.0.0.crate", |
| ".cargo/registry/src/*/bar-1.0.0", |
| ] { |
| p.glob(paths::home().join(pattern)) |
| .map(|p| p.unwrap()) |
| .next() |
| .unwrap() |
| .rm_rf(); |
| } |
| |
| // Clean should update the db. |
| p.cargo("clean gc -v --max-download-size=1GB -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr("[REMOVED] 0 files") |
| .run(); |
| |
| // Verify |
| let crates = tracker.registry_crate_all().unwrap(); |
| assert_eq!(crates.len(), 1); |
| let srcs = tracker.registry_src_all().unwrap(); |
| assert_eq!(srcs.len(), 1); |
| } |
| |
| #[cargo_test] |
| fn offline_doesnt_auto_gc() { |
| // When running offline, auto-gc shouldn't run. |
| let p = basic_foo_bar_project(); |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| // Remove the dependency. |
| p.change_file("Cargo.toml", &basic_manifest("foo", "0.1.0")); |
| // Run offline, make sure it doesn't delete anything |
| p.cargo("check --offline -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr("[CHECKING] foo v0.1.0[..]\n[FINISHED][..]") |
| .run(); |
| assert_eq!(get_registry_names("src"), ["bar-1.0.0"]); |
| assert_eq!(get_registry_names("cache"), ["bar-1.0.0.crate"]); |
| // Run online, make sure auto-gc runs. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr("[FINISHED][..]") |
| .run(); |
| assert_eq!(get_registry_names("src"), &[] as &[String]); |
| assert_eq!(get_registry_names("cache"), &[] as &[String]); |
| } |
| |
| #[cargo_test] |
| fn can_handle_future_schema() -> anyhow::Result<()> { |
| // It should work when a future version of cargo has made schema changes |
| // to the database. |
| let p = basic_foo_bar_project(); |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| // Modify the schema to pretend this is done by a future version of cargo. |
| let gctx = GlobalContextBuilder::new().build(); |
| let db_path = GlobalCacheTracker::db_path(&gctx).into_path_unlocked(); |
| let conn = rusqlite::Connection::open(&db_path)?; |
| let user_version: u32 = |
| conn.query_row("SELECT user_version FROM pragma_user_version", [], |row| { |
| row.get(0) |
| })?; |
| conn.execute("ALTER TABLE global_data ADD COLUMN foo DEFAULT 123", [])?; |
| conn.pragma_update(None, "user_version", &(user_version + 1))?; |
| drop(conn); |
| // Verify it doesn't blow up. |
| p.cargo("clean gc --max-download-size=0 -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr("[REMOVED] 4 files, [..] total") |
| .run(); |
| Ok(()) |
| } |
| |
| #[cargo_test] |
| fn clean_max_git_age() { |
| // --max-git-*-age flags |
| let (git_a, git_a_repo) = git::new_repo("git_a", |p| { |
| p.file("Cargo.toml", &basic_manifest("git_a", "1.0.0")) |
| .file("src/lib.rs", "") |
| }); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| &format!( |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| git_a = {{ git = '{}' }} |
| "#, |
| git_a.url() |
| ), |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| // Populate last-use tracking. |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", days_ago_unix(4)) |
| .run(); |
| // Update git_a to create a separate checkout. |
| git_a.change_file("src/lib.rs", "// test"); |
| git::add(&git_a_repo); |
| git::commit(&git_a_repo); |
| // Update last-use tracking, where the first git checkout will stay "old". |
| p.cargo("update -p git_a -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", days_ago_unix(2)) |
| .with_stderr( |
| "\ |
| [UPDATING] git repository [..] |
| [UPDATING] git_a v1.0.0 [..] |
| ", |
| ) |
| .run(); |
| |
| let db_names = get_git_db_names(); |
| assert_eq!(db_names.len(), 1); |
| let db_name = &db_names[0]; |
| let co_names = get_git_checkout_names(&db_name); |
| assert_eq!(co_names.len(), 2); |
| |
| // Delete the first checkout |
| p.cargo("clean gc -v -Zgc") |
| .arg("--max-git-co-age=3 days") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/git_a-[..]/[..] |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| |
| let db_names = get_git_db_names(); |
| assert_eq!(db_names.len(), 1); |
| let co_names = get_git_checkout_names(&db_name); |
| assert_eq!(co_names.len(), 1); |
| |
| // delete the second checkout |
| p.cargo("clean gc -v -Zgc") |
| .arg("--max-git-co-age=0 days") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/git_a-[..]/[..] |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| |
| let db_names = get_git_db_names(); |
| assert_eq!(db_names.len(), 1); |
| let co_names = get_git_checkout_names(&db_name); |
| assert_eq!(co_names.len(), 0); |
| |
| // delete the db |
| p.cargo("clean gc -v -Zgc") |
| .arg("--max-git-db-age=1 days") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/db/git_a-[..] |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/git_a-[..] |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| |
| let db_names = get_git_db_names(); |
| assert_eq!(db_names.len(), 0); |
| let co_names = get_git_checkout_names(&db_name); |
| assert_eq!(co_names.len(), 0); |
| } |
| |
| #[cargo_test] |
| fn clean_max_src_crate_age() { |
| // --max-src-age and --max-crate-age flags |
| let p = basic_foo_bar_project(); |
| // Populate last-use tracking. |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", days_ago_unix(4)) |
| .run(); |
| // Update bar to create a separate copy with a different timestamp. |
| Package::new("bar", "1.0.1").publish(); |
| p.cargo("update -p bar -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", days_ago_unix(2)) |
| .with_stderr( |
| "\ |
| [UPDATING] `dummy-registry` index |
| [UPDATING] bar v1.0.0 -> v1.0.1 |
| ", |
| ) |
| .run(); |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", days_ago_unix(2)) |
| .with_stderr( |
| "\ |
| [DOWNLOADING] crates ... |
| [DOWNLOADED] bar v1.0.1 [..] |
| ", |
| ) |
| .run(); |
| |
| assert_eq!(get_registry_names("src"), ["bar-1.0.0", "bar-1.0.1"]); |
| assert_eq!( |
| get_registry_names("cache"), |
| ["bar-1.0.0.crate", "bar-1.0.1.crate"] |
| ); |
| |
| // Delete the old src. |
| p.cargo("clean gc -v -Zgc") |
| .arg("--max-src-age=3 days") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [..]/bar-1.0.0 |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| |
| // delete the second src |
| p.cargo("clean gc -v -Zgc") |
| .arg("--max-src-age=0 days") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [..]/bar-1.0.1 |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| |
| // delete the old crate |
| p.cargo("clean gc -v -Zgc") |
| .arg("--max-crate-age=3 days") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [..]/bar-1.0.0.crate |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| |
| // delete the seecond crate |
| p.cargo("clean gc -v -Zgc") |
| .arg("--max-crate-age=0 days") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [..]/bar-1.0.1.crate |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| } |
| |
| #[cargo_test] |
| fn clean_max_git_size() { |
| // clean --max-git-size |
| // |
| // Creates two checkouts. The sets a size threshold to delete one. And |
| // then with 0 max size to delete everything. |
| let (git_project, git_repo) = git::new_repo("bar", |p| { |
| p.file("Cargo.toml", &basic_manifest("bar", "1.0.0")) |
| .file("src/lib.rs", "") |
| }); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| &format!( |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| bar = {{ git = '{}' }} |
| "#, |
| git_project.url() |
| ), |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| // Fetch and populate db. |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", days_ago_unix(3)) |
| .run(); |
| |
| // Figure out the name of the first checkout. |
| let git_root = paths::home().join(".cargo/git"); |
| let db_names = get_git_db_names(); |
| assert_eq!(db_names.len(), 1); |
| let db_name = &db_names[0]; |
| let co_names = get_git_checkout_names(&db_name); |
| assert_eq!(co_names.len(), 1); |
| let first_co_name = &co_names[0]; |
| |
| // Make an update and create a new checkout. |
| git_project.change_file("src/lib.rs", "// modified"); |
| git::add(&git_repo); |
| git::commit(&git_repo); |
| p.cargo("update -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| // Use a different time so that the first checkout timestamp is less |
| // than the second. |
| .env("__CARGO_TEST_LAST_USE_NOW", days_ago_unix(2)) |
| .run(); |
| |
| // Figure out the threshold to use. |
| let mut co_names = get_git_checkout_names(&db_name); |
| assert_eq!(co_names.len(), 2); |
| co_names.retain(|name| name != first_co_name); |
| assert_eq!(co_names.len(), 1); |
| let second_co_name = &co_names[0]; |
| let second_co_path = git_root |
| .join("checkouts") |
| .join(db_name) |
| .join(second_co_name); |
| let second_co_size = cargo_util::du(&second_co_path, &["!.git"]).unwrap(); |
| |
| let db_size = cargo_util::du(&git_root.join("db").join(db_name), &[]).unwrap(); |
| |
| let threshold = db_size + second_co_size; |
| |
| p.cargo(&format!("clean gc --max-git-size={threshold} -Zgc -v")) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr(&format!( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/{db_name}/{first_co_name} |
| [REMOVED] [..] |
| " |
| )) |
| .run(); |
| |
| // And then try cleaning everything. |
| p.cargo("clean gc --max-git-size=0 -Zgc -v") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr_unordered(&format!( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/{db_name}/{second_co_name} |
| [REMOVING] [ROOT]/home/.cargo/git/db/{db_name} |
| [REMOVED] [..] |
| " |
| )) |
| .run(); |
| } |
| |
| // Helper for setting up fake git sizes for git size cleaning. |
| fn setup_fake_git_sizes(db_name: &str, db_size: usize, co_sizes: &[usize]) { |
| let base_git = paths::home().join(".cargo/git"); |
| let db_path = base_git.join("db").join(db_name); |
| db_path.mkdir_p(); |
| std::fs::write(db_path.join("test"), "x".repeat(db_size)).unwrap(); |
| let base_co = base_git.join("checkouts").join(db_name); |
| for (i, size) in co_sizes.iter().enumerate() { |
| let co_name = format!("co{i}"); |
| let co_path = base_co.join(co_name); |
| co_path.mkdir_p(); |
| std::fs::write(co_path.join("test"), "x".repeat(*size)).unwrap(); |
| } |
| } |
| |
| #[cargo_test] |
| fn clean_max_git_size_untracked() { |
| // If there are git directories that aren't tracked in the database, |
| // `--max-git-size` should pick it up. |
| // |
| // The db_name of "example" depends on the sorting order of the names ("e" |
| // should be after "c"), so that the db comes after the checkouts. |
| setup_fake_git_sizes("example", 5000, &[1000, 2000]); |
| cargo_process(&format!("clean gc -Zgc -v --max-git-size=7000")) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/example/co0 |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| cargo_process(&format!("clean gc -Zgc -v --max-git-size=5000")) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/example/co1 |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| cargo_process(&format!("clean gc -Zgc -v --max-git-size=0")) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/db/example |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| } |
| |
| #[cargo_test] |
| fn clean_max_git_size_deletes_co_from_db() { |
| // In the scenario where it thinks it needs to delete the db, it should |
| // also delete all the checkouts. |
| // |
| // The db_name of "abc" depends on the sorting order of the names ("a" |
| // should be before "c"), so that the db comes before the checkouts. |
| setup_fake_git_sizes("abc", 5000, &[1000, 2000]); |
| // This deletes everything because it tries to delete the db, which then |
| // deletes all checkouts. |
| cargo_process(&format!("clean gc -Zgc -v --max-git-size=3000")) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/db/abc |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/abc/co1 |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/abc/co0 |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| } |
| |
| #[cargo_test] |
| fn handles_missing_index() { |
| // Checks behavior when index is missing. |
| let p = basic_foo_bar_project(); |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| paths::home().join(".cargo/registry/index").rm_rf(); |
| cargo_process("clean gc -v --max-download-size=0 -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr_unordered( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/registry/cache/[..] |
| [REMOVING] [ROOT]/home/.cargo/registry/src/[..] |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| } |
| |
| #[cargo_test] |
| fn handles_missing_git_db() { |
| // Checks behavior when git db is missing. |
| let git_project = git::new("bar", |p| { |
| p.file("Cargo.toml", &basic_manifest("bar", "1.0.0")) |
| .file("src/lib.rs", "") |
| }); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| &format!( |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| bar = {{ git = '{}' }} |
| "#, |
| git_project.url() |
| ), |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| paths::home().join(".cargo/git/db").rm_rf(); |
| cargo_process("clean gc -v --max-git-size=0 -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stderr( |
| "\ |
| [REMOVING] [ROOT]/home/.cargo/git/checkouts/[..] |
| [REMOVED] [..] |
| ", |
| ) |
| .run(); |
| } |
| |
| #[cargo_test] |
| fn clean_gc_quiet_is_quiet() { |
| // Checks that --quiet works with `cargo clean gc`, since there was a |
| // subtle issue with how the flag is defined as a global flag. |
| let p = basic_foo_bar_project(); |
| p.cargo("fetch -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| p.cargo("clean gc --quiet -Zgc --dry-run") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stdout("") |
| .with_stderr("") |
| .run(); |
| // Verify exact same command without -q would actually display something. |
| p.cargo("clean gc -Zgc --dry-run") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .with_stdout("") |
| .with_stderr( |
| "\ |
| [SUMMARY] [..] files, [..] total |
| [WARNING] no files deleted due to --dry-run |
| ", |
| ) |
| .run(); |
| } |
| |
| #[cargo_test(requires_rustup_stable)] |
| fn compatible_with_older_cargo() { |
| // Ensures that db stays backwards compatible across versions. |
| |
| // T-4 months: Current version, build the database. |
| Package::new("old", "1.0.0").publish(); |
| Package::new("middle", "1.0.0").publish(); |
| Package::new("new", "1.0.0").publish(); |
| let p = project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| old = "1.0" |
| middle = "1.0" |
| new = "1.0" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| // Populate the last-use data. |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) |
| .run(); |
| assert_eq!( |
| get_registry_names("src"), |
| ["middle-1.0.0", "new-1.0.0", "old-1.0.0"] |
| ); |
| assert_eq!( |
| get_registry_names("cache"), |
| ["middle-1.0.0.crate", "new-1.0.0.crate", "old-1.0.0.crate"] |
| ); |
| |
| // T-2 months: Stable version, make sure it reads and deletes old src. |
| p.change_file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| new = "1.0" |
| middle = "1.0" |
| "#, |
| ); |
| rustup_cargo() |
| .args(&["+stable", "check", "-Zgc"]) |
| .cwd(p.root()) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(2)) |
| .run(); |
| assert_eq!(get_registry_names("src"), ["middle-1.0.0", "new-1.0.0"]); |
| assert_eq!( |
| get_registry_names("cache"), |
| ["middle-1.0.0.crate", "new-1.0.0.crate", "old-1.0.0.crate"] |
| ); |
| |
| // T-0 months: Current version, make sure it can read data from stable, |
| // deletes old crate and middle src. |
| p.change_file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| version = "0.1.0" |
| edition = "2015" |
| |
| [dependencies] |
| new = "1.0" |
| "#, |
| ); |
| p.cargo("check -Zgc") |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| assert_eq!(get_registry_names("src"), ["new-1.0.0"]); |
| assert_eq!( |
| get_registry_names("cache"), |
| ["middle-1.0.0.crate", "new-1.0.0.crate"] |
| ); |
| } |
| |
| #[cargo_test(requires_rustup_stable)] |
| fn forward_compatible() { |
| // Checks that db created in an older version can be read in a newer version. |
| Package::new("bar", "1.0.0").publish(); |
| let git_project = git::new("from_git", |p| { |
| p.file("Cargo.toml", &basic_manifest("from_git", "1.0.0")) |
| .file("src/lib.rs", "") |
| }); |
| |
| let p = project() |
| .file( |
| "Cargo.toml", |
| &format!( |
| r#" |
| [package] |
| name = "foo" |
| |
| [dependencies] |
| bar = "1.0.0" |
| from_git = {{ git = '{}' }} |
| "#, |
| git_project.url() |
| ), |
| ) |
| .file("src/lib.rs", "") |
| .build(); |
| |
| rustup_cargo() |
| .args(&["+stable", "check", "-Zgc"]) |
| .cwd(p.root()) |
| .masquerade_as_nightly_cargo(&["gc"]) |
| .run(); |
| |
| let config = GlobalContextBuilder::new().build(); |
| let lock = config |
| .acquire_package_cache_lock(CacheLockMode::MutateExclusive) |
| .unwrap(); |
| let tracker = GlobalCacheTracker::new(&config).unwrap(); |
| // Don't want to check the actual index name here, since although the |
| // names are semi-stable, they might change over long periods of time. |
| let indexes = tracker.registry_index_all().unwrap(); |
| assert_eq!(indexes.len(), 1); |
| let crates = tracker.registry_crate_all().unwrap(); |
| let names: Vec<_> = crates |
| .iter() |
| .map(|(krate, _timestamp)| krate.crate_filename) |
| .collect(); |
| assert_eq!(names, &["bar-1.0.0.crate"]); |
| let srcs = tracker.registry_src_all().unwrap(); |
| let names: Vec<_> = srcs |
| .iter() |
| .map(|(src, _timestamp)| src.package_dir) |
| .collect(); |
| assert_eq!(names, &["bar-1.0.0"]); |
| let dbs: Vec<_> = tracker.git_db_all().unwrap(); |
| assert_eq!(dbs.len(), 1); |
| let cos: Vec<_> = tracker.git_checkout_all().unwrap(); |
| assert_eq!(cos.len(), 1); |
| drop(lock); |
| } |