Files
zed/crates/project/tests/integration/debugger.rs
T
Andrey Fedoseev cff8de9239 Fix PythonLocator to pass through env variables from the task template (#63154)
This matches `GoLocator` and `NodeLocator`

# Objective

The problem: `env` variables defined in `tasks.json` aren't used when
the task runs in debug mode.

Example:

```json
[
  {
    "label": "pytest (custom)",
    "env": {
      "DATABASE_URL": "...",
    },
    "command": "$ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN",
    "args": [
      "-m",
      "pytest",
      "$ZED_CUSTOM_PYTHON_TEST_TARGET",
    ],
    "tags": ["python-pytest-class", "python-pytest-method"],
    "cwd": "$ZED_WORKTREE_ROOT",
  },
]
```

When a test runs normally, the `DATABASE_URL` variable is set correctly.
When the test runs in debug mode, the variable is not set.

## Solution

`PythonLocator` copies the `env` value from the `TaskTemplate`. This
matches other locators, such as `GoLocator` and `NodeLocator`

## Testing

- Did you test these changes? If so, how?
  - The existing integration test is updated to verify the change
- Are there any parts that need more testing?
  - No
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
- Run `cargo test -p project --test integration
debugger::python_locator::test_python_locator`
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
  - Linux (NixOS), the change is platform-agnostic.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

## Showcase

N/A

---

Release Notes:

- Fix `PythonLocator` to pass through `env` variables from the task
template
2026-08-24 16:43:13 +00:00

346 lines
12 KiB
Rust

mod go_locator {
use collections::HashMap;
use dap::{DapLocator, adapters::DebugAdapterName};
use gpui::TestAppContext;
use project::debugger::locators::go::{DelveLaunchRequest, GoLocator};
use task::{HideStrategy, RevealStrategy, RevealTarget, SaveStrategy, Shell, TaskTemplate};
#[gpui::test]
async fn test_create_scenario_for_go_build(_: &mut TestAppContext) {
let locator = GoLocator;
let task = TaskTemplate {
label: "go build".into(),
command: "go".into(),
args: vec!["build".into(), ".".into()],
env: Default::default(),
cwd: Some("${ZED_WORKTREE_ROOT}".into()),
use_new_terminal: false,
allow_concurrent_runs: false,
reveal: RevealStrategy::Always,
reveal_target: RevealTarget::Dock,
hide: HideStrategy::Never,
shell: Shell::System,
tags: vec![],
show_summary: true,
show_command: true,
save: SaveStrategy::default(),
hooks: Default::default(),
};
let scenario = locator
.create_scenario(&task, "test label", &DebugAdapterName("Delve".into()))
.await;
assert!(scenario.is_none());
}
#[gpui::test]
async fn test_skip_non_go_commands_with_non_delve_adapter(_: &mut TestAppContext) {
let locator = GoLocator;
let task = TaskTemplate {
label: "cargo build".into(),
command: "cargo".into(),
args: vec!["build".into()],
env: Default::default(),
cwd: Some("${ZED_WORKTREE_ROOT}".into()),
use_new_terminal: false,
allow_concurrent_runs: false,
reveal: RevealStrategy::Always,
reveal_target: RevealTarget::Dock,
hide: HideStrategy::Never,
shell: Shell::System,
tags: vec![],
show_summary: true,
show_command: true,
save: SaveStrategy::default(),
hooks: Default::default(),
};
let scenario = locator
.create_scenario(
&task,
"test label",
&DebugAdapterName("SomeOtherAdapter".into()),
)
.await;
assert!(scenario.is_none());
let scenario = locator
.create_scenario(&task, "test label", &DebugAdapterName("Delve".into()))
.await;
assert!(scenario.is_none());
}
#[gpui::test]
async fn test_go_locator_run(_: &mut TestAppContext) {
let locator = GoLocator;
let delve = DebugAdapterName("Delve".into());
let task = TaskTemplate {
label: "go run with flags".into(),
command: "go".into(),
args: vec![
"run".to_string(),
"-race".to_string(),
"-ldflags".to_string(),
"-X main.version=1.0".to_string(),
"./cmd/myapp".to_string(),
"--config".to_string(),
"production.yaml".to_string(),
"--verbose".to_string(),
],
env: {
let mut env = HashMap::default();
env.insert("GO_ENV".to_string(), "production".to_string());
env
},
cwd: Some("/project/root".into()),
..Default::default()
};
let scenario = locator
.create_scenario(&task, "test run label", &delve)
.await
.unwrap();
let config: DelveLaunchRequest = serde_json::from_value(scenario.config).unwrap();
assert_eq!(
config,
DelveLaunchRequest {
request: "launch".to_string(),
mode: "debug".to_string(),
program: "./cmd/myapp".to_string(),
build_flags: vec![
"-race".to_string(),
"-ldflags".to_string(),
"-X main.version=1.0".to_string()
],
args: vec![
"--config".to_string(),
"production.yaml".to_string(),
"--verbose".to_string(),
],
env: {
let mut env = HashMap::default();
env.insert("GO_ENV".to_string(), "production".to_string());
env
},
cwd: Some("/project/root".to_string()),
}
);
}
#[gpui::test]
async fn test_go_locator_test(_: &mut TestAppContext) {
let locator = GoLocator;
let delve = DebugAdapterName("Delve".into());
// Test with tags and run flag
let task_with_tags = TaskTemplate {
label: "test".into(),
command: "go".into(),
args: vec![
"test".to_string(),
"-tags".to_string(),
"integration,unit".to_string(),
"-run".to_string(),
"Foo".to_string(),
".".to_string(),
],
..Default::default()
};
let result = locator
.create_scenario(&task_with_tags, "", &delve)
.await
.unwrap();
let config: DelveLaunchRequest = serde_json::from_value(result.config).unwrap();
assert_eq!(
config,
DelveLaunchRequest {
request: "launch".to_string(),
mode: "test".to_string(),
program: ".".to_string(),
build_flags: vec!["-tags".to_string(), "integration,unit".to_string(),],
args: vec![
"-test.run".to_string(),
"Foo".to_string(),
"-test.v".to_string()
],
env: Default::default(),
cwd: None,
}
);
}
#[gpui::test]
async fn test_go_locator_unescapes_nested_subtest_regex(_: &mut TestAppContext) {
let locator = GoLocator;
let delve = DebugAdapterName("Delve".into());
// Delve receives the `-run` regex with no shell, so GoLocator must strip
// the escaping itself.
let task = TaskTemplate {
label: "test subtest".into(),
command: "go".into(),
args: vec![
"test".to_string(),
"-v".to_string(),
"-run".to_string(),
"\\^TestFoo\\$/\\^simple_subtest\\$".to_string(),
],
..Default::default()
};
let result = locator.create_scenario(&task, "", &delve).await.unwrap();
let config: DelveLaunchRequest = serde_json::from_value(result.config).unwrap();
assert_eq!(
config,
DelveLaunchRequest {
request: "launch".to_string(),
mode: "test".to_string(),
program: ".".to_string(),
build_flags: vec![],
args: vec![
"-test.v".to_string(),
"-test.run".to_string(),
"^TestFoo$/^simple_subtest$".to_string(),
],
env: Default::default(),
cwd: None,
}
);
}
#[gpui::test]
async fn test_skip_unsupported_go_commands(_: &mut TestAppContext) {
let locator = GoLocator;
let task = TaskTemplate {
label: "go clean".into(),
command: "go".into(),
args: vec!["clean".into()],
env: Default::default(),
cwd: Some("${ZED_WORKTREE_ROOT}".into()),
use_new_terminal: false,
allow_concurrent_runs: false,
reveal: RevealStrategy::Always,
reveal_target: RevealTarget::Dock,
hide: HideStrategy::Never,
shell: Shell::System,
tags: vec![],
show_summary: true,
show_command: true,
save: SaveStrategy::default(),
hooks: Default::default(),
};
let scenario = locator
.create_scenario(&task, "test label", &DebugAdapterName("Delve".into()))
.await;
assert!(scenario.is_none());
}
}
mod python_locator {
use collections::HashMap;
use dap::{DapLocator, adapters::DebugAdapterName};
use serde_json::json;
use project::debugger::locators::python::*;
use task::{DebugScenario, TaskTemplate};
#[gpui::test]
async fn test_python_locator() {
let adapter = DebugAdapterName("Debugpy".into());
let build_task = TaskTemplate {
label: "run module '$ZED_FILE'".into(),
command: "$ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN".into(),
args: vec!["-m".into(), "$ZED_CUSTOM_PYTHON_MODULE_NAME".into()],
env: {
let mut env = HashMap::default();
env.insert("PYTHON_ENV".to_string(), "production".to_string());
env
},
cwd: Some("$ZED_WORKTREE_ROOT".into()),
use_new_terminal: false,
allow_concurrent_runs: false,
reveal: task::RevealStrategy::Always,
reveal_target: task::RevealTarget::Dock,
hide: task::HideStrategy::Never,
tags: vec!["python-module-main-method".into()],
shell: task::Shell::System,
show_summary: false,
show_command: false,
save: task::SaveStrategy::default(),
hooks: Default::default(),
};
let expected_scenario = DebugScenario {
adapter: "Debugpy".into(),
label: "run module 'main.py'".into(),
build: None,
config: json!({
"request": "launch",
"python": "$ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN",
"args": [],
"cwd": "$ZED_WORKTREE_ROOT",
"module": "$ZED_CUSTOM_PYTHON_MODULE_NAME",
"env": { "PYTHON_ENV": "production" },
}),
tcp_connection: None,
};
assert_eq!(
PythonLocator
.create_scenario(&build_task, "run module 'main.py'", &adapter)
.await
.expect("Failed to create a scenario"),
expected_scenario
);
}
}
mod memory {
use project::debugger::{
MemoryCell,
memory::{MemoryIterator, PageAddress, PageContents},
};
#[test]
fn iterate_over_unmapped_memory() {
let empty_iterator = MemoryIterator::new(0..=127, Default::default());
let actual = empty_iterator.collect::<Vec<_>>();
let expected = vec![MemoryCell(None); 128];
assert_eq!(actual.len(), expected.len());
assert_eq!(actual, expected);
}
#[test]
fn iterate_over_partially_mapped_memory() {
let it = MemoryIterator::new(
0..=127,
vec![(PageAddress(5), PageContents::mapped(vec![1]))].into_iter(),
);
let actual = it.collect::<Vec<_>>();
let expected = std::iter::repeat_n(MemoryCell(None), 5)
.chain(std::iter::once(MemoryCell(Some(1))))
.chain(std::iter::repeat_n(MemoryCell(None), 122))
.collect::<Vec<_>>();
assert_eq!(actual.len(), expected.len());
assert_eq!(actual, expected);
}
#[test]
fn reads_from_the_middle_of_a_page() {
let partial_iter = MemoryIterator::new(
20..=30,
vec![(PageAddress(0), PageContents::mapped((0..255).collect()))].into_iter(),
);
let actual = partial_iter.collect::<Vec<_>>();
let expected = (20..=30)
.map(|val| MemoryCell(Some(val)))
.collect::<Vec<_>>();
assert_eq!(actual.len(), expected.len());
assert_eq!(actual, expected);
}
}