debugger.rs

  1mod go_locator {
  2    use collections::HashMap;
  3    use dap::{DapLocator, adapters::DebugAdapterName};
  4    use gpui::TestAppContext;
  5    use project::debugger::locators::go::{DelveLaunchRequest, GoLocator};
  6    use task::{HideStrategy, RevealStrategy, RevealTarget, SaveStrategy, Shell, TaskTemplate};
  7    #[gpui::test]
  8    async fn test_create_scenario_for_go_build(_: &mut TestAppContext) {
  9        let locator = GoLocator;
 10        let task = TaskTemplate {
 11            label: "go build".into(),
 12            command: "go".into(),
 13            args: vec!["build".into(), ".".into()],
 14            env: Default::default(),
 15            cwd: Some("${ZED_WORKTREE_ROOT}".into()),
 16            use_new_terminal: false,
 17            allow_concurrent_runs: false,
 18            reveal: RevealStrategy::Always,
 19            reveal_target: RevealTarget::Dock,
 20            hide: HideStrategy::Never,
 21            shell: Shell::System,
 22            tags: vec![],
 23            show_summary: true,
 24            show_command: true,
 25            save: SaveStrategy::default(),
 26            hooks: Default::default(),
 27        };
 28
 29        let scenario = locator
 30            .create_scenario(&task, "test label", &DebugAdapterName("Delve".into()))
 31            .await;
 32
 33        assert!(scenario.is_none());
 34    }
 35
 36    #[gpui::test]
 37    async fn test_skip_non_go_commands_with_non_delve_adapter(_: &mut TestAppContext) {
 38        let locator = GoLocator;
 39        let task = TaskTemplate {
 40            label: "cargo build".into(),
 41            command: "cargo".into(),
 42            args: vec!["build".into()],
 43            env: Default::default(),
 44            cwd: Some("${ZED_WORKTREE_ROOT}".into()),
 45            use_new_terminal: false,
 46            allow_concurrent_runs: false,
 47            reveal: RevealStrategy::Always,
 48            reveal_target: RevealTarget::Dock,
 49            hide: HideStrategy::Never,
 50            shell: Shell::System,
 51            tags: vec![],
 52            show_summary: true,
 53            show_command: true,
 54            save: SaveStrategy::default(),
 55            hooks: Default::default(),
 56        };
 57
 58        let scenario = locator
 59            .create_scenario(
 60                &task,
 61                "test label",
 62                &DebugAdapterName("SomeOtherAdapter".into()),
 63            )
 64            .await;
 65        assert!(scenario.is_none());
 66
 67        let scenario = locator
 68            .create_scenario(&task, "test label", &DebugAdapterName("Delve".into()))
 69            .await;
 70        assert!(scenario.is_none());
 71    }
 72    #[gpui::test]
 73    async fn test_go_locator_run(_: &mut TestAppContext) {
 74        let locator = GoLocator;
 75        let delve = DebugAdapterName("Delve".into());
 76
 77        let task = TaskTemplate {
 78            label: "go run with flags".into(),
 79            command: "go".into(),
 80            args: vec![
 81                "run".to_string(),
 82                "-race".to_string(),
 83                "-ldflags".to_string(),
 84                "-X main.version=1.0".to_string(),
 85                "./cmd/myapp".to_string(),
 86                "--config".to_string(),
 87                "production.yaml".to_string(),
 88                "--verbose".to_string(),
 89            ],
 90            env: {
 91                let mut env = HashMap::default();
 92                env.insert("GO_ENV".to_string(), "production".to_string());
 93                env
 94            },
 95            cwd: Some("/project/root".into()),
 96            ..Default::default()
 97        };
 98
 99        let scenario = locator
100            .create_scenario(&task, "test run label", &delve)
101            .await
102            .unwrap();
103
104        let config: DelveLaunchRequest = serde_json::from_value(scenario.config).unwrap();
105
106        assert_eq!(
107            config,
108            DelveLaunchRequest {
109                request: "launch".to_string(),
110                mode: "debug".to_string(),
111                program: "./cmd/myapp".to_string(),
112                build_flags: vec![
113                    "-race".to_string(),
114                    "-ldflags".to_string(),
115                    "-X main.version=1.0".to_string()
116                ],
117                args: vec![
118                    "--config".to_string(),
119                    "production.yaml".to_string(),
120                    "--verbose".to_string(),
121                ],
122                env: {
123                    let mut env = HashMap::default();
124                    env.insert("GO_ENV".to_string(), "production".to_string());
125                    env
126                },
127                cwd: Some("/project/root".to_string()),
128            }
129        );
130    }
131
132    #[gpui::test]
133    async fn test_go_locator_test(_: &mut TestAppContext) {
134        let locator = GoLocator;
135        let delve = DebugAdapterName("Delve".into());
136
137        // Test with tags and run flag
138        let task_with_tags = TaskTemplate {
139            label: "test".into(),
140            command: "go".into(),
141            args: vec![
142                "test".to_string(),
143                "-tags".to_string(),
144                "integration,unit".to_string(),
145                "-run".to_string(),
146                "Foo".to_string(),
147                ".".to_string(),
148            ],
149            ..Default::default()
150        };
151        let result = locator
152            .create_scenario(&task_with_tags, "", &delve)
153            .await
154            .unwrap();
155
156        let config: DelveLaunchRequest = serde_json::from_value(result.config).unwrap();
157
158        assert_eq!(
159            config,
160            DelveLaunchRequest {
161                request: "launch".to_string(),
162                mode: "test".to_string(),
163                program: ".".to_string(),
164                build_flags: vec!["-tags".to_string(), "integration,unit".to_string(),],
165                args: vec![
166                    "-test.run".to_string(),
167                    "Foo".to_string(),
168                    "-test.v".to_string()
169                ],
170                env: Default::default(),
171                cwd: None,
172            }
173        );
174    }
175
176    #[gpui::test]
177    async fn test_go_locator_subtest(_: &mut TestAppContext) {
178        let locator = GoLocator;
179        let delve = DebugAdapterName("Delve".into());
180
181        // The `go-subtest` task template uses the \^...\$ format for the `-run`
182        // arg so the GoLocator strips the shell escaping before passing it to
183        // Delve. Previously the template used single quotes ('^...$') which
184        // Delve passed literally to the test binary, breaking all subtests.
185        let task = TaskTemplate {
186            label: "test subtest".into(),
187            command: "go".into(),
188            args: vec![
189                "test".to_string(),
190                "-v".to_string(),
191                "-run".to_string(),
192                "\\^TestFoo\\$/\\^simple_subtest\\$".to_string(),
193            ],
194            ..Default::default()
195        };
196        let result = locator.create_scenario(&task, "", &delve).await.unwrap();
197        let config: DelveLaunchRequest = serde_json::from_value(result.config).unwrap();
198        assert_eq!(
199            config,
200            DelveLaunchRequest {
201                request: "launch".to_string(),
202                mode: "test".to_string(),
203                program: ".".to_string(),
204                build_flags: vec![],
205                args: vec![
206                    "-test.v".to_string(),
207                    "-test.run".to_string(),
208                    "^TestFoo$/^simple_subtest$".to_string(),
209                ],
210                env: Default::default(),
211                cwd: None,
212            }
213        );
214    }
215
216    #[gpui::test]
217    async fn test_skip_unsupported_go_commands(_: &mut TestAppContext) {
218        let locator = GoLocator;
219        let task = TaskTemplate {
220            label: "go clean".into(),
221            command: "go".into(),
222            args: vec!["clean".into()],
223            env: Default::default(),
224            cwd: Some("${ZED_WORKTREE_ROOT}".into()),
225            use_new_terminal: false,
226            allow_concurrent_runs: false,
227            reveal: RevealStrategy::Always,
228            reveal_target: RevealTarget::Dock,
229            hide: HideStrategy::Never,
230            shell: Shell::System,
231            tags: vec![],
232            show_summary: true,
233            show_command: true,
234            save: SaveStrategy::default(),
235            hooks: Default::default(),
236        };
237
238        let scenario = locator
239            .create_scenario(&task, "test label", &DebugAdapterName("Delve".into()))
240            .await;
241        assert!(scenario.is_none());
242    }
243}
244
245mod python_locator {
246    use dap::{DapLocator, adapters::DebugAdapterName};
247    use serde_json::json;
248
249    use project::debugger::locators::python::*;
250    use task::{DebugScenario, TaskTemplate};
251
252    #[gpui::test]
253    async fn test_python_locator() {
254        let adapter = DebugAdapterName("Debugpy".into());
255        let build_task = TaskTemplate {
256            label: "run module '$ZED_FILE'".into(),
257            command: "$ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN".into(),
258            args: vec!["-m".into(), "$ZED_CUSTOM_PYTHON_MODULE_NAME".into()],
259            env: Default::default(),
260            cwd: Some("$ZED_WORKTREE_ROOT".into()),
261            use_new_terminal: false,
262            allow_concurrent_runs: false,
263            reveal: task::RevealStrategy::Always,
264            reveal_target: task::RevealTarget::Dock,
265            hide: task::HideStrategy::Never,
266            tags: vec!["python-module-main-method".into()],
267            shell: task::Shell::System,
268            show_summary: false,
269            show_command: false,
270            save: task::SaveStrategy::default(),
271            hooks: Default::default(),
272        };
273
274        let expected_scenario = DebugScenario {
275            adapter: "Debugpy".into(),
276            label: "run module 'main.py'".into(),
277            build: None,
278            config: json!({
279                "request": "launch",
280                "python": "$ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN",
281                "args": [],
282                "cwd": "$ZED_WORKTREE_ROOT",
283                "module": "$ZED_CUSTOM_PYTHON_MODULE_NAME",
284            }),
285            tcp_connection: None,
286        };
287
288        assert_eq!(
289            PythonLocator
290                .create_scenario(&build_task, "run module 'main.py'", &adapter)
291                .await
292                .expect("Failed to create a scenario"),
293            expected_scenario
294        );
295    }
296}
297
298mod memory {
299    use project::debugger::{
300        MemoryCell,
301        memory::{MemoryIterator, PageAddress, PageContents},
302    };
303
304    #[test]
305    fn iterate_over_unmapped_memory() {
306        let empty_iterator = MemoryIterator::new(0..=127, Default::default());
307        let actual = empty_iterator.collect::<Vec<_>>();
308        let expected = vec![MemoryCell(None); 128];
309        assert_eq!(actual.len(), expected.len());
310        assert_eq!(actual, expected);
311    }
312
313    #[test]
314    fn iterate_over_partially_mapped_memory() {
315        let it = MemoryIterator::new(
316            0..=127,
317            vec![(PageAddress(5), PageContents::mapped(vec![1]))].into_iter(),
318        );
319        let actual = it.collect::<Vec<_>>();
320        let expected = std::iter::repeat_n(MemoryCell(None), 5)
321            .chain(std::iter::once(MemoryCell(Some(1))))
322            .chain(std::iter::repeat_n(MemoryCell(None), 122))
323            .collect::<Vec<_>>();
324        assert_eq!(actual.len(), expected.len());
325        assert_eq!(actual, expected);
326    }
327
328    #[test]
329    fn reads_from_the_middle_of_a_page() {
330        let partial_iter = MemoryIterator::new(
331            20..=30,
332            vec![(PageAddress(0), PageContents::mapped((0..255).collect()))].into_iter(),
333        );
334        let actual = partial_iter.collect::<Vec<_>>();
335        let expected = (20..=30)
336            .map(|val| MemoryCell(Some(val)))
337            .collect::<Vec<_>>();
338        assert_eq!(actual.len(), expected.len());
339        assert_eq!(actual, expected);
340    }
341}