1use crate::{
2 persistence::DebuggerPaneItem,
3 tests::{start_debug_session, start_debug_session_with},
4 *,
5};
6use dap::{
7 ErrorResponse, Message, RunInTerminalRequestArguments, SourceBreakpoint,
8 StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
9 adapters::DebugTaskDefinition,
10 client::SessionId,
11 requests::{
12 Continue, Disconnect, Launch, Next, RunInTerminal, SetBreakpoints, StackTrace,
13 StartDebugging, StepBack, StepIn, StepOut, Threads,
14 },
15};
16use editor::{
17 ActiveDebugLine, Editor, EditorMode, MultiBuffer,
18 actions::{self},
19};
20use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
21use project::{
22 FakeFs, Project,
23 debugger::session::{ThreadId, ThreadStatus},
24};
25use serde_json::json;
26use std::{
27 collections::HashMap,
28 path::Path,
29 sync::{
30 Arc,
31 atomic::{AtomicBool, Ordering},
32 },
33};
34use task::LaunchRequest;
35use terminal_view::terminal_panel::TerminalPanel;
36use tests::{active_debug_session_panel, init_test, init_test_workspace};
37use util::path;
38use workspace::{Item, dock::Panel};
39
40#[gpui::test]
41async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut TestAppContext) {
42 init_test(cx);
43
44 let fs = FakeFs::new(executor.clone());
45
46 fs.insert_tree(
47 path!("/project"),
48 json!({
49 "main.rs": "First line\nSecond line\nThird line\nFourth line",
50 }),
51 )
52 .await;
53
54 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
55 let workspace = init_test_workspace(&project, cx).await;
56 let cx = &mut VisualTestContext::from_window(*workspace, cx);
57
58 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
59 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
60
61 client.on_request::<Threads, _>(move |_, _| {
62 Ok(dap::ThreadsResponse {
63 threads: vec![dap::Thread {
64 id: 1,
65 name: "Thread 1".into(),
66 }],
67 })
68 });
69
70 client.on_request::<StackTrace, _>(move |_, _| {
71 Ok(dap::StackTraceResponse {
72 stack_frames: Vec::default(),
73 total_frames: None,
74 })
75 });
76
77 cx.run_until_parked();
78
79 // assert we have a debug panel item before the session has stopped
80 workspace
81 .update(cx, |workspace, _window, cx| {
82 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
83 let active_session =
84 debug_panel.update(cx, |debug_panel, _| debug_panel.active_session().unwrap());
85
86 let running_state = active_session.update(cx, |active_session, _| {
87 active_session.running_state().clone()
88 });
89
90 debug_panel.update(cx, |this, cx| {
91 assert!(this.active_session().is_some());
92 assert!(running_state.read(cx).selected_thread_id().is_none());
93 });
94 })
95 .unwrap();
96
97 client
98 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
99 reason: dap::StoppedEventReason::Pause,
100 description: None,
101 thread_id: Some(1),
102 preserve_focus_hint: None,
103 text: None,
104 all_threads_stopped: None,
105 hit_breakpoint_ids: None,
106 }))
107 .await;
108
109 cx.run_until_parked();
110
111 workspace
112 .update(cx, |workspace, _window, cx| {
113 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
114 let active_session = debug_panel
115 .update(cx, |this, _| this.active_session())
116 .unwrap();
117
118 let running_state = active_session.update(cx, |active_session, _| {
119 active_session.running_state().clone()
120 });
121
122 assert_eq!(client.id(), running_state.read(cx).session_id());
123 assert_eq!(
124 ThreadId(1),
125 running_state.read(cx).selected_thread_id().unwrap()
126 );
127 })
128 .unwrap();
129
130 let shutdown_session = project.update(cx, |project, cx| {
131 project.dap_store().update(cx, |dap_store, cx| {
132 dap_store.shutdown_session(session.read(cx).session_id(), cx)
133 })
134 });
135
136 shutdown_session.await.unwrap();
137
138 // assert we still have a debug panel item after the client shutdown
139 workspace
140 .update(cx, |workspace, _window, cx| {
141 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
142
143 let active_session = debug_panel
144 .update(cx, |this, _| this.active_session())
145 .unwrap();
146
147 let running_state = active_session.update(cx, |active_session, _| {
148 active_session.running_state().clone()
149 });
150
151 debug_panel.update(cx, |this, cx| {
152 assert!(this.active_session().is_some());
153 assert_eq!(
154 ThreadId(1),
155 running_state.read(cx).selected_thread_id().unwrap()
156 );
157 });
158 })
159 .unwrap();
160}
161
162#[gpui::test]
163async fn test_we_can_only_have_one_panel_per_debug_session(
164 executor: BackgroundExecutor,
165 cx: &mut TestAppContext,
166) {
167 init_test(cx);
168
169 let fs = FakeFs::new(executor.clone());
170
171 fs.insert_tree(
172 path!("/project"),
173 json!({
174 "main.rs": "First line\nSecond line\nThird line\nFourth line",
175 }),
176 )
177 .await;
178
179 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
180 let workspace = init_test_workspace(&project, cx).await;
181 let cx = &mut VisualTestContext::from_window(*workspace, cx);
182
183 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
184 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
185
186 client.on_request::<Threads, _>(move |_, _| {
187 Ok(dap::ThreadsResponse {
188 threads: vec![dap::Thread {
189 id: 1,
190 name: "Thread 1".into(),
191 }],
192 })
193 });
194
195 client.on_request::<StackTrace, _>(move |_, _| {
196 Ok(dap::StackTraceResponse {
197 stack_frames: Vec::default(),
198 total_frames: None,
199 })
200 });
201
202 cx.run_until_parked();
203
204 // assert we have a debug panel item before the session has stopped
205 workspace
206 .update(cx, |workspace, _window, cx| {
207 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
208
209 debug_panel.update(cx, |this, _| {
210 assert!(this.active_session().is_some());
211 });
212 })
213 .unwrap();
214
215 client
216 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
217 reason: dap::StoppedEventReason::Pause,
218 description: None,
219 thread_id: Some(1),
220 preserve_focus_hint: None,
221 text: None,
222 all_threads_stopped: None,
223 hit_breakpoint_ids: None,
224 }))
225 .await;
226
227 cx.run_until_parked();
228
229 // assert we added a debug panel item
230 workspace
231 .update(cx, |workspace, _window, cx| {
232 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
233 let active_session = debug_panel
234 .update(cx, |this, _| this.active_session())
235 .unwrap();
236
237 let running_state = active_session.update(cx, |active_session, _| {
238 active_session.running_state().clone()
239 });
240
241 assert_eq!(client.id(), active_session.read(cx).session_id(cx));
242 assert_eq!(
243 ThreadId(1),
244 running_state.read(cx).selected_thread_id().unwrap()
245 );
246 })
247 .unwrap();
248
249 client
250 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
251 reason: dap::StoppedEventReason::Pause,
252 description: None,
253 thread_id: Some(2),
254 preserve_focus_hint: None,
255 text: None,
256 all_threads_stopped: None,
257 hit_breakpoint_ids: None,
258 }))
259 .await;
260
261 cx.run_until_parked();
262
263 workspace
264 .update(cx, |workspace, _window, cx| {
265 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
266 let active_session = debug_panel
267 .update(cx, |this, _| this.active_session())
268 .unwrap();
269
270 let running_state = active_session.update(cx, |active_session, _| {
271 active_session.running_state().clone()
272 });
273
274 assert_eq!(client.id(), active_session.read(cx).session_id(cx));
275 assert_eq!(
276 ThreadId(1),
277 running_state.read(cx).selected_thread_id().unwrap()
278 );
279 })
280 .unwrap();
281
282 let shutdown_session = project.update(cx, |project, cx| {
283 project.dap_store().update(cx, |dap_store, cx| {
284 dap_store.shutdown_session(session.read(cx).session_id(), cx)
285 })
286 });
287
288 shutdown_session.await.unwrap();
289
290 // assert we still have a debug panel item after the client shutdown
291 workspace
292 .update(cx, |workspace, _window, cx| {
293 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
294 let active_session = debug_panel
295 .update(cx, |this, _| this.active_session())
296 .unwrap();
297
298 let running_state = active_session.update(cx, |active_session, _| {
299 active_session.running_state().clone()
300 });
301
302 debug_panel.update(cx, |this, cx| {
303 assert!(this.active_session().is_some());
304 assert_eq!(
305 ThreadId(1),
306 running_state.read(cx).selected_thread_id().unwrap()
307 );
308 });
309 })
310 .unwrap();
311}
312
313#[gpui::test]
314async fn test_handle_successful_run_in_terminal_reverse_request(
315 executor: BackgroundExecutor,
316 cx: &mut TestAppContext,
317) {
318 init_test(cx);
319
320 let send_response = Arc::new(AtomicBool::new(false));
321
322 let fs = FakeFs::new(executor.clone());
323
324 fs.insert_tree(
325 path!("/project"),
326 json!({
327 "main.rs": "First line\nSecond line\nThird line\nFourth line",
328 }),
329 )
330 .await;
331
332 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
333 let workspace = init_test_workspace(&project, cx).await;
334 let cx = &mut VisualTestContext::from_window(*workspace, cx);
335
336 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
337 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
338
339 client
340 .on_response::<RunInTerminal, _>({
341 let send_response = send_response.clone();
342 move |response| {
343 send_response.store(true, Ordering::SeqCst);
344
345 assert!(response.success);
346 assert!(response.body.is_some());
347 }
348 })
349 .await;
350
351 client
352 .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
353 kind: None,
354 title: None,
355 cwd: std::env::temp_dir().to_string_lossy().to_string(),
356 args: vec![],
357 env: None,
358 args_can_be_interpreted_by_shell: None,
359 })
360 .await;
361
362 cx.run_until_parked();
363
364 assert!(
365 send_response.load(std::sync::atomic::Ordering::SeqCst),
366 "Expected to receive response from reverse request"
367 );
368
369 workspace
370 .update(cx, |workspace, _window, cx| {
371 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
372 let session = debug_panel.read(cx).active_session().unwrap();
373 let running = session.read(cx).running_state();
374 assert_eq!(
375 running
376 .read(cx)
377 .pane_items_status(cx)
378 .get(&DebuggerPaneItem::Terminal),
379 Some(&true)
380 );
381 assert!(running.read(cx).debug_terminal.read(cx).terminal.is_some());
382 })
383 .unwrap();
384}
385
386#[gpui::test]
387async fn test_handle_start_debugging_request(
388 executor: BackgroundExecutor,
389 cx: &mut TestAppContext,
390) {
391 init_test(cx);
392
393 let fs = FakeFs::new(executor.clone());
394
395 fs.insert_tree(
396 path!("/project"),
397 json!({
398 "main.rs": "First line\nSecond line\nThird line\nFourth line",
399 }),
400 )
401 .await;
402
403 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
404 let workspace = init_test_workspace(&project, cx).await;
405 let cx = &mut VisualTestContext::from_window(*workspace, cx);
406
407 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
408 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
409
410 let fake_config = json!({"one": "two"});
411 let launched_with = Arc::new(parking_lot::Mutex::new(None));
412
413 let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
414 let launched_with = launched_with.clone();
415 move |client| {
416 let launched_with = launched_with.clone();
417 client.on_request::<dap::requests::Launch, _>(move |_, args| {
418 launched_with.lock().replace(args.raw);
419 Ok(())
420 });
421 client.on_request::<dap::requests::Attach, _>(move |_, _| {
422 assert!(false, "should not get attach request");
423 Ok(())
424 });
425 }
426 });
427
428 client
429 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
430 request: StartDebuggingRequestArgumentsRequest::Launch,
431 configuration: fake_config.clone(),
432 })
433 .await;
434
435 cx.run_until_parked();
436
437 workspace
438 .update(cx, |workspace, _window, cx| {
439 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
440 let active_session = debug_panel
441 .read(cx)
442 .active_session()
443 .unwrap()
444 .read(cx)
445 .session(cx);
446 let parent_session = active_session.read(cx).parent_session().unwrap();
447 let mut original_binary = parent_session.read(cx).binary().clone();
448 original_binary.request_args = StartDebuggingRequestArguments {
449 request: StartDebuggingRequestArgumentsRequest::Launch,
450 configuration: fake_config.clone(),
451 };
452
453 assert_eq!(active_session.read(cx).binary(), &original_binary);
454 })
455 .unwrap();
456
457 assert_eq!(&fake_config, launched_with.lock().as_ref().unwrap());
458}
459
460// // covers that we always send a response back, if something when wrong,
461// // while spawning the terminal
462#[gpui::test]
463async fn test_handle_error_run_in_terminal_reverse_request(
464 executor: BackgroundExecutor,
465 cx: &mut TestAppContext,
466) {
467 init_test(cx);
468
469 let send_response = Arc::new(AtomicBool::new(false));
470
471 let fs = FakeFs::new(executor.clone());
472
473 fs.insert_tree(
474 path!("/project"),
475 json!({
476 "main.rs": "First line\nSecond line\nThird line\nFourth line",
477 }),
478 )
479 .await;
480
481 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
482 let workspace = init_test_workspace(&project, cx).await;
483 let cx = &mut VisualTestContext::from_window(*workspace, cx);
484
485 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
486 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
487
488 client
489 .on_response::<RunInTerminal, _>({
490 let send_response = send_response.clone();
491 move |response| {
492 send_response.store(true, Ordering::SeqCst);
493
494 assert!(!response.success);
495 assert!(response.body.is_some());
496 }
497 })
498 .await;
499
500 client
501 .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
502 kind: None,
503 title: None,
504 cwd: "".into(),
505 args: vec!["oops".into(), "oops".into()],
506 env: None,
507 args_can_be_interpreted_by_shell: None,
508 })
509 .await;
510
511 cx.run_until_parked();
512
513 assert!(
514 send_response.load(std::sync::atomic::Ordering::SeqCst),
515 "Expected to receive response from reverse request"
516 );
517
518 workspace
519 .update(cx, |workspace, _window, cx| {
520 let terminal_panel = workspace.panel::<TerminalPanel>(cx).unwrap();
521
522 assert_eq!(
523 0,
524 terminal_panel.read(cx).pane().unwrap().read(cx).items_len()
525 );
526 })
527 .unwrap();
528}
529
530#[gpui::test]
531async fn test_handle_start_debugging_reverse_request(
532 executor: BackgroundExecutor,
533 cx: &mut TestAppContext,
534) {
535 init_test(cx);
536
537 let send_response = Arc::new(AtomicBool::new(false));
538
539 let fs = FakeFs::new(executor.clone());
540
541 fs.insert_tree(
542 path!("/project"),
543 json!({
544 "main.rs": "First line\nSecond line\nThird line\nFourth line",
545 }),
546 )
547 .await;
548
549 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
550 let workspace = init_test_workspace(&project, cx).await;
551 let cx = &mut VisualTestContext::from_window(*workspace, cx);
552
553 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
554 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
555
556 client.on_request::<dap::requests::Threads, _>(move |_, _| {
557 Ok(dap::ThreadsResponse {
558 threads: vec![dap::Thread {
559 id: 1,
560 name: "Thread 1".into(),
561 }],
562 })
563 });
564
565 client
566 .on_response::<StartDebugging, _>({
567 let send_response = send_response.clone();
568 move |response| {
569 send_response.store(true, Ordering::SeqCst);
570
571 assert!(response.success);
572 assert!(response.body.is_some());
573 }
574 })
575 .await;
576 // Set up handlers for sessions spawned with reverse request too.
577 let _reverse_request_subscription =
578 project::debugger::test::intercept_debug_sessions(cx, |_| {});
579 client
580 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
581 configuration: json!({}),
582 request: StartDebuggingRequestArgumentsRequest::Launch,
583 })
584 .await;
585
586 cx.run_until_parked();
587
588 let child_session = project.update(cx, |project, cx| {
589 project
590 .dap_store()
591 .read(cx)
592 .session_by_id(SessionId(1))
593 .unwrap()
594 });
595 let child_client = child_session.update(cx, |session, _| session.adapter_client().unwrap());
596
597 child_client.on_request::<dap::requests::Threads, _>(move |_, _| {
598 Ok(dap::ThreadsResponse {
599 threads: vec![dap::Thread {
600 id: 1,
601 name: "Thread 1".into(),
602 }],
603 })
604 });
605
606 child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
607
608 child_client
609 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
610 reason: dap::StoppedEventReason::Pause,
611 description: None,
612 thread_id: Some(2),
613 preserve_focus_hint: None,
614 text: None,
615 all_threads_stopped: None,
616 hit_breakpoint_ids: None,
617 }))
618 .await;
619
620 cx.run_until_parked();
621
622 assert!(
623 send_response.load(std::sync::atomic::Ordering::SeqCst),
624 "Expected to receive response from reverse request"
625 );
626}
627
628#[gpui::test]
629async fn test_shutdown_children_when_parent_session_shutdown(
630 executor: BackgroundExecutor,
631 cx: &mut TestAppContext,
632) {
633 init_test(cx);
634
635 let fs = FakeFs::new(executor.clone());
636
637 fs.insert_tree(
638 path!("/project"),
639 json!({
640 "main.rs": "First line\nSecond line\nThird line\nFourth line",
641 }),
642 )
643 .await;
644
645 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
646 let dap_store = project.update(cx, |project, _| project.dap_store());
647 let workspace = init_test_workspace(&project, cx).await;
648 let cx = &mut VisualTestContext::from_window(*workspace, cx);
649
650 let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
651 let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
652
653 client.on_request::<dap::requests::Threads, _>(move |_, _| {
654 Ok(dap::ThreadsResponse {
655 threads: vec![dap::Thread {
656 id: 1,
657 name: "Thread 1".into(),
658 }],
659 })
660 });
661
662 client.on_response::<StartDebugging, _>(move |_| {}).await;
663 // Set up handlers for sessions spawned with reverse request too.
664 let _reverse_request_subscription =
665 project::debugger::test::intercept_debug_sessions(cx, |_| {});
666 // start first child session
667 client
668 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
669 configuration: json!({}),
670 request: StartDebuggingRequestArgumentsRequest::Launch,
671 })
672 .await;
673
674 cx.run_until_parked();
675
676 // start second child session
677 client
678 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
679 configuration: json!({}),
680 request: StartDebuggingRequestArgumentsRequest::Launch,
681 })
682 .await;
683
684 cx.run_until_parked();
685
686 // configure first child session
687 let first_child_session = dap_store.read_with(cx, |dap_store, _| {
688 dap_store.session_by_id(SessionId(1)).unwrap()
689 });
690 let first_child_client =
691 first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
692
693 first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
694
695 // configure second child session
696 let second_child_session = dap_store.read_with(cx, |dap_store, _| {
697 dap_store.session_by_id(SessionId(2)).unwrap()
698 });
699 let second_child_client =
700 second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
701
702 second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
703
704 cx.run_until_parked();
705
706 // shutdown parent session
707 dap_store
708 .update(cx, |dap_store, cx| {
709 dap_store.shutdown_session(parent_session.read(cx).session_id(), cx)
710 })
711 .await
712 .unwrap();
713
714 // assert parent session and all children sessions are shutdown
715 dap_store.update(cx, |dap_store, cx| {
716 assert!(
717 dap_store
718 .session_by_id(parent_session.read(cx).session_id())
719 .is_none()
720 );
721 assert!(
722 dap_store
723 .session_by_id(first_child_session.read(cx).session_id())
724 .is_none()
725 );
726 assert!(
727 dap_store
728 .session_by_id(second_child_session.read(cx).session_id())
729 .is_none()
730 );
731 });
732}
733
734#[gpui::test]
735async fn test_shutdown_parent_session_if_all_children_are_shutdown(
736 executor: BackgroundExecutor,
737 cx: &mut TestAppContext,
738) {
739 init_test(cx);
740
741 let fs = FakeFs::new(executor.clone());
742
743 fs.insert_tree(
744 path!("/project"),
745 json!({
746 "main.rs": "First line\nSecond line\nThird line\nFourth line",
747 }),
748 )
749 .await;
750
751 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
752 let dap_store = project.update(cx, |project, _| project.dap_store());
753 let workspace = init_test_workspace(&project, cx).await;
754 let cx = &mut VisualTestContext::from_window(*workspace, cx);
755
756 let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
757 let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
758
759 client.on_response::<StartDebugging, _>(move |_| {}).await;
760 // Set up handlers for sessions spawned with reverse request too.
761 let _reverse_request_subscription =
762 project::debugger::test::intercept_debug_sessions(cx, |_| {});
763 // start first child session
764 client
765 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
766 configuration: json!({}),
767 request: StartDebuggingRequestArgumentsRequest::Launch,
768 })
769 .await;
770
771 cx.run_until_parked();
772
773 // start second child session
774 client
775 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
776 configuration: json!({}),
777 request: StartDebuggingRequestArgumentsRequest::Launch,
778 })
779 .await;
780
781 cx.run_until_parked();
782
783 // configure first child session
784 let first_child_session = dap_store.read_with(cx, |dap_store, _| {
785 dap_store.session_by_id(SessionId(1)).unwrap()
786 });
787 let first_child_client =
788 first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
789
790 first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
791
792 // configure second child session
793 let second_child_session = dap_store.read_with(cx, |dap_store, _| {
794 dap_store.session_by_id(SessionId(2)).unwrap()
795 });
796 let second_child_client =
797 second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
798
799 second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
800
801 cx.run_until_parked();
802
803 // shutdown first child session
804 dap_store
805 .update(cx, |dap_store, cx| {
806 dap_store.shutdown_session(first_child_session.read(cx).session_id(), cx)
807 })
808 .await
809 .unwrap();
810
811 // assert parent session and second child session still exist
812 dap_store.update(cx, |dap_store, cx| {
813 assert!(
814 dap_store
815 .session_by_id(parent_session.read(cx).session_id())
816 .is_some()
817 );
818 assert!(
819 dap_store
820 .session_by_id(first_child_session.read(cx).session_id())
821 .is_none()
822 );
823 assert!(
824 dap_store
825 .session_by_id(second_child_session.read(cx).session_id())
826 .is_some()
827 );
828 });
829
830 // shutdown first child session
831 dap_store
832 .update(cx, |dap_store, cx| {
833 dap_store.shutdown_session(second_child_session.read(cx).session_id(), cx)
834 })
835 .await
836 .unwrap();
837
838 // assert parent session got shutdown by second child session
839 // because it was the last child
840 dap_store.update(cx, |dap_store, cx| {
841 assert!(
842 dap_store
843 .session_by_id(parent_session.read(cx).session_id())
844 .is_none()
845 );
846 assert!(
847 dap_store
848 .session_by_id(second_child_session.read(cx).session_id())
849 .is_none()
850 );
851 });
852}
853
854#[gpui::test]
855async fn test_debug_panel_item_thread_status_reset_on_failure(
856 executor: BackgroundExecutor,
857 cx: &mut TestAppContext,
858) {
859 init_test(cx);
860
861 let fs = FakeFs::new(executor.clone());
862
863 fs.insert_tree(
864 path!("/project"),
865 json!({
866 "main.rs": "First line\nSecond line\nThird line\nFourth line",
867 }),
868 )
869 .await;
870
871 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
872 let workspace = init_test_workspace(&project, cx).await;
873 let cx = &mut VisualTestContext::from_window(*workspace, cx);
874
875 let session = start_debug_session(&workspace, cx, |client| {
876 client.on_request::<dap::requests::Initialize, _>(move |_, _| {
877 Ok(dap::Capabilities {
878 supports_step_back: Some(true),
879 ..Default::default()
880 })
881 });
882 })
883 .unwrap();
884
885 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
886 const THREAD_ID_NUM: u64 = 1;
887
888 client.on_request::<dap::requests::Threads, _>(move |_, _| {
889 Ok(dap::ThreadsResponse {
890 threads: vec![dap::Thread {
891 id: THREAD_ID_NUM,
892 name: "Thread 1".into(),
893 }],
894 })
895 });
896
897 client.on_request::<Launch, _>(move |_, _| Ok(()));
898
899 client.on_request::<StackTrace, _>(move |_, _| {
900 Ok(dap::StackTraceResponse {
901 stack_frames: Vec::default(),
902 total_frames: None,
903 })
904 });
905
906 client.on_request::<Next, _>(move |_, _| {
907 Err(ErrorResponse {
908 error: Some(dap::Message {
909 id: 1,
910 format: "error".into(),
911 variables: None,
912 send_telemetry: None,
913 show_user: None,
914 url: None,
915 url_label: None,
916 }),
917 })
918 });
919
920 client.on_request::<StepOut, _>(move |_, _| {
921 Err(ErrorResponse {
922 error: Some(dap::Message {
923 id: 1,
924 format: "error".into(),
925 variables: None,
926 send_telemetry: None,
927 show_user: None,
928 url: None,
929 url_label: None,
930 }),
931 })
932 });
933
934 client.on_request::<StepIn, _>(move |_, _| {
935 Err(ErrorResponse {
936 error: Some(dap::Message {
937 id: 1,
938 format: "error".into(),
939 variables: None,
940 send_telemetry: None,
941 show_user: None,
942 url: None,
943 url_label: None,
944 }),
945 })
946 });
947
948 client.on_request::<StepBack, _>(move |_, _| {
949 Err(ErrorResponse {
950 error: Some(dap::Message {
951 id: 1,
952 format: "error".into(),
953 variables: None,
954 send_telemetry: None,
955 show_user: None,
956 url: None,
957 url_label: None,
958 }),
959 })
960 });
961
962 client.on_request::<Continue, _>(move |_, _| {
963 Err(ErrorResponse {
964 error: Some(dap::Message {
965 id: 1,
966 format: "error".into(),
967 variables: None,
968 send_telemetry: None,
969 show_user: None,
970 url: None,
971 url_label: None,
972 }),
973 })
974 });
975
976 client
977 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
978 reason: dap::StoppedEventReason::Pause,
979 description: None,
980 thread_id: Some(1),
981 preserve_focus_hint: None,
982 text: None,
983 all_threads_stopped: None,
984 hit_breakpoint_ids: None,
985 }))
986 .await;
987
988 cx.run_until_parked();
989
990 let running_state = active_debug_session_panel(workspace, cx)
991 .update(cx, |item, _| item.running_state().clone());
992
993 cx.run_until_parked();
994 let thread_id = ThreadId(1);
995
996 for operation in &[
997 "step_over",
998 "continue_thread",
999 "step_back",
1000 "step_in",
1001 "step_out",
1002 ] {
1003 running_state.update(cx, |running_state, cx| match *operation {
1004 "step_over" => running_state.step_over(cx),
1005 "continue_thread" => running_state.continue_thread(cx),
1006 "step_back" => running_state.step_back(cx),
1007 "step_in" => running_state.step_in(cx),
1008 "step_out" => running_state.step_out(cx),
1009 _ => unreachable!(),
1010 });
1011
1012 // Check that we step the thread status to the correct intermediate state
1013 running_state.update(cx, |running_state, cx| {
1014 assert_eq!(
1015 running_state
1016 .thread_status(cx)
1017 .expect("There should be an active thread selected"),
1018 match *operation {
1019 "continue_thread" => ThreadStatus::Running,
1020 _ => ThreadStatus::Stepping,
1021 },
1022 "Thread status was not set to correct intermediate state after {} request",
1023 operation
1024 );
1025 });
1026
1027 cx.run_until_parked();
1028
1029 running_state.update(cx, |running_state, cx| {
1030 assert_eq!(
1031 running_state
1032 .thread_status(cx)
1033 .expect("There should be an active thread selected"),
1034 ThreadStatus::Stopped,
1035 "Thread status not reset to Stopped after failed {}",
1036 operation
1037 );
1038
1039 // update state to running, so we can test it actually changes the status back to stopped
1040 running_state
1041 .session()
1042 .update(cx, |session, cx| session.continue_thread(thread_id, cx));
1043 });
1044 }
1045}
1046
1047#[gpui::test]
1048async fn test_send_breakpoints_when_editor_has_been_saved(
1049 executor: BackgroundExecutor,
1050 cx: &mut TestAppContext,
1051) {
1052 init_test(cx);
1053
1054 let fs = FakeFs::new(executor.clone());
1055
1056 fs.insert_tree(
1057 path!("/project"),
1058 json!({
1059 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1060 }),
1061 )
1062 .await;
1063
1064 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1065 let workspace = init_test_workspace(&project, cx).await;
1066 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1067 let project_path = Path::new(path!("/project"));
1068 let worktree = project
1069 .update(cx, |project, cx| project.find_worktree(project_path, cx))
1070 .expect("This worktree should exist in project")
1071 .0;
1072
1073 let worktree_id = workspace
1074 .update(cx, |_, _, cx| worktree.read(cx).id())
1075 .unwrap();
1076
1077 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1078 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1079
1080 let buffer = project
1081 .update(cx, |project, cx| {
1082 project.open_buffer((worktree_id, "main.rs"), cx)
1083 })
1084 .await
1085 .unwrap();
1086
1087 let (editor, cx) = cx.add_window_view(|window, cx| {
1088 Editor::new(
1089 EditorMode::full(),
1090 MultiBuffer::build_from_buffer(buffer, cx),
1091 Some(project.clone()),
1092 window,
1093 cx,
1094 )
1095 });
1096
1097 client.on_request::<Launch, _>(move |_, _| Ok(()));
1098
1099 client.on_request::<StackTrace, _>(move |_, _| {
1100 Ok(dap::StackTraceResponse {
1101 stack_frames: Vec::default(),
1102 total_frames: None,
1103 })
1104 });
1105
1106 client
1107 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1108 reason: dap::StoppedEventReason::Pause,
1109 description: None,
1110 thread_id: Some(1),
1111 preserve_focus_hint: None,
1112 text: None,
1113 all_threads_stopped: None,
1114 hit_breakpoint_ids: None,
1115 }))
1116 .await;
1117
1118 let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1119 client.on_request::<SetBreakpoints, _>({
1120 let called_set_breakpoints = called_set_breakpoints.clone();
1121 move |_, args| {
1122 assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1123 assert_eq!(
1124 vec![SourceBreakpoint {
1125 line: 2,
1126 column: None,
1127 condition: None,
1128 hit_condition: None,
1129 log_message: None,
1130 mode: None
1131 }],
1132 args.breakpoints.unwrap()
1133 );
1134 assert!(!args.source_modified.unwrap());
1135
1136 called_set_breakpoints.store(true, Ordering::SeqCst);
1137
1138 Ok(dap::SetBreakpointsResponse {
1139 breakpoints: Vec::default(),
1140 })
1141 }
1142 });
1143
1144 editor.update_in(cx, |editor, window, cx| {
1145 editor.move_down(&actions::MoveDown, window, cx);
1146 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1147 });
1148
1149 cx.run_until_parked();
1150
1151 assert!(
1152 called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1153 "SetBreakpoint request must be called"
1154 );
1155
1156 let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1157 client.on_request::<SetBreakpoints, _>({
1158 let called_set_breakpoints = called_set_breakpoints.clone();
1159 move |_, args| {
1160 assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1161 assert_eq!(
1162 vec![SourceBreakpoint {
1163 line: 3,
1164 column: None,
1165 condition: None,
1166 hit_condition: None,
1167 log_message: None,
1168 mode: None
1169 }],
1170 args.breakpoints.unwrap()
1171 );
1172 assert!(args.source_modified.unwrap());
1173
1174 called_set_breakpoints.store(true, Ordering::SeqCst);
1175
1176 Ok(dap::SetBreakpointsResponse {
1177 breakpoints: Vec::default(),
1178 })
1179 }
1180 });
1181
1182 editor.update_in(cx, |editor, window, cx| {
1183 editor.move_up(&actions::MoveUp, window, cx);
1184 editor.insert("new text\n", window, cx);
1185 });
1186
1187 editor
1188 .update_in(cx, |editor, window, cx| {
1189 editor.save(true, project.clone(), window, cx)
1190 })
1191 .await
1192 .unwrap();
1193
1194 cx.run_until_parked();
1195
1196 assert!(
1197 called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1198 "SetBreakpoint request must be called after editor is saved"
1199 );
1200}
1201
1202#[gpui::test]
1203async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
1204 executor: BackgroundExecutor,
1205 cx: &mut TestAppContext,
1206) {
1207 init_test(cx);
1208
1209 let fs = FakeFs::new(executor.clone());
1210
1211 fs.insert_tree(
1212 path!("/project"),
1213 json!({
1214 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1215 "second.rs": "First line\nSecond line\nThird line\nFourth line",
1216 "no_breakpoints.rs": "Used to ensure that we don't unset breakpoint in files with no breakpoints"
1217 }),
1218 )
1219 .await;
1220
1221 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1222 let workspace = init_test_workspace(&project, cx).await;
1223 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1224 let project_path = Path::new(path!("/project"));
1225 let worktree = project
1226 .update(cx, |project, cx| project.find_worktree(project_path, cx))
1227 .expect("This worktree should exist in project")
1228 .0;
1229
1230 let worktree_id = workspace
1231 .update(cx, |_, _, cx| worktree.read(cx).id())
1232 .unwrap();
1233
1234 let first = project
1235 .update(cx, |project, cx| {
1236 project.open_buffer((worktree_id, "main.rs"), cx)
1237 })
1238 .await
1239 .unwrap();
1240
1241 let second = project
1242 .update(cx, |project, cx| {
1243 project.open_buffer((worktree_id, "second.rs"), cx)
1244 })
1245 .await
1246 .unwrap();
1247
1248 let (first_editor, cx) = cx.add_window_view(|window, cx| {
1249 Editor::new(
1250 EditorMode::full(),
1251 MultiBuffer::build_from_buffer(first, cx),
1252 Some(project.clone()),
1253 window,
1254 cx,
1255 )
1256 });
1257
1258 let (second_editor, cx) = cx.add_window_view(|window, cx| {
1259 Editor::new(
1260 EditorMode::full(),
1261 MultiBuffer::build_from_buffer(second, cx),
1262 Some(project.clone()),
1263 window,
1264 cx,
1265 )
1266 });
1267
1268 first_editor.update_in(cx, |editor, window, cx| {
1269 editor.move_down(&actions::MoveDown, window, cx);
1270 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1271 editor.move_down(&actions::MoveDown, window, cx);
1272 editor.move_down(&actions::MoveDown, window, cx);
1273 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1274 });
1275
1276 second_editor.update_in(cx, |editor, window, cx| {
1277 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1278 editor.move_down(&actions::MoveDown, window, cx);
1279 editor.move_down(&actions::MoveDown, window, cx);
1280 editor.move_down(&actions::MoveDown, window, cx);
1281 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1282 });
1283
1284 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1285 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1286
1287 let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1288
1289 client.on_request::<SetBreakpoints, _>({
1290 let called_set_breakpoints = called_set_breakpoints.clone();
1291 move |_, args| {
1292 assert!(
1293 args.breakpoints.is_none_or(|bps| bps.is_empty()),
1294 "Send empty breakpoint sets to clear them from DAP servers"
1295 );
1296
1297 match args
1298 .source
1299 .path
1300 .expect("We should always send a breakpoint's path")
1301 .as_str()
1302 {
1303 path!("/project/main.rs") | path!("/project/second.rs") => {}
1304 _ => {
1305 panic!("Unset breakpoints for path that doesn't have any")
1306 }
1307 }
1308
1309 called_set_breakpoints.store(true, Ordering::SeqCst);
1310
1311 Ok(dap::SetBreakpointsResponse {
1312 breakpoints: Vec::default(),
1313 })
1314 }
1315 });
1316
1317 cx.dispatch_action(crate::ClearAllBreakpoints);
1318 cx.run_until_parked();
1319}
1320
1321#[gpui::test]
1322async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
1323 executor: BackgroundExecutor,
1324 cx: &mut TestAppContext,
1325) {
1326 init_test(cx);
1327
1328 let fs = FakeFs::new(executor.clone());
1329
1330 fs.insert_tree(
1331 path!("/project"),
1332 json!({
1333 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1334 }),
1335 )
1336 .await;
1337
1338 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1339 let workspace = init_test_workspace(&project, cx).await;
1340 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1341
1342 start_debug_session(&workspace, cx, |client| {
1343 client.on_request::<dap::requests::Initialize, _>(|_, _| {
1344 Err(ErrorResponse {
1345 error: Some(Message {
1346 format: "failed to launch".to_string(),
1347 id: 1,
1348 variables: None,
1349 send_telemetry: None,
1350 show_user: None,
1351 url: None,
1352 url_label: None,
1353 }),
1354 })
1355 });
1356 })
1357 .ok();
1358
1359 cx.run_until_parked();
1360
1361 project.update(cx, |project, cx| {
1362 assert!(
1363 project.dap_store().read(cx).sessions().count() == 0,
1364 "Session wouldn't exist if it was shutdown"
1365 );
1366 });
1367}
1368
1369#[gpui::test]
1370async fn test_we_send_arguments_from_user_config(
1371 executor: BackgroundExecutor,
1372 cx: &mut TestAppContext,
1373) {
1374 init_test(cx);
1375
1376 let fs = FakeFs::new(executor.clone());
1377
1378 fs.insert_tree(
1379 path!("/project"),
1380 json!({
1381 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1382 }),
1383 )
1384 .await;
1385
1386 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1387 let workspace = init_test_workspace(&project, cx).await;
1388 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1389 let debug_definition = DebugTaskDefinition {
1390 adapter: "fake-adapter".into(),
1391 request: dap::DebugRequest::Launch(LaunchRequest {
1392 program: "main.rs".to_owned(),
1393 args: vec!["arg1".to_owned(), "arg2".to_owned()],
1394 cwd: Some(path!("/Random_path").into()),
1395 env: HashMap::from_iter(vec![("KEY".to_owned(), "VALUE".to_owned())]),
1396 }),
1397 label: "test".into(),
1398 initialize_args: None,
1399 tcp_connection: None,
1400 stop_on_entry: None,
1401 };
1402
1403 let launch_handler_called = Arc::new(AtomicBool::new(false));
1404
1405 start_debug_session_with(&workspace, cx, debug_definition.clone(), {
1406 let debug_definition = debug_definition.clone();
1407 let launch_handler_called = launch_handler_called.clone();
1408
1409 move |client| {
1410 let debug_definition = debug_definition.clone();
1411 let launch_handler_called = launch_handler_called.clone();
1412
1413 client.on_request::<dap::requests::Launch, _>(move |_, args| {
1414 launch_handler_called.store(true, Ordering::SeqCst);
1415
1416 let obj = args.raw.as_object().unwrap();
1417 let sent_definition = serde_json::from_value::<DebugTaskDefinition>(
1418 obj.get(&"raw_request".to_owned()).unwrap().clone(),
1419 )
1420 .unwrap();
1421
1422 assert_eq!(sent_definition, debug_definition);
1423
1424 Ok(())
1425 });
1426 }
1427 })
1428 .ok();
1429
1430 cx.run_until_parked();
1431
1432 assert!(
1433 launch_handler_called.load(Ordering::SeqCst),
1434 "Launch request handler was not called"
1435 );
1436}
1437
1438#[gpui::test]
1439async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut TestAppContext) {
1440 init_test(cx);
1441
1442 let fs = FakeFs::new(executor.clone());
1443
1444 fs.insert_tree(
1445 path!("/project"),
1446 json!({
1447 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1448 "second.rs": "First line\nSecond line\nThird line\nFourth line",
1449 }),
1450 )
1451 .await;
1452
1453 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1454 let workspace = init_test_workspace(&project, cx).await;
1455 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1456 let project_path = Path::new(path!("/project"));
1457 let worktree = project
1458 .update(cx, |project, cx| project.find_worktree(project_path, cx))
1459 .expect("This worktree should exist in project")
1460 .0;
1461
1462 let worktree_id = workspace
1463 .update(cx, |_, _, cx| worktree.read(cx).id())
1464 .unwrap();
1465
1466 let main_buffer = project
1467 .update(cx, |project, cx| {
1468 project.open_buffer((worktree_id, "main.rs"), cx)
1469 })
1470 .await
1471 .unwrap();
1472
1473 let second_buffer = project
1474 .update(cx, |project, cx| {
1475 project.open_buffer((worktree_id, "second.rs"), cx)
1476 })
1477 .await
1478 .unwrap();
1479
1480 let (main_editor, cx) = cx.add_window_view(|window, cx| {
1481 Editor::new(
1482 EditorMode::full(),
1483 MultiBuffer::build_from_buffer(main_buffer, cx),
1484 Some(project.clone()),
1485 window,
1486 cx,
1487 )
1488 });
1489
1490 let (second_editor, cx) = cx.add_window_view(|window, cx| {
1491 Editor::new(
1492 EditorMode::full(),
1493 MultiBuffer::build_from_buffer(second_buffer, cx),
1494 Some(project.clone()),
1495 window,
1496 cx,
1497 )
1498 });
1499
1500 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1501 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1502
1503 client.on_request::<dap::requests::Threads, _>(move |_, _| {
1504 Ok(dap::ThreadsResponse {
1505 threads: vec![dap::Thread {
1506 id: 1,
1507 name: "Thread 1".into(),
1508 }],
1509 })
1510 });
1511
1512 client.on_request::<dap::requests::Scopes, _>(move |_, _| {
1513 Ok(dap::ScopesResponse {
1514 scopes: Vec::default(),
1515 })
1516 });
1517
1518 client.on_request::<StackTrace, _>(move |_, args| {
1519 assert_eq!(args.thread_id, 1);
1520
1521 Ok(dap::StackTraceResponse {
1522 stack_frames: vec![dap::StackFrame {
1523 id: 1,
1524 name: "frame 1".into(),
1525 source: Some(dap::Source {
1526 name: Some("main.rs".into()),
1527 path: Some(path!("/project/main.rs").into()),
1528 source_reference: None,
1529 presentation_hint: None,
1530 origin: None,
1531 sources: None,
1532 adapter_data: None,
1533 checksums: None,
1534 }),
1535 line: 2,
1536 column: 0,
1537 end_line: None,
1538 end_column: None,
1539 can_restart: None,
1540 instruction_pointer_reference: None,
1541 module_id: None,
1542 presentation_hint: None,
1543 }],
1544 total_frames: None,
1545 })
1546 });
1547
1548 client
1549 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1550 reason: dap::StoppedEventReason::Breakpoint,
1551 description: None,
1552 thread_id: Some(1),
1553 preserve_focus_hint: None,
1554 text: None,
1555 all_threads_stopped: None,
1556 hit_breakpoint_ids: None,
1557 }))
1558 .await;
1559
1560 cx.run_until_parked();
1561
1562 main_editor.update_in(cx, |editor, window, cx| {
1563 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1564
1565 assert_eq!(
1566 active_debug_lines.len(),
1567 1,
1568 "There should be only one active debug line"
1569 );
1570
1571 let point = editor
1572 .snapshot(window, cx)
1573 .buffer_snapshot
1574 .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1575
1576 assert_eq!(point.row, 1);
1577 });
1578
1579 second_editor.update(cx, |editor, _| {
1580 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1581
1582 assert!(
1583 active_debug_lines.is_empty(),
1584 "There shouldn't be any active debug lines"
1585 );
1586 });
1587
1588 let handled_second_stacktrace = Arc::new(AtomicBool::new(false));
1589 client.on_request::<StackTrace, _>({
1590 let handled_second_stacktrace = handled_second_stacktrace.clone();
1591 move |_, args| {
1592 handled_second_stacktrace.store(true, Ordering::SeqCst);
1593 assert_eq!(args.thread_id, 1);
1594
1595 Ok(dap::StackTraceResponse {
1596 stack_frames: vec![dap::StackFrame {
1597 id: 2,
1598 name: "frame 2".into(),
1599 source: Some(dap::Source {
1600 name: Some("second.rs".into()),
1601 path: Some(path!("/project/second.rs").into()),
1602 source_reference: None,
1603 presentation_hint: None,
1604 origin: None,
1605 sources: None,
1606 adapter_data: None,
1607 checksums: None,
1608 }),
1609 line: 3,
1610 column: 0,
1611 end_line: None,
1612 end_column: None,
1613 can_restart: None,
1614 instruction_pointer_reference: None,
1615 module_id: None,
1616 presentation_hint: None,
1617 }],
1618 total_frames: None,
1619 })
1620 }
1621 });
1622
1623 client
1624 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1625 reason: dap::StoppedEventReason::Breakpoint,
1626 description: None,
1627 thread_id: Some(1),
1628 preserve_focus_hint: None,
1629 text: None,
1630 all_threads_stopped: None,
1631 hit_breakpoint_ids: None,
1632 }))
1633 .await;
1634
1635 cx.run_until_parked();
1636
1637 second_editor.update_in(cx, |editor, window, cx| {
1638 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1639
1640 assert_eq!(
1641 active_debug_lines.len(),
1642 1,
1643 "There should be only one active debug line"
1644 );
1645
1646 let point = editor
1647 .snapshot(window, cx)
1648 .buffer_snapshot
1649 .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1650
1651 assert_eq!(point.row, 2);
1652 });
1653
1654 main_editor.update(cx, |editor, _| {
1655 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1656
1657 assert!(
1658 active_debug_lines.is_empty(),
1659 "There shouldn't be any active debug lines"
1660 );
1661 });
1662
1663 assert!(
1664 handled_second_stacktrace.load(Ordering::SeqCst),
1665 "Second stacktrace request handler was not called"
1666 );
1667
1668 client
1669 .fake_event(dap::messages::Events::Continued(dap::ContinuedEvent {
1670 thread_id: 0,
1671 all_threads_continued: Some(true),
1672 }))
1673 .await;
1674
1675 cx.run_until_parked();
1676
1677 second_editor.update(cx, |editor, _| {
1678 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1679
1680 assert!(
1681 active_debug_lines.is_empty(),
1682 "There shouldn't be any active debug lines"
1683 );
1684 });
1685
1686 main_editor.update(cx, |editor, _| {
1687 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1688
1689 assert!(
1690 active_debug_lines.is_empty(),
1691 "There shouldn't be any active debug lines"
1692 );
1693 });
1694
1695 // Clean up
1696 let shutdown_session = project.update(cx, |project, cx| {
1697 project.dap_store().update(cx, |dap_store, cx| {
1698 dap_store.shutdown_session(session.read(cx).session_id(), cx)
1699 })
1700 });
1701
1702 shutdown_session.await.unwrap();
1703
1704 main_editor.update(cx, |editor, _| {
1705 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1706
1707 assert!(
1708 active_debug_lines.is_empty(),
1709 "There shouldn't be any active debug lines after session shutdown"
1710 );
1711 });
1712
1713 second_editor.update(cx, |editor, _| {
1714 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1715
1716 assert!(
1717 active_debug_lines.is_empty(),
1718 "There shouldn't be any active debug lines after session shutdown"
1719 );
1720 });
1721}