Skip to content
Draft
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
84 changes: 84 additions & 0 deletions crates/pet/src/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,4 +653,88 @@ mod tests {
"canonicalize() would resolve to target, but path() does not"
);
}

/// Test for https://github.com/microsoft/python-environment-tools/issues/151
/// Verifies that refresh with searchKind (e.g., "Venv") still finds environments
/// in workspace directories, not just global locations.
///
/// The bug was that when searchKind was provided, workspace_directories was cleared,
/// preventing discovery of workspace-based environments like venvs.
#[test]
fn test_search_kind_finds_workspace_venvs() {
use super::{find_and_report_envs, SearchScope};
use crate::locators::create_locators;
use pet_conda::Conda;
use pet_core::os_environment::EnvironmentApi;
use pet_core::python_environment::PythonEnvironmentKind;
use pet_core::Configuration;
use pet_poetry::Poetry;
use pet_reporter::collect;
use std::sync::Arc;

let tmp = TempDir::new().expect("Failed to create temp dir");
let workspace = tmp.path().to_path_buf();

// Create a venv structure in the workspace
let venv_dir = workspace.join(".venv");
#[cfg(windows)]
let bin_dir = venv_dir.join("Scripts");
#[cfg(unix)]
let bin_dir = venv_dir.join("bin");
fs::create_dir_all(&bin_dir).expect("Failed to create bin dir");

// Create pyvenv.cfg (required for venv detection)
fs::write(
venv_dir.join("pyvenv.cfg"),
"home = /usr/bin\nversion = 3.11.0\n",
)
.expect("Failed to create pyvenv.cfg");

// Create python executable
#[cfg(windows)]
let python_exe = bin_dir.join("python.exe");
#[cfg(unix)]
let python_exe = bin_dir.join("python");
fs::write(&python_exe, "fake python").expect("Failed to create python exe");

// Set up locators and configuration
let environment = EnvironmentApi::new();
let conda_locator = Arc::new(Conda::from(&environment));
let poetry_locator = Arc::new(Poetry::from(&environment));
let locators = create_locators(conda_locator, poetry_locator, &environment);

let config = Configuration {
workspace_directories: Some(vec![workspace.clone()]),
..Default::default()
};
for locator in locators.iter() {
locator.configure(&config);
}

let reporter = Arc::new(collect::create_reporter());

// Call find_and_report_envs with SearchScope::Global(Venv)
// This simulates the behavior when refresh is called with searchKind: "Venv"
find_and_report_envs(
reporter.as_ref(),
config,
&locators,
&environment,
Some(SearchScope::Global(PythonEnvironmentKind::Venv)),
);

let environments = reporter.environments.lock().unwrap();

// The venv should be discovered even when searching by kind
let venv_found = environments.iter().any(|env| {
env.kind == Some(PythonEnvironmentKind::Venv)
&& env.prefix.as_ref().map(|p| p == &venv_dir).unwrap_or(false)
});

assert!(
venv_found,
"Venv in workspace should be found when searching by kind. Found environments: {:?}",
*environments
);
}
}
71 changes: 40 additions & 31 deletions crates/pet/src/jsonrpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,43 +211,52 @@ pub fn handle_refresh(context: Arc<Context>, id: u32, params: Value) {

let mut search_scope = None;

// If search kind is provided and no search_paths, then we will only search in the global locations.
if refresh_options.search_kind.is_some() || refresh_options.search_paths.is_some() {
// Always clear this, as we will either serach in specified folder or a specific kind in global locations.
// If search_paths is provided, limit search to those paths.
// If only search_kind is provided (without search_paths), we still search
// workspace directories because many environment types (like Venv, VirtualEnv)
// don't have global locations - they only exist in workspace folders.
// The reporter will filter results to only report the requested kind.
if let Some(search_paths) = refresh_options.search_paths {
// Clear workspace directories when explicit search paths are provided.
config.workspace_directories = None;
if let Some(search_paths) = refresh_options.search_paths {
// Expand any glob patterns in the search paths
let expanded_paths = expand_glob_patterns(&search_paths);
trace!(
"Expanded {} search paths to {} paths",
search_paths.len(),
expanded_paths.len()
);
// These workspace folders are only for this refresh.
config.workspace_directories = Some(
expanded_paths
.iter()
.filter(|p| p.is_dir())
.cloned()
.collect(),
);
config.executables = Some(
expanded_paths
.iter()
.filter(|p| p.is_file())
.cloned()
.collect(),
);
search_scope = Some(SearchScope::Workspace);
} else if let Some(search_kind) = refresh_options.search_kind {
config.executables = None;
search_scope = Some(SearchScope::Global(search_kind));
}
// Expand any glob patterns in the search paths
let expanded_paths = expand_glob_patterns(&search_paths);
trace!(
"Expanded {} search paths to {} paths",
search_paths.len(),
expanded_paths.len()
);
// These workspace folders are only for this refresh.
config.workspace_directories = Some(
expanded_paths
.iter()
.filter(|p| p.is_dir())
.cloned()
.collect(),
);
config.executables = Some(
expanded_paths
.iter()
.filter(|p| p.is_file())
.cloned()
.collect(),
);
search_scope = Some(SearchScope::Workspace);

// Configure the locators with the modified config.
for locator in context.locators.iter() {
locator.configure(&config);
}
} else if let Some(search_kind) = refresh_options.search_kind {
// When only search_kind is provided, keep workspace directories so that
// workspace-based environments (Venv, VirtualEnv, etc.) can be found.
// The search_scope tells find_and_report_envs to filter locators by kind.
search_scope = Some(SearchScope::Global(search_kind));

// Re-configure the locators (config unchanged, but ensures consistency).
for locator in context.locators.iter() {
locator.configure(&config);
}
} else {
// Re-configure the locators with an un-modified config.
// Possible we congirued the locators with a modified config in the in the previous request.
Expand Down
Loading