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