diff --git a/crates/pet-conda/src/lib.rs b/crates/pet-conda/src/lib.rs index 25b745fd..ef084c71 100644 --- a/crates/pet-conda/src/lib.rs +++ b/crates/pet-conda/src/lib.rs @@ -172,16 +172,19 @@ impl Conda { where F: FnOnce() -> Option, { + let cache_key = norm_case(path); let fingerprint_before = CondaEnvironmentFingerprint::from_prefix(path); if let Some(fingerprint) = &fingerprint_before { if let Some(cached) = self .environment_info_cache .read() .expect("conda environment info cache lock poisoned") - .get(path) + .get(&cache_key) .filter(|cached| &cached.fingerprint == fingerprint) { - return Some(cached.details.clone()); + let mut details = cached.details.clone(); + details.environment.prefix = Some(path.to_path_buf()); + return Some(details); } } @@ -189,7 +192,7 @@ impl Conda { self.environment_info_cache .write() .expect("conda environment info cache lock poisoned") - .remove(path); + .remove(&cache_key); return None; }; let fingerprint_after = CondaEnvironmentFingerprint::from_prefix(path); @@ -199,14 +202,14 @@ impl Conda { .expect("conda environment info cache lock poisoned"); if fingerprint_before.is_some() && fingerprint_before == fingerprint_after { cache.insert( - path.to_path_buf(), + cache_key, CachedCondaEnvironment { fingerprint: fingerprint_after.expect("fingerprint checked as present"), details: details.clone(), }, ); } else { - cache.remove(path); + cache.remove(&cache_key); } Some(details) @@ -458,7 +461,8 @@ impl Locator for Conda { } let possible_conda_envs = get_conda_environment_paths(&env_vars, &executable); - let active_prefixes: HashSet = possible_conda_envs.iter().cloned().collect(); + let active_prefixes: HashSet = + possible_conda_envs.iter().map(norm_case).collect(); for path in possible_conda_envs { s.spawn(move || { let details = self.get_environment_details(&path)?; @@ -594,4 +598,41 @@ mod tests { fs::remove_dir_all(prefix).unwrap(); } + + #[cfg(windows)] + #[test] + fn environment_info_cache_normalizes_windows_keys() { + static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + + let prefix = std::env::temp_dir().join(format!( + "pet-conda-environment-cache-case-{}-{}", + std::process::id(), + NEXT_ID.fetch_add(1, Ordering::Relaxed) + )); + let conda_meta = prefix.join("conda-meta"); + fs::create_dir_all(&conda_meta).unwrap(); + fs::write(conda_meta.join("history"), "history").unwrap(); + + let alternate_separators = PathBuf::from(prefix.to_string_lossy().replace('\\', "/")); + let environment = EnvironmentApi::new(); + let locator = Conda::from(&environment); + let loads = AtomicUsize::new(0); + + locator + .get_or_load_environment_details(&prefix, || { + loads.fetch_add(1, Ordering::Relaxed); + Some(test_details(&prefix, 1)) + }) + .unwrap(); + let cached = locator + .get_or_load_environment_details(&alternate_separators, || { + panic!("equivalent Windows paths should reuse the cache") + }) + .unwrap(); + + assert_eq!(loads.load(Ordering::Relaxed), 1); + assert_eq!(cached.environment.prefix, Some(alternate_separators)); + + fs::remove_dir_all(prefix).unwrap(); + } }