use clap::Parser;
use midenup::{commands::Midenup, paths, state::PublicationRef};
mod common;
use common::*;
#[test]
fn integration_gc_removes_orphans_and_never_touches_referenced_publications() {
let _guard = common::harness::mutating_test_guard();
let test_env = environment_setup("integration_gc");
let fixture = common::harness::OfflineFixture::create(test_env.tmp_dir.path(), "0.15.0");
let (mut state, config) = test_setup(&test_env, &fixture.manifest_uri);
let channel = semver::Version::new(0, 15, 0);
Midenup::try_parse_from(["midenup", "install", "0.15.0"])
.unwrap()
.execute_with_state(&config, &mut state)
.expect("failed to install");
let PublicationRef::Managed { id, .. } = &state.get(&channel).unwrap().publication else {
panic!("expected a managed publication");
};
let replaced = paths::publication_dir(&test_env.midenup_home, &channel, id);
Midenup::try_parse_from(["midenup", "install", "0.15.0", "--profile", "complete"])
.unwrap()
.execute_with_state(&config, &mut state)
.expect("failed to republish");
let PublicationRef::Managed { id, .. } = &state.get(&channel).unwrap().publication else {
panic!("expected a managed publication");
};
let live = paths::publication_dir(&test_env.midenup_home, &channel, id);
assert!(replaced.is_dir(), "the replaced publication is what gc exists to reclaim");
let orphan = paths::publications_dir(&test_env.midenup_home).join("0.15.0-orphaned");
std::fs::create_dir_all(&orphan).unwrap();
Midenup::try_parse_from(["midenup", "gc"])
.unwrap()
.execute_with_state(&config, &mut state)
.expect("gc failed");
assert!(!replaced.exists(), "the replaced publication must be reclaimed");
assert!(!orphan.exists(), "an unrecorded publication must be reclaimed");
assert!(live.is_dir(), "the referenced publication must survive");
assert!(
live.join("bin").join("miden-vm").exists(),
"and must survive intact, not merely as a directory"
);
Midenup::try_parse_from(["midenup", "gc"])
.unwrap()
.execute_with_state(&config, &mut state)
.expect("gc must be idempotent");
assert!(live.is_dir());
let toolchain = paths::toolchain_link(&test_env.midenup_home, &channel);
assert!(toolchain.join("opt").join("miden vm").symlink_metadata().is_ok());
}
#[test]
fn integration_gc_reclaims_cargo_caches_without_publication_orphans() {
let test_env = environment_setup("integration_gc_cargo_cache");
let fixture = common::harness::OfflineFixture::create(test_env.tmp_dir.path(), "0.15.0");
let (mut state, config) = test_setup(&test_env, &fixture.manifest_uri);
let orphan = paths::cargo_build_cache(&test_env.midenup_home).join("a".repeat(64));
std::fs::create_dir_all(&orphan).unwrap();
std::fs::write(orphan.join("artifact"), "obsolete build output").unwrap();
Midenup::try_parse_from(["midenup", "gc"])
.unwrap()
.execute_with_state(&config, &mut state)
.expect("gc failed");
assert!(!orphan.exists(), "gc must reclaim caches even without publication orphans");
}
#[test]
fn integration_gc_retains_shared_and_pending_cargo_caches_and_preserves_user_data() {
use midenup::{
channel::UserChannel,
identity::CustomToolchain,
publish::journal::{self, JournalEntry},
state::PublicationId,
version::Authority,
};
let test_env = environment_setup("integration_gc_shared_cache");
let fixture = common::harness::OfflineFixture::create(test_env.tmp_dir.path(), "0.15.0");
let (mut state, config) = test_setup(&test_env, &fixture.manifest_uri);
Midenup::try_parse_from(["midenup", "install", "0.15.0"])
.unwrap()
.execute_with_state(&config, &mut state)
.unwrap();
let source = test_env.tmp_dir.path().join("patch-source");
std::fs::create_dir(&source).unwrap();
let source = source.canonicalize().unwrap();
std::fs::write(source.join("source.rs"), "user source").unwrap();
let newer_source = source.with_file_name("new-patch-source");
std::fs::create_dir(&newer_source).unwrap();
let newer_source = newer_source.canonicalize().unwrap();
let cache_root = paths::cargo_build_cache(&test_env.midenup_home);
let cache = paths::cargo_source_cache(&cache_root, &source);
let newer_cache = paths::cargo_source_cache(&cache_root, &newer_source);
for directory in [&cache, &newer_cache] {
std::fs::create_dir_all(directory).unwrap();
std::fs::write(directory.join("artifact"), "build output").unwrap();
}
std::os::unix::fs::symlink(&source, cache_root.join("source-link")).unwrap();
let runtime = paths::var_dir(&test_env.midenup_home, &"custom:second".parse().unwrap());
std::fs::create_dir_all(&runtime).unwrap();
std::fs::write(runtime.join("data"), "user data").unwrap();
let base = state.get(&semver::Version::new(0, 15, 0)).unwrap().clone();
let named = |name: &str, path: &std::path::Path| {
let mut installation = base.clone();
installation.custom = Some(CustomToolchain {
name: name.parse().unwrap(),
channel: UserChannel::Version(base.channel.clone()),
});
installation.components[0].version = Authority::Path {
path: path.to_path_buf(),
last_modification: None,
};
installation
};
let first = named("first", &source);
let second = named("second", &source);
state.upsert(first.clone());
state.upsert(second.clone());
midenup::commands::gc(&config, &state).unwrap();
assert!(cache.join("artifact").is_file());
assert!(!newer_cache.exists(), "unused source cache is collectible");
state.remove_by_id(&first.id());
midenup::commands::gc(&config, &state).unwrap();
assert!(cache.is_dir(), "the second variant still needs the shared cache");
state.upsert(named("second", &newer_source));
let pending = JournalEntry::install(base.channel, None, PublicationId::generate(), first);
journal::prepare(&test_env.midenup_home, &pending).unwrap();
std::fs::rename(&source, source.with_file_name("temporarily-moved-source")).unwrap();
midenup::commands::gc(&config, &state).unwrap();
assert!(cache.is_dir(), "the journal's target still needs the shared cache");
std::fs::rename(source.with_file_name("temporarily-moved-source"), &source).unwrap();
std::fs::remove_file(journal::entry_path(&test_env.midenup_home, &pending.id)).unwrap();
midenup::commands::gc(&config, &state).unwrap();
assert!(!cache.exists(), "source replacement releases the last cache reference");
assert_eq!(std::fs::read_to_string(source.join("source.rs")).unwrap(), "user source");
assert_eq!(std::fs::read_to_string(runtime.join("data")).unwrap(), "user data");
assert!(
cache_root
.join("source-link")
.symlink_metadata()
.unwrap()
.file_type()
.is_symlink()
);
}