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
448 assert_eq!(
449 active_session.read(cx).definition(),
450 parent_session.read(cx).definition()
451 );
452 })
453 .unwrap();
454
455 assert_eq!(&fake_config, launched_with.lock().as_ref().unwrap());
456}
457
458// // covers that we always send a response back, if something when wrong,
459// // while spawning the terminal
460#[gpui::test]
461async fn test_handle_error_run_in_terminal_reverse_request(
462 executor: BackgroundExecutor,
463 cx: &mut TestAppContext,
464) {
465 init_test(cx);
466
467 let send_response = Arc::new(AtomicBool::new(false));
468
469 let fs = FakeFs::new(executor.clone());
470
471 fs.insert_tree(
472 path!("/project"),
473 json!({
474 "main.rs": "First line\nSecond line\nThird line\nFourth line",
475 }),
476 )
477 .await;
478
479 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
480 let workspace = init_test_workspace(&project, cx).await;
481 let cx = &mut VisualTestContext::from_window(*workspace, cx);
482
483 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
484 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
485
486 client
487 .on_response::<RunInTerminal, _>({
488 let send_response = send_response.clone();
489 move |response| {
490 send_response.store(true, Ordering::SeqCst);
491
492 assert!(!response.success);
493 assert!(response.body.is_some());
494 }
495 })
496 .await;
497
498 client
499 .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
500 kind: None,
501 title: None,
502 cwd: "".into(),
503 args: vec!["oops".into(), "oops".into()],
504 env: None,
505 args_can_be_interpreted_by_shell: None,
506 })
507 .await;
508
509 cx.run_until_parked();
510
511 assert!(
512 send_response.load(std::sync::atomic::Ordering::SeqCst),
513 "Expected to receive response from reverse request"
514 );
515
516 workspace
517 .update(cx, |workspace, _window, cx| {
518 let terminal_panel = workspace.panel::<TerminalPanel>(cx).unwrap();
519
520 assert_eq!(
521 0,
522 terminal_panel.read(cx).pane().unwrap().read(cx).items_len()
523 );
524 })
525 .unwrap();
526}
527
528#[gpui::test]
529async fn test_handle_start_debugging_reverse_request(
530 executor: BackgroundExecutor,
531 cx: &mut TestAppContext,
532) {
533 init_test(cx);
534
535 let send_response = Arc::new(AtomicBool::new(false));
536
537 let fs = FakeFs::new(executor.clone());
538
539 fs.insert_tree(
540 path!("/project"),
541 json!({
542 "main.rs": "First line\nSecond line\nThird line\nFourth line",
543 }),
544 )
545 .await;
546
547 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
548 let workspace = init_test_workspace(&project, cx).await;
549 let cx = &mut VisualTestContext::from_window(*workspace, cx);
550
551 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
552 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
553
554 client.on_request::<dap::requests::Threads, _>(move |_, _| {
555 Ok(dap::ThreadsResponse {
556 threads: vec![dap::Thread {
557 id: 1,
558 name: "Thread 1".into(),
559 }],
560 })
561 });
562
563 client
564 .on_response::<StartDebugging, _>({
565 let send_response = send_response.clone();
566 move |response| {
567 send_response.store(true, Ordering::SeqCst);
568
569 assert!(response.success);
570 assert!(response.body.is_some());
571 }
572 })
573 .await;
574 // Set up handlers for sessions spawned with reverse request too.
575 let _reverse_request_subscription =
576 project::debugger::test::intercept_debug_sessions(cx, |_| {});
577 client
578 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
579 configuration: json!({}),
580 request: StartDebuggingRequestArgumentsRequest::Launch,
581 })
582 .await;
583
584 cx.run_until_parked();
585
586 let child_session = project.update(cx, |project, cx| {
587 project
588 .dap_store()
589 .read(cx)
590 .session_by_id(SessionId(1))
591 .unwrap()
592 });
593 let child_client = child_session.update(cx, |session, _| session.adapter_client().unwrap());
594
595 child_client.on_request::<dap::requests::Threads, _>(move |_, _| {
596 Ok(dap::ThreadsResponse {
597 threads: vec![dap::Thread {
598 id: 1,
599 name: "Thread 1".into(),
600 }],
601 })
602 });
603
604 child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
605
606 child_client
607 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
608 reason: dap::StoppedEventReason::Pause,
609 description: None,
610 thread_id: Some(2),
611 preserve_focus_hint: None,
612 text: None,
613 all_threads_stopped: None,
614 hit_breakpoint_ids: None,
615 }))
616 .await;
617
618 cx.run_until_parked();
619
620 assert!(
621 send_response.load(std::sync::atomic::Ordering::SeqCst),
622 "Expected to receive response from reverse request"
623 );
624}
625
626#[gpui::test]
627async fn test_shutdown_children_when_parent_session_shutdown(
628 executor: BackgroundExecutor,
629 cx: &mut TestAppContext,
630) {
631 init_test(cx);
632
633 let fs = FakeFs::new(executor.clone());
634
635 fs.insert_tree(
636 path!("/project"),
637 json!({
638 "main.rs": "First line\nSecond line\nThird line\nFourth line",
639 }),
640 )
641 .await;
642
643 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
644 let dap_store = project.update(cx, |project, _| project.dap_store());
645 let workspace = init_test_workspace(&project, cx).await;
646 let cx = &mut VisualTestContext::from_window(*workspace, cx);
647
648 let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
649 let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
650
651 client.on_request::<dap::requests::Threads, _>(move |_, _| {
652 Ok(dap::ThreadsResponse {
653 threads: vec![dap::Thread {
654 id: 1,
655 name: "Thread 1".into(),
656 }],
657 })
658 });
659
660 client.on_response::<StartDebugging, _>(move |_| {}).await;
661 // Set up handlers for sessions spawned with reverse request too.
662 let _reverse_request_subscription =
663 project::debugger::test::intercept_debug_sessions(cx, |_| {});
664 // start first child session
665 client
666 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
667 configuration: json!({}),
668 request: StartDebuggingRequestArgumentsRequest::Launch,
669 })
670 .await;
671
672 cx.run_until_parked();
673
674 // start second child session
675 client
676 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
677 configuration: json!({}),
678 request: StartDebuggingRequestArgumentsRequest::Launch,
679 })
680 .await;
681
682 cx.run_until_parked();
683
684 // configure first child session
685 let first_child_session = dap_store.read_with(cx, |dap_store, _| {
686 dap_store.session_by_id(SessionId(1)).unwrap()
687 });
688 let first_child_client =
689 first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
690
691 first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
692
693 // configure second child session
694 let second_child_session = dap_store.read_with(cx, |dap_store, _| {
695 dap_store.session_by_id(SessionId(2)).unwrap()
696 });
697 let second_child_client =
698 second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
699
700 second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
701
702 cx.run_until_parked();
703
704 // shutdown parent session
705 dap_store
706 .update(cx, |dap_store, cx| {
707 dap_store.shutdown_session(parent_session.read(cx).session_id(), cx)
708 })
709 .await
710 .unwrap();
711
712 // assert parent session and all children sessions are shutdown
713 dap_store.update(cx, |dap_store, cx| {
714 assert!(
715 dap_store
716 .session_by_id(parent_session.read(cx).session_id())
717 .is_none()
718 );
719 assert!(
720 dap_store
721 .session_by_id(first_child_session.read(cx).session_id())
722 .is_none()
723 );
724 assert!(
725 dap_store
726 .session_by_id(second_child_session.read(cx).session_id())
727 .is_none()
728 );
729 });
730}
731
732#[gpui::test]
733async fn test_shutdown_parent_session_if_all_children_are_shutdown(
734 executor: BackgroundExecutor,
735 cx: &mut TestAppContext,
736) {
737 init_test(cx);
738
739 let fs = FakeFs::new(executor.clone());
740
741 fs.insert_tree(
742 path!("/project"),
743 json!({
744 "main.rs": "First line\nSecond line\nThird line\nFourth line",
745 }),
746 )
747 .await;
748
749 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
750 let dap_store = project.update(cx, |project, _| project.dap_store());
751 let workspace = init_test_workspace(&project, cx).await;
752 let cx = &mut VisualTestContext::from_window(*workspace, cx);
753
754 let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
755 let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
756
757 client.on_response::<StartDebugging, _>(move |_| {}).await;
758 // Set up handlers for sessions spawned with reverse request too.
759 let _reverse_request_subscription =
760 project::debugger::test::intercept_debug_sessions(cx, |_| {});
761 // start first child session
762 client
763 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
764 configuration: json!({}),
765 request: StartDebuggingRequestArgumentsRequest::Launch,
766 })
767 .await;
768
769 cx.run_until_parked();
770
771 // start second child session
772 client
773 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
774 configuration: json!({}),
775 request: StartDebuggingRequestArgumentsRequest::Launch,
776 })
777 .await;
778
779 cx.run_until_parked();
780
781 // configure first child session
782 let first_child_session = dap_store.read_with(cx, |dap_store, _| {
783 dap_store.session_by_id(SessionId(1)).unwrap()
784 });
785 let first_child_client =
786 first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
787
788 first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
789
790 // configure second child session
791 let second_child_session = dap_store.read_with(cx, |dap_store, _| {
792 dap_store.session_by_id(SessionId(2)).unwrap()
793 });
794 let second_child_client =
795 second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
796
797 second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
798
799 cx.run_until_parked();
800
801 // shutdown first child session
802 dap_store
803 .update(cx, |dap_store, cx| {
804 dap_store.shutdown_session(first_child_session.read(cx).session_id(), cx)
805 })
806 .await
807 .unwrap();
808
809 // assert parent session and second child session still exist
810 dap_store.update(cx, |dap_store, cx| {
811 assert!(
812 dap_store
813 .session_by_id(parent_session.read(cx).session_id())
814 .is_some()
815 );
816 assert!(
817 dap_store
818 .session_by_id(first_child_session.read(cx).session_id())
819 .is_none()
820 );
821 assert!(
822 dap_store
823 .session_by_id(second_child_session.read(cx).session_id())
824 .is_some()
825 );
826 });
827
828 // shutdown first child session
829 dap_store
830 .update(cx, |dap_store, cx| {
831 dap_store.shutdown_session(second_child_session.read(cx).session_id(), cx)
832 })
833 .await
834 .unwrap();
835
836 // assert parent session got shutdown by second child session
837 // because it was the last child
838 dap_store.update(cx, |dap_store, cx| {
839 assert!(
840 dap_store
841 .session_by_id(parent_session.read(cx).session_id())
842 .is_none()
843 );
844 assert!(
845 dap_store
846 .session_by_id(second_child_session.read(cx).session_id())
847 .is_none()
848 );
849 });
850}
851
852#[gpui::test]
853async fn test_debug_panel_item_thread_status_reset_on_failure(
854 executor: BackgroundExecutor,
855 cx: &mut TestAppContext,
856) {
857 init_test(cx);
858
859 let fs = FakeFs::new(executor.clone());
860
861 fs.insert_tree(
862 path!("/project"),
863 json!({
864 "main.rs": "First line\nSecond line\nThird line\nFourth line",
865 }),
866 )
867 .await;
868
869 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
870 let workspace = init_test_workspace(&project, cx).await;
871 let cx = &mut VisualTestContext::from_window(*workspace, cx);
872
873 let session = start_debug_session(&workspace, cx, |client| {
874 client.on_request::<dap::requests::Initialize, _>(move |_, _| {
875 Ok(dap::Capabilities {
876 supports_step_back: Some(true),
877 ..Default::default()
878 })
879 });
880 })
881 .unwrap();
882
883 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
884 const THREAD_ID_NUM: u64 = 1;
885
886 client.on_request::<dap::requests::Threads, _>(move |_, _| {
887 Ok(dap::ThreadsResponse {
888 threads: vec![dap::Thread {
889 id: THREAD_ID_NUM,
890 name: "Thread 1".into(),
891 }],
892 })
893 });
894
895 client.on_request::<Launch, _>(move |_, _| Ok(()));
896
897 client.on_request::<StackTrace, _>(move |_, _| {
898 Ok(dap::StackTraceResponse {
899 stack_frames: Vec::default(),
900 total_frames: None,
901 })
902 });
903
904 client.on_request::<Next, _>(move |_, _| {
905 Err(ErrorResponse {
906 error: Some(dap::Message {
907 id: 1,
908 format: "error".into(),
909 variables: None,
910 send_telemetry: None,
911 show_user: None,
912 url: None,
913 url_label: None,
914 }),
915 })
916 });
917
918 client.on_request::<StepOut, _>(move |_, _| {
919 Err(ErrorResponse {
920 error: Some(dap::Message {
921 id: 1,
922 format: "error".into(),
923 variables: None,
924 send_telemetry: None,
925 show_user: None,
926 url: None,
927 url_label: None,
928 }),
929 })
930 });
931
932 client.on_request::<StepIn, _>(move |_, _| {
933 Err(ErrorResponse {
934 error: Some(dap::Message {
935 id: 1,
936 format: "error".into(),
937 variables: None,
938 send_telemetry: None,
939 show_user: None,
940 url: None,
941 url_label: None,
942 }),
943 })
944 });
945
946 client.on_request::<StepBack, _>(move |_, _| {
947 Err(ErrorResponse {
948 error: Some(dap::Message {
949 id: 1,
950 format: "error".into(),
951 variables: None,
952 send_telemetry: None,
953 show_user: None,
954 url: None,
955 url_label: None,
956 }),
957 })
958 });
959
960 client.on_request::<Continue, _>(move |_, _| {
961 Err(ErrorResponse {
962 error: Some(dap::Message {
963 id: 1,
964 format: "error".into(),
965 variables: None,
966 send_telemetry: None,
967 show_user: None,
968 url: None,
969 url_label: None,
970 }),
971 })
972 });
973
974 client
975 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
976 reason: dap::StoppedEventReason::Pause,
977 description: None,
978 thread_id: Some(1),
979 preserve_focus_hint: None,
980 text: None,
981 all_threads_stopped: None,
982 hit_breakpoint_ids: None,
983 }))
984 .await;
985
986 cx.run_until_parked();
987
988 let running_state = active_debug_session_panel(workspace, cx)
989 .update(cx, |item, _| item.running_state().clone());
990
991 cx.run_until_parked();
992 let thread_id = ThreadId(1);
993
994 for operation in &[
995 "step_over",
996 "continue_thread",
997 "step_back",
998 "step_in",
999 "step_out",
1000 ] {
1001 running_state.update(cx, |running_state, cx| match *operation {
1002 "step_over" => running_state.step_over(cx),
1003 "continue_thread" => running_state.continue_thread(cx),
1004 "step_back" => running_state.step_back(cx),
1005 "step_in" => running_state.step_in(cx),
1006 "step_out" => running_state.step_out(cx),
1007 _ => unreachable!(),
1008 });
1009
1010 // Check that we step the thread status to the correct intermediate state
1011 running_state.update(cx, |running_state, cx| {
1012 assert_eq!(
1013 running_state
1014 .thread_status(cx)
1015 .expect("There should be an active thread selected"),
1016 match *operation {
1017 "continue_thread" => ThreadStatus::Running,
1018 _ => ThreadStatus::Stepping,
1019 },
1020 "Thread status was not set to correct intermediate state after {} request",
1021 operation
1022 );
1023 });
1024
1025 cx.run_until_parked();
1026
1027 running_state.update(cx, |running_state, cx| {
1028 assert_eq!(
1029 running_state
1030 .thread_status(cx)
1031 .expect("There should be an active thread selected"),
1032 ThreadStatus::Stopped,
1033 "Thread status not reset to Stopped after failed {}",
1034 operation
1035 );
1036
1037 // update state to running, so we can test it actually changes the status back to stopped
1038 running_state
1039 .session()
1040 .update(cx, |session, cx| session.continue_thread(thread_id, cx));
1041 });
1042 }
1043}
1044
1045#[gpui::test]
1046async fn test_send_breakpoints_when_editor_has_been_saved(
1047 executor: BackgroundExecutor,
1048 cx: &mut TestAppContext,
1049) {
1050 init_test(cx);
1051
1052 let fs = FakeFs::new(executor.clone());
1053
1054 fs.insert_tree(
1055 path!("/project"),
1056 json!({
1057 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1058 }),
1059 )
1060 .await;
1061
1062 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1063 let workspace = init_test_workspace(&project, cx).await;
1064 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1065 let project_path = Path::new(path!("/project"));
1066 let worktree = project
1067 .update(cx, |project, cx| project.find_worktree(project_path, cx))
1068 .expect("This worktree should exist in project")
1069 .0;
1070
1071 let worktree_id = workspace
1072 .update(cx, |_, _, cx| worktree.read(cx).id())
1073 .unwrap();
1074
1075 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1076 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1077
1078 let buffer = project
1079 .update(cx, |project, cx| {
1080 project.open_buffer((worktree_id, "main.rs"), cx)
1081 })
1082 .await
1083 .unwrap();
1084
1085 let (editor, cx) = cx.add_window_view(|window, cx| {
1086 Editor::new(
1087 EditorMode::full(),
1088 MultiBuffer::build_from_buffer(buffer, cx),
1089 Some(project.clone()),
1090 window,
1091 cx,
1092 )
1093 });
1094
1095 client.on_request::<Launch, _>(move |_, _| Ok(()));
1096
1097 client.on_request::<StackTrace, _>(move |_, _| {
1098 Ok(dap::StackTraceResponse {
1099 stack_frames: Vec::default(),
1100 total_frames: None,
1101 })
1102 });
1103
1104 client
1105 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1106 reason: dap::StoppedEventReason::Pause,
1107 description: None,
1108 thread_id: Some(1),
1109 preserve_focus_hint: None,
1110 text: None,
1111 all_threads_stopped: None,
1112 hit_breakpoint_ids: None,
1113 }))
1114 .await;
1115
1116 let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1117 client.on_request::<SetBreakpoints, _>({
1118 let called_set_breakpoints = called_set_breakpoints.clone();
1119 move |_, args| {
1120 assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1121 assert_eq!(
1122 vec![SourceBreakpoint {
1123 line: 2,
1124 column: None,
1125 condition: None,
1126 hit_condition: None,
1127 log_message: None,
1128 mode: None
1129 }],
1130 args.breakpoints.unwrap()
1131 );
1132 assert!(!args.source_modified.unwrap());
1133
1134 called_set_breakpoints.store(true, Ordering::SeqCst);
1135
1136 Ok(dap::SetBreakpointsResponse {
1137 breakpoints: Vec::default(),
1138 })
1139 }
1140 });
1141
1142 editor.update_in(cx, |editor, window, cx| {
1143 editor.move_down(&actions::MoveDown, window, cx);
1144 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1145 });
1146
1147 cx.run_until_parked();
1148
1149 assert!(
1150 called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1151 "SetBreakpoint request must be called"
1152 );
1153
1154 let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1155 client.on_request::<SetBreakpoints, _>({
1156 let called_set_breakpoints = called_set_breakpoints.clone();
1157 move |_, args| {
1158 assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1159 assert_eq!(
1160 vec![SourceBreakpoint {
1161 line: 3,
1162 column: None,
1163 condition: None,
1164 hit_condition: None,
1165 log_message: None,
1166 mode: None
1167 }],
1168 args.breakpoints.unwrap()
1169 );
1170 assert!(args.source_modified.unwrap());
1171
1172 called_set_breakpoints.store(true, Ordering::SeqCst);
1173
1174 Ok(dap::SetBreakpointsResponse {
1175 breakpoints: Vec::default(),
1176 })
1177 }
1178 });
1179
1180 editor.update_in(cx, |editor, window, cx| {
1181 editor.move_up(&actions::MoveUp, window, cx);
1182 editor.insert("new text\n", window, cx);
1183 });
1184
1185 editor
1186 .update_in(cx, |editor, window, cx| {
1187 editor.save(true, project.clone(), window, cx)
1188 })
1189 .await
1190 .unwrap();
1191
1192 cx.run_until_parked();
1193
1194 assert!(
1195 called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1196 "SetBreakpoint request must be called after editor is saved"
1197 );
1198}
1199
1200#[gpui::test]
1201async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
1202 executor: BackgroundExecutor,
1203 cx: &mut TestAppContext,
1204) {
1205 init_test(cx);
1206
1207 let fs = FakeFs::new(executor.clone());
1208
1209 fs.insert_tree(
1210 path!("/project"),
1211 json!({
1212 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1213 "second.rs": "First line\nSecond line\nThird line\nFourth line",
1214 "no_breakpoints.rs": "Used to ensure that we don't unset breakpoint in files with no breakpoints"
1215 }),
1216 )
1217 .await;
1218
1219 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1220 let workspace = init_test_workspace(&project, cx).await;
1221 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1222 let project_path = Path::new(path!("/project"));
1223 let worktree = project
1224 .update(cx, |project, cx| project.find_worktree(project_path, cx))
1225 .expect("This worktree should exist in project")
1226 .0;
1227
1228 let worktree_id = workspace
1229 .update(cx, |_, _, cx| worktree.read(cx).id())
1230 .unwrap();
1231
1232 let first = project
1233 .update(cx, |project, cx| {
1234 project.open_buffer((worktree_id, "main.rs"), cx)
1235 })
1236 .await
1237 .unwrap();
1238
1239 let second = project
1240 .update(cx, |project, cx| {
1241 project.open_buffer((worktree_id, "second.rs"), cx)
1242 })
1243 .await
1244 .unwrap();
1245
1246 let (first_editor, cx) = cx.add_window_view(|window, cx| {
1247 Editor::new(
1248 EditorMode::full(),
1249 MultiBuffer::build_from_buffer(first, cx),
1250 Some(project.clone()),
1251 window,
1252 cx,
1253 )
1254 });
1255
1256 let (second_editor, cx) = cx.add_window_view(|window, cx| {
1257 Editor::new(
1258 EditorMode::full(),
1259 MultiBuffer::build_from_buffer(second, cx),
1260 Some(project.clone()),
1261 window,
1262 cx,
1263 )
1264 });
1265
1266 first_editor.update_in(cx, |editor, window, cx| {
1267 editor.move_down(&actions::MoveDown, window, cx);
1268 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1269 editor.move_down(&actions::MoveDown, window, cx);
1270 editor.move_down(&actions::MoveDown, window, cx);
1271 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1272 });
1273
1274 second_editor.update_in(cx, |editor, window, cx| {
1275 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1276 editor.move_down(&actions::MoveDown, window, cx);
1277 editor.move_down(&actions::MoveDown, window, cx);
1278 editor.move_down(&actions::MoveDown, window, cx);
1279 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1280 });
1281
1282 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1283 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1284
1285 let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1286
1287 client.on_request::<SetBreakpoints, _>({
1288 let called_set_breakpoints = called_set_breakpoints.clone();
1289 move |_, args| {
1290 assert!(
1291 args.breakpoints.is_none_or(|bps| bps.is_empty()),
1292 "Send empty breakpoint sets to clear them from DAP servers"
1293 );
1294
1295 match args
1296 .source
1297 .path
1298 .expect("We should always send a breakpoint's path")
1299 .as_str()
1300 {
1301 path!("/project/main.rs") | path!("/project/second.rs") => {}
1302 _ => {
1303 panic!("Unset breakpoints for path that doesn't have any")
1304 }
1305 }
1306
1307 called_set_breakpoints.store(true, Ordering::SeqCst);
1308
1309 Ok(dap::SetBreakpointsResponse {
1310 breakpoints: Vec::default(),
1311 })
1312 }
1313 });
1314
1315 cx.dispatch_action(crate::ClearAllBreakpoints);
1316 cx.run_until_parked();
1317}
1318
1319#[gpui::test]
1320async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
1321 executor: BackgroundExecutor,
1322 cx: &mut TestAppContext,
1323) {
1324 init_test(cx);
1325
1326 let fs = FakeFs::new(executor.clone());
1327
1328 fs.insert_tree(
1329 path!("/project"),
1330 json!({
1331 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1332 }),
1333 )
1334 .await;
1335
1336 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1337 let workspace = init_test_workspace(&project, cx).await;
1338 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1339
1340 start_debug_session(&workspace, cx, |client| {
1341 client.on_request::<dap::requests::Initialize, _>(|_, _| {
1342 Err(ErrorResponse {
1343 error: Some(Message {
1344 format: "failed to launch".to_string(),
1345 id: 1,
1346 variables: None,
1347 send_telemetry: None,
1348 show_user: None,
1349 url: None,
1350 url_label: None,
1351 }),
1352 })
1353 });
1354 })
1355 .ok();
1356
1357 cx.run_until_parked();
1358
1359 project.update(cx, |project, cx| {
1360 assert!(
1361 project.dap_store().read(cx).sessions().count() == 0,
1362 "Session wouldn't exist if it was shutdown"
1363 );
1364 });
1365}
1366
1367#[gpui::test]
1368async fn test_we_send_arguments_from_user_config(
1369 executor: BackgroundExecutor,
1370 cx: &mut TestAppContext,
1371) {
1372 init_test(cx);
1373
1374 let fs = FakeFs::new(executor.clone());
1375
1376 fs.insert_tree(
1377 path!("/project"),
1378 json!({
1379 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1380 }),
1381 )
1382 .await;
1383
1384 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1385 let workspace = init_test_workspace(&project, cx).await;
1386 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1387 let debug_definition = DebugTaskDefinition {
1388 adapter: "fake-adapter".into(),
1389 request: dap::DebugRequest::Launch(LaunchRequest {
1390 program: "main.rs".to_owned(),
1391 args: vec!["arg1".to_owned(), "arg2".to_owned()],
1392 cwd: Some(path!("/Random_path").into()),
1393 env: HashMap::from_iter(vec![("KEY".to_owned(), "VALUE".to_owned())]),
1394 }),
1395 label: "test".into(),
1396 initialize_args: None,
1397 tcp_connection: None,
1398 stop_on_entry: None,
1399 };
1400
1401 let launch_handler_called = Arc::new(AtomicBool::new(false));
1402
1403 start_debug_session_with(&workspace, cx, debug_definition.clone(), {
1404 let debug_definition = debug_definition.clone();
1405 let launch_handler_called = launch_handler_called.clone();
1406
1407 move |client| {
1408 let debug_definition = debug_definition.clone();
1409 let launch_handler_called = launch_handler_called.clone();
1410
1411 client.on_request::<dap::requests::Launch, _>(move |_, args| {
1412 launch_handler_called.store(true, Ordering::SeqCst);
1413
1414 let obj = args.raw.as_object().unwrap();
1415 let sent_definition = serde_json::from_value::<DebugTaskDefinition>(
1416 obj.get(&"raw_request".to_owned()).unwrap().clone(),
1417 )
1418 .unwrap();
1419
1420 assert_eq!(sent_definition, debug_definition);
1421
1422 Ok(())
1423 });
1424 }
1425 })
1426 .ok();
1427
1428 cx.run_until_parked();
1429
1430 assert!(
1431 launch_handler_called.load(Ordering::SeqCst),
1432 "Launch request handler was not called"
1433 );
1434}
1435
1436#[gpui::test]
1437async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut TestAppContext) {
1438 init_test(cx);
1439
1440 let fs = FakeFs::new(executor.clone());
1441
1442 fs.insert_tree(
1443 path!("/project"),
1444 json!({
1445 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1446 "second.rs": "First line\nSecond line\nThird line\nFourth line",
1447 }),
1448 )
1449 .await;
1450
1451 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1452 let workspace = init_test_workspace(&project, cx).await;
1453 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1454 let project_path = Path::new(path!("/project"));
1455 let worktree = project
1456 .update(cx, |project, cx| project.find_worktree(project_path, cx))
1457 .expect("This worktree should exist in project")
1458 .0;
1459
1460 let worktree_id = workspace
1461 .update(cx, |_, _, cx| worktree.read(cx).id())
1462 .unwrap();
1463
1464 let main_buffer = project
1465 .update(cx, |project, cx| {
1466 project.open_buffer((worktree_id, "main.rs"), cx)
1467 })
1468 .await
1469 .unwrap();
1470
1471 let second_buffer = project
1472 .update(cx, |project, cx| {
1473 project.open_buffer((worktree_id, "second.rs"), cx)
1474 })
1475 .await
1476 .unwrap();
1477
1478 let (main_editor, cx) = cx.add_window_view(|window, cx| {
1479 Editor::new(
1480 EditorMode::full(),
1481 MultiBuffer::build_from_buffer(main_buffer, cx),
1482 Some(project.clone()),
1483 window,
1484 cx,
1485 )
1486 });
1487
1488 let (second_editor, cx) = cx.add_window_view(|window, cx| {
1489 Editor::new(
1490 EditorMode::full(),
1491 MultiBuffer::build_from_buffer(second_buffer, cx),
1492 Some(project.clone()),
1493 window,
1494 cx,
1495 )
1496 });
1497
1498 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1499 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1500
1501 client.on_request::<dap::requests::Threads, _>(move |_, _| {
1502 Ok(dap::ThreadsResponse {
1503 threads: vec![dap::Thread {
1504 id: 1,
1505 name: "Thread 1".into(),
1506 }],
1507 })
1508 });
1509
1510 client.on_request::<dap::requests::Scopes, _>(move |_, _| {
1511 Ok(dap::ScopesResponse {
1512 scopes: Vec::default(),
1513 })
1514 });
1515
1516 client.on_request::<StackTrace, _>(move |_, args| {
1517 assert_eq!(args.thread_id, 1);
1518
1519 Ok(dap::StackTraceResponse {
1520 stack_frames: vec![dap::StackFrame {
1521 id: 1,
1522 name: "frame 1".into(),
1523 source: Some(dap::Source {
1524 name: Some("main.rs".into()),
1525 path: Some(path!("/project/main.rs").into()),
1526 source_reference: None,
1527 presentation_hint: None,
1528 origin: None,
1529 sources: None,
1530 adapter_data: None,
1531 checksums: None,
1532 }),
1533 line: 2,
1534 column: 0,
1535 end_line: None,
1536 end_column: None,
1537 can_restart: None,
1538 instruction_pointer_reference: None,
1539 module_id: None,
1540 presentation_hint: None,
1541 }],
1542 total_frames: None,
1543 })
1544 });
1545
1546 client
1547 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1548 reason: dap::StoppedEventReason::Breakpoint,
1549 description: None,
1550 thread_id: Some(1),
1551 preserve_focus_hint: None,
1552 text: None,
1553 all_threads_stopped: None,
1554 hit_breakpoint_ids: None,
1555 }))
1556 .await;
1557
1558 cx.run_until_parked();
1559
1560 main_editor.update_in(cx, |editor, window, cx| {
1561 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1562
1563 assert_eq!(
1564 active_debug_lines.len(),
1565 1,
1566 "There should be only one active debug line"
1567 );
1568
1569 let point = editor
1570 .snapshot(window, cx)
1571 .buffer_snapshot
1572 .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1573
1574 assert_eq!(point.row, 1);
1575 });
1576
1577 second_editor.update(cx, |editor, _| {
1578 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1579
1580 assert!(
1581 active_debug_lines.is_empty(),
1582 "There shouldn't be any active debug lines"
1583 );
1584 });
1585
1586 let handled_second_stacktrace = Arc::new(AtomicBool::new(false));
1587 client.on_request::<StackTrace, _>({
1588 let handled_second_stacktrace = handled_second_stacktrace.clone();
1589 move |_, args| {
1590 handled_second_stacktrace.store(true, Ordering::SeqCst);
1591 assert_eq!(args.thread_id, 1);
1592
1593 Ok(dap::StackTraceResponse {
1594 stack_frames: vec![dap::StackFrame {
1595 id: 2,
1596 name: "frame 2".into(),
1597 source: Some(dap::Source {
1598 name: Some("second.rs".into()),
1599 path: Some(path!("/project/second.rs").into()),
1600 source_reference: None,
1601 presentation_hint: None,
1602 origin: None,
1603 sources: None,
1604 adapter_data: None,
1605 checksums: None,
1606 }),
1607 line: 3,
1608 column: 0,
1609 end_line: None,
1610 end_column: None,
1611 can_restart: None,
1612 instruction_pointer_reference: None,
1613 module_id: None,
1614 presentation_hint: None,
1615 }],
1616 total_frames: None,
1617 })
1618 }
1619 });
1620
1621 client
1622 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1623 reason: dap::StoppedEventReason::Breakpoint,
1624 description: None,
1625 thread_id: Some(1),
1626 preserve_focus_hint: None,
1627 text: None,
1628 all_threads_stopped: None,
1629 hit_breakpoint_ids: None,
1630 }))
1631 .await;
1632
1633 cx.run_until_parked();
1634
1635 second_editor.update_in(cx, |editor, window, cx| {
1636 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1637
1638 assert_eq!(
1639 active_debug_lines.len(),
1640 1,
1641 "There should be only one active debug line"
1642 );
1643
1644 let point = editor
1645 .snapshot(window, cx)
1646 .buffer_snapshot
1647 .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1648
1649 assert_eq!(point.row, 2);
1650 });
1651
1652 main_editor.update(cx, |editor, _| {
1653 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1654
1655 assert!(
1656 active_debug_lines.is_empty(),
1657 "There shouldn't be any active debug lines"
1658 );
1659 });
1660
1661 assert!(
1662 handled_second_stacktrace.load(Ordering::SeqCst),
1663 "Second stacktrace request handler was not called"
1664 );
1665
1666 client
1667 .fake_event(dap::messages::Events::Continued(dap::ContinuedEvent {
1668 thread_id: 0,
1669 all_threads_continued: Some(true),
1670 }))
1671 .await;
1672
1673 cx.run_until_parked();
1674
1675 second_editor.update(cx, |editor, _| {
1676 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1677
1678 assert!(
1679 active_debug_lines.is_empty(),
1680 "There shouldn't be any active debug lines"
1681 );
1682 });
1683
1684 main_editor.update(cx, |editor, _| {
1685 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1686
1687 assert!(
1688 active_debug_lines.is_empty(),
1689 "There shouldn't be any active debug lines"
1690 );
1691 });
1692
1693 // Clean up
1694 let shutdown_session = project.update(cx, |project, cx| {
1695 project.dap_store().update(cx, |dap_store, cx| {
1696 dap_store.shutdown_session(session.read(cx).session_id(), cx)
1697 })
1698 });
1699
1700 shutdown_session.await.unwrap();
1701
1702 main_editor.update(cx, |editor, _| {
1703 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1704
1705 assert!(
1706 active_debug_lines.is_empty(),
1707 "There shouldn't be any active debug lines after session shutdown"
1708 );
1709 });
1710
1711 second_editor.update(cx, |editor, _| {
1712 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1713
1714 assert!(
1715 active_debug_lines.is_empty(),
1716 "There shouldn't be any active debug lines after session shutdown"
1717 );
1718 });
1719}