Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 27 additions & 15 deletions crates/pet-conda/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,24 +225,36 @@ impl Locator for Conda {
});

match scope {
RefreshStateSyncScope::Full => {}
RefreshStateSyncScope::Full => {
// Full refresh: replace all caches entirely.
self.environments.clear();
self.environments
.insert_many(source.environments.clone_map());

self.managers.clear();
self.managers.insert_many(source.managers.clone_map());

self.mamba_managers.clear();
self.mamba_managers
.insert_many(source.mamba_managers.clone_map());
}
RefreshStateSyncScope::GlobalFiltered(kind)
if self.supported_categories().contains(kind) => {}
RefreshStateSyncScope::GlobalFiltered(_) | RefreshStateSyncScope::Workspace => {
return;
if self.supported_categories().contains(kind) =>
{
// Filtered refresh: merge discoveries without clearing existing
// caches. Today find() exhaustively discovers all conda
// environments, but a filtered scope should not assume that and
// must not drop entries found by a previous full refresh.
// Trade-off: deleted environments may linger until the next Full
// refresh, but that is preferable to silently losing live entries.
self.environments
.insert_many(source.environments.clone_map());
self.managers.insert_many(source.managers.clone_map());
self.mamba_managers
.insert_many(source.mamba_managers.clone_map());
}
RefreshStateSyncScope::GlobalFiltered(_) | RefreshStateSyncScope::Workspace => {}
}

self.environments.clear();
self.environments
.insert_many(source.environments.clone_map());

self.managers.clear();
self.managers.insert_many(source.managers.clone_map());

self.mamba_managers.clear();
self.mamba_managers
.insert_many(source.mamba_managers.clone_map());
}
fn configure(&self, config: &pet_core::Configuration) {
self.conda_executable
Expand Down
246 changes: 246 additions & 0 deletions crates/pet-conda/tests/lib_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,252 @@

mod common;

#[test]
fn sync_refresh_state_full_replaces_all_caches() {
use common::create_test_environment;
use pet_conda::Conda;
use pet_core::{
python_environment::{PythonEnvironment, PythonEnvironmentKind},
Locator, RefreshStateSyncScope,
};
use std::{collections::HashMap, path::PathBuf};

let env = create_test_environment(HashMap::new(), None, vec![], None);
let shared = Conda::from(&env);
let transient = Conda::from(&env);

// Populate shared with two environments from a "previous" refresh.
let env_a = PythonEnvironment::new(
Some(PathBuf::from("/envs/a/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/a")),
None,
Some("3.10.0".into()),
);
let env_b = PythonEnvironment::new(
Some(PathBuf::from("/envs/b/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/b")),
None,
Some("3.11.0".into()),
);
shared
.environments
.insert(PathBuf::from("/envs/a"), env_a.clone());
shared
.environments
.insert(PathBuf::from("/envs/b"), env_b.clone());

// Transient only discovered env_a (e.g. env_b was deleted).
transient
.environments
.insert(PathBuf::from("/envs/a"), env_a);

// Full sync should replace: shared loses env_b.
shared.sync_refresh_state_from(&transient, &RefreshStateSyncScope::Full);

assert_eq!(shared.environments.len(), 1);
assert!(shared.environments.get(&PathBuf::from("/envs/a")).is_some());
assert!(shared.environments.get(&PathBuf::from("/envs/b")).is_none());
}

#[test]
fn sync_refresh_state_global_filtered_merges_caches() {
use common::create_test_environment;
use pet_conda::{manager::CondaManager, Conda};
use pet_core::{
manager::EnvManagerType,
python_environment::{PythonEnvironment, PythonEnvironmentKind},
Locator, RefreshStateSyncScope,
};
use std::{collections::HashMap, path::PathBuf};

let env = create_test_environment(HashMap::new(), None, vec![], None);
let shared = Conda::from(&env);
let transient = Conda::from(&env);

// Populate shared with two environments from a "previous" refresh.
let env_a = PythonEnvironment::new(
Some(PathBuf::from("/envs/a/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/a")),
None,
Some("3.10.0".into()),
);
let env_b = PythonEnvironment::new(
Some(PathBuf::from("/envs/b/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/b")),
None,
Some("3.11.0".into()),
);
shared.environments.insert(PathBuf::from("/envs/a"), env_a);
shared.environments.insert(PathBuf::from("/envs/b"), env_b);

// Also populate shared with a manager.
let mgr_old = CondaManager {
executable: PathBuf::from("/conda/bin/conda"),
version: Some("23.0.0".into()),
conda_dir: Some(PathBuf::from("/conda")),
manager_type: EnvManagerType::Conda,
};
shared.managers.insert(PathBuf::from("/conda"), mgr_old);

// Transient discovered env_a (updated) and a new env_c, plus an updated manager.
let env_a_updated = PythonEnvironment::new(
Some(PathBuf::from("/envs/a/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/a")),
None,
Some("3.10.1".into()),
);
let env_c = PythonEnvironment::new(
Some(PathBuf::from("/envs/c/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/c")),
None,
Some("3.12.0".into()),
);
transient
.environments
.insert(PathBuf::from("/envs/a"), env_a_updated);
transient
.environments
.insert(PathBuf::from("/envs/c"), env_c);

let mgr_new = CondaManager {
executable: PathBuf::from("/conda/bin/conda"),
version: Some("24.0.0".into()),
conda_dir: Some(PathBuf::from("/conda")),
manager_type: EnvManagerType::Conda,
};
transient.managers.insert(PathBuf::from("/conda"), mgr_new);

// GlobalFiltered(Conda) should merge: shared keeps env_b, updates env_a, adds env_c.
shared.sync_refresh_state_from(
&transient,
&RefreshStateSyncScope::GlobalFiltered(PythonEnvironmentKind::Conda),
);

assert_eq!(shared.environments.len(), 3);
assert_eq!(
shared
.environments
.get(&PathBuf::from("/envs/a"))
.unwrap()
.version,
Some("3.10.1".into())
);
assert!(shared.environments.get(&PathBuf::from("/envs/b")).is_some());
assert!(shared.environments.get(&PathBuf::from("/envs/c")).is_some());

// Manager cache should also be merged (updated, not cleared).
assert_eq!(shared.managers.len(), 1);
assert_eq!(
shared
.managers
.get(&PathBuf::from("/conda"))
.unwrap()
.version,
Some("24.0.0".into())
);
}

#[test]
fn sync_refresh_state_full_then_global_filtered_preserves_entries() {
use common::create_test_environment;
use pet_conda::Conda;
use pet_core::{
python_environment::{PythonEnvironment, PythonEnvironmentKind},
Locator, RefreshStateSyncScope,
};
use std::{collections::HashMap, path::PathBuf};

let env = create_test_environment(HashMap::new(), None, vec![], None);
let shared = Conda::from(&env);

// Step 1: Full refresh populates shared with A and B.
let transient_full = Conda::from(&env);
transient_full.environments.insert(
PathBuf::from("/envs/a"),
PythonEnvironment::new(
Some(PathBuf::from("/envs/a/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/a")),
None,
Some("3.10.0".into()),
),
);
transient_full.environments.insert(
PathBuf::from("/envs/b"),
PythonEnvironment::new(
Some(PathBuf::from("/envs/b/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/b")),
None,
Some("3.11.0".into()),
),
);
shared.sync_refresh_state_from(&transient_full, &RefreshStateSyncScope::Full);
assert_eq!(shared.environments.len(), 2);

// Step 2: GlobalFiltered refresh discovers only C; A and B must survive.
let transient_filtered = Conda::from(&env);
transient_filtered.environments.insert(
PathBuf::from("/envs/c"),
PythonEnvironment::new(
Some(PathBuf::from("/envs/c/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/c")),
None,
Some("3.12.0".into()),
),
);
shared.sync_refresh_state_from(
&transient_filtered,
&RefreshStateSyncScope::GlobalFiltered(PythonEnvironmentKind::Conda),
);

assert_eq!(shared.environments.len(), 3);
assert!(shared.environments.get(&PathBuf::from("/envs/a")).is_some());
assert!(shared.environments.get(&PathBuf::from("/envs/b")).is_some());
assert!(shared.environments.get(&PathBuf::from("/envs/c")).is_some());
}

#[test]
fn sync_refresh_state_irrelevant_scope_is_noop() {
use common::create_test_environment;
use pet_conda::Conda;
use pet_core::{
python_environment::{PythonEnvironment, PythonEnvironmentKind},
Locator, RefreshStateSyncScope,
};
use std::{collections::HashMap, path::PathBuf};

let env = create_test_environment(HashMap::new(), None, vec![], None);
let shared = Conda::from(&env);
let transient = Conda::from(&env);

let env_a = PythonEnvironment::new(
Some(PathBuf::from("/envs/a/bin/python")),
Some(PythonEnvironmentKind::Conda),
Some(PathBuf::from("/envs/a")),
None,
Some("3.10.0".into()),
);
shared.environments.insert(PathBuf::from("/envs/a"), env_a);

// Workspace and unrelated GlobalFiltered should not touch conda caches.
shared.sync_refresh_state_from(&transient, &RefreshStateSyncScope::Workspace);
assert_eq!(shared.environments.len(), 1);

shared.sync_refresh_state_from(
&transient,
&RefreshStateSyncScope::GlobalFiltered(PythonEnvironmentKind::Poetry),
);
assert_eq!(shared.environments.len(), 1);
}

#[cfg(unix)]
#[test]
fn find_conda_env_without_manager() {
Expand Down
Loading