python: Respect picked toolchain (when it's not at the root) when running tests (#31150)

# Fix Python venv Detection for Test Runner
## Problem
Zed’s Python test runner was not reliably detecting and activating the
project’s Python virtual environment (.venv or venv), causing it to
default to the system Python. This led to issues such as missing
dependencies (e.g., pytest) when running tests.
## Solution
Project Root Awareness: The Python context provider now receives the
project root path, ensuring venv detection always starts from the
project root rather than the test file’s directory.
Robust venv Activation: The test runner now correctly detects and
activates the Python interpreter from .venv or venv in the project root,
setting VIRTUAL_ENV and updating PATH as needed.
Minimal Impact: The change is limited in scope, affecting only the
necessary code paths for Python test runner venv detection. No broad
architectural changes were made.
## Additional Improvements
Updated trait and function signatures to thread the project root path
where needed.
Cleaned up linter warnings and unused code.
## Result
Python tests now reliably run using the project’s virtual environment,
matching the behavior of other IDEs and ensuring all dependencies are
available.

Release Notes:

- Fixed Python tasks always running with a toolchain selected for the
root of a workspace.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
Thiago Pacheco 2025-06-02 09:29:34 -04:00 committed by GitHub
parent f90333f92e
commit aacbb9c2f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -379,17 +379,19 @@ impl ContextProvider for PythonContextProvider {
};
let module_target = self.build_module_target(variables);
let worktree_id = location
.file_location
.buffer
.read(cx)
.file()
.map(|f| f.worktree_id(cx));
let location_file = location.file_location.buffer.read(cx).file().cloned();
let worktree_id = location_file.as_ref().map(|f| f.worktree_id(cx));
cx.spawn(async move |cx| {
let raw_toolchain = if let Some(worktree_id) = worktree_id {
let file_path = location_file
.as_ref()
.and_then(|f| f.path().parent())
.map(Arc::from)
.unwrap_or_else(|| Arc::from("".as_ref()));
toolchains
.active_toolchain(worktree_id, Arc::from("".as_ref()), "Python".into(), cx)
.active_toolchain(worktree_id, file_path, "Python".into(), cx)
.await
.map_or_else(
|| String::from("python3"),
@ -398,14 +400,16 @@ impl ContextProvider for PythonContextProvider {
} else {
String::from("python3")
};
let active_toolchain = format!("\"{raw_toolchain}\"");
let toolchain = (PYTHON_ACTIVE_TOOLCHAIN_PATH, active_toolchain);
let raw_toolchain = (PYTHON_ACTIVE_TOOLCHAIN_PATH_RAW, raw_toolchain);
let raw_toolchain_var = (PYTHON_ACTIVE_TOOLCHAIN_PATH_RAW, raw_toolchain);
Ok(task::TaskVariables::from_iter(
test_target
.into_iter()
.chain(module_target.into_iter())
.chain([toolchain, raw_toolchain]),
.chain([toolchain, raw_toolchain_var]),
))
})
}