Skip to content

Commit b613187

Browse files
karthiknadigCopilot
andcommitted
test: make Conda cache pruning deterministic (Fixes #547)
Exercise populated metadata-cache pruning independently of discovery worker timing. Preserve active metadata and prove stale and empty-set entries reload, including Windows normalized aliases. Runtime pruning semantics and scheduling are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 38ae9a1 commit b613187

1 file changed

Lines changed: 84 additions & 4 deletions

File tree

‎crates/pet-conda/src/lib.rs‎

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ impl Conda {
153153
self.mamba_managers.clear();
154154
}
155155

156+
fn prune_environment_info_cache(&self, active_prefixes: &HashSet<PathBuf>) {
157+
self.environment_info_cache
158+
.write()
159+
.expect("conda environment info cache lock poisoned")
160+
.retain(|prefix, _| active_prefixes.contains(prefix));
161+
}
162+
156163
fn get_environment_details(&self, path: &Path) -> Option<CondaEnvironmentDetails> {
157164
self.get_or_load_environment_details(path, || {
158165
let environment = get_conda_environment_info(path, &None)?;
@@ -517,10 +524,7 @@ impl Locator for Conda {
517524
});
518525
}
519526

520-
self.environment_info_cache
521-
.write()
522-
.expect("conda environment info cache lock poisoned")
523-
.retain(|prefix, _| active_prefixes.contains(prefix));
527+
self.prune_environment_info_cache(&active_prefixes);
524528
});
525529
}
526530
}
@@ -552,6 +556,82 @@ mod tests {
552556
}
553557
}
554558

559+
#[test]
560+
fn environment_info_cache_pruning_retains_active_and_reloads_removed_entries() {
561+
let root = tempfile::tempdir().unwrap();
562+
let active = root.path().join("active");
563+
let stale = root.path().join("stale");
564+
let environment = EnvironmentApi::new();
565+
let shared = Conda::from(&environment);
566+
let refresh = Conda::from_shared_environment_cache(&environment, &shared);
567+
let loads = AtomicUsize::new(0);
568+
569+
for prefix in [&active, &stale] {
570+
let metadata = prefix.join("conda-meta");
571+
fs::create_dir_all(&metadata).unwrap();
572+
fs::write(metadata.join("history"), "unchanged history").unwrap();
573+
shared
574+
.get_or_load_environment_details(prefix, || {
575+
let load = loads.fetch_add(1, Ordering::Relaxed) + 1;
576+
Some(test_details(prefix, load))
577+
})
578+
.unwrap();
579+
}
580+
assert_eq!(shared.environment_info_cache.read().unwrap().len(), 2);
581+
582+
#[cfg(windows)]
583+
let active_alias = PathBuf::from(active.to_string_lossy().replace('\\', "/"));
584+
#[cfg(not(windows))]
585+
let active_alias = active.clone();
586+
let active_prefixes = HashSet::from([norm_case(&active_alias)]);
587+
refresh.prune_environment_info_cache(&active_prefixes);
588+
589+
{
590+
let cache = shared.environment_info_cache.read().unwrap();
591+
assert_eq!(cache.len(), 1);
592+
assert!(cache.contains_key(&norm_case(&active)));
593+
assert!(!cache.contains_key(&norm_case(&stale)));
594+
}
595+
let retained = shared
596+
.get_or_load_environment_details(&active_alias, || {
597+
panic!("active metadata should survive pruning without reloading")
598+
})
599+
.unwrap();
600+
assert_eq!(retained.environment.prefix, Some(active_alias));
601+
assert_eq!(retained.environment.version.as_deref(), Some("1"));
602+
assert_eq!(loads.load(Ordering::Relaxed), 2);
603+
604+
let reloaded = shared
605+
.get_or_load_environment_details(&stale, || {
606+
let load = loads.fetch_add(1, Ordering::Relaxed) + 1;
607+
Some(test_details(&stale, load))
608+
})
609+
.unwrap();
610+
assert_eq!(reloaded.environment.version.as_deref(), Some("3"));
611+
assert_eq!(loads.load(Ordering::Relaxed), 3);
612+
613+
refresh.prune_environment_info_cache(&HashSet::new());
614+
assert!(shared.environment_info_cache.read().unwrap().is_empty());
615+
for prefix in [&active, &stale] {
616+
shared
617+
.get_or_load_environment_details(prefix, || {
618+
let load = loads.fetch_add(1, Ordering::Relaxed) + 1;
619+
Some(test_details(prefix, load))
620+
})
621+
.unwrap();
622+
}
623+
assert_eq!(loads.load(Ordering::Relaxed), 5);
624+
}
625+
626+
#[test]
627+
fn environment_info_cache_pruning_empty_cache_does_not_create_entries() {
628+
let root = tempfile::tempdir().unwrap();
629+
let locator = Conda::from(&EnvironmentApi::new());
630+
locator.prune_environment_info_cache(&HashSet::new());
631+
locator.prune_environment_info_cache(&HashSet::from([norm_case(root.path())]));
632+
assert!(locator.environment_info_cache.read().unwrap().is_empty());
633+
}
634+
555635
#[test]
556636
fn environment_info_cache_is_shared_and_invalidated_by_history_changes() {
557637
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);

0 commit comments

Comments
 (0)