1use crate::*;
2use dap::{
3 client::SessionId,
4 requests::{
5 Continue, Disconnect, Launch, Next, RunInTerminal, SetBreakpoints, StackTrace,
6 StartDebugging, StepBack, StepIn, StepOut, Threads,
7 },
8 DebugRequestType, ErrorResponse, RunInTerminalRequestArguments, SourceBreakpoint,
9 StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
10};
11use editor::{
12 actions::{self},
13 Editor, EditorMode, MultiBuffer,
14};
15use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
16use project::{
17 debugger::session::{ThreadId, ThreadStatus},
18 FakeFs, Project,
19};
20use serde_json::json;
21use std::{
22 path::Path,
23 sync::{
24 atomic::{AtomicBool, Ordering},
25 Arc,
26 },
27};
28use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
29use tests::{active_debug_session_panel, init_test, init_test_workspace};
30use util::path;
31use workspace::{dock::Panel, Item};
32
33#[gpui::test]
34async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut TestAppContext) {
35 init_test(cx);
36
37 let fs = FakeFs::new(executor.clone());
38
39 fs.insert_tree(
40 "/project",
41 json!({
42 "main.rs": "First line\nSecond line\nThird line\nFourth line",
43 }),
44 )
45 .await;
46
47 let project = Project::test(fs, ["/project".as_ref()], cx).await;
48 let workspace = init_test_workspace(&project, cx).await;
49 let cx = &mut VisualTestContext::from_window(*workspace, cx);
50
51 let task = project.update(cx, |project, cx| {
52 project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
53 });
54
55 let session = task.await.unwrap();
56 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
57
58 client
59 .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 .await;
68
69 client
70 .on_request::<StackTrace, _>(move |_, _| {
71 Ok(dap::StackTraceResponse {
72 stack_frames: Vec::default(),
73 total_frames: None,
74 })
75 })
76 .await;
77
78 // assert we have a debug panel item before the session has stopped
79 workspace
80 .update(cx, |workspace, _window, cx| {
81 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
82 let active_session = debug_panel.update(cx, |debug_panel, cx| {
83 debug_panel.active_session(cx).unwrap()
84 });
85
86 let running_state = active_session.update(cx, |active_session, _| {
87 active_session
88 .mode()
89 .as_running()
90 .expect("Session should be running by this point")
91 .clone()
92 });
93
94 debug_panel.update(cx, |this, cx| {
95 assert!(this.active_session(cx).is_some());
96 // we have one active session and one inert item
97 assert_eq!(2, this.pane().unwrap().read(cx).items_len());
98 assert!(running_state.read(cx).selected_thread_id().is_none());
99 });
100 })
101 .unwrap();
102
103 client
104 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
105 reason: dap::StoppedEventReason::Pause,
106 description: None,
107 thread_id: Some(1),
108 preserve_focus_hint: None,
109 text: None,
110 all_threads_stopped: None,
111 hit_breakpoint_ids: None,
112 }))
113 .await;
114
115 cx.run_until_parked();
116
117 workspace
118 .update(cx, |workspace, _window, cx| {
119 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
120 let active_session = debug_panel
121 .update(cx, |this, cx| this.active_session(cx))
122 .unwrap();
123
124 let running_state = active_session.update(cx, |active_session, _| {
125 active_session
126 .mode()
127 .as_running()
128 .expect("Session should be running by this point")
129 .clone()
130 });
131
132 // we have one active session and one inert item
133 assert_eq!(
134 2,
135 debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
136 );
137 assert_eq!(client.id(), running_state.read(cx).session_id());
138 assert_eq!(
139 ThreadId(1),
140 running_state.read(cx).selected_thread_id().unwrap()
141 );
142 })
143 .unwrap();
144
145 let shutdown_session = project.update(cx, |project, cx| {
146 project.dap_store().update(cx, |dap_store, cx| {
147 dap_store.shutdown_session(session.read(cx).session_id(), cx)
148 })
149 });
150
151 shutdown_session.await.unwrap();
152
153 // assert we still have a debug panel item after the client shutdown
154 workspace
155 .update(cx, |workspace, _window, cx| {
156 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
157
158 let active_session = debug_panel
159 .update(cx, |this, cx| this.active_session(cx))
160 .unwrap();
161
162 let running_state = active_session.update(cx, |active_session, _| {
163 active_session
164 .mode()
165 .as_running()
166 .expect("Session should be running by this point")
167 .clone()
168 });
169
170 debug_panel.update(cx, |this, cx| {
171 assert!(this.active_session(cx).is_some());
172 assert_eq!(2, this.pane().unwrap().read(cx).items_len());
173 assert_eq!(
174 ThreadId(1),
175 running_state.read(cx).selected_thread_id().unwrap()
176 );
177 });
178 })
179 .unwrap();
180}
181
182#[gpui::test]
183async fn test_we_can_only_have_one_panel_per_debug_session(
184 executor: BackgroundExecutor,
185 cx: &mut TestAppContext,
186) {
187 init_test(cx);
188
189 let fs = FakeFs::new(executor.clone());
190
191 fs.insert_tree(
192 "/project",
193 json!({
194 "main.rs": "First line\nSecond line\nThird line\nFourth line",
195 }),
196 )
197 .await;
198
199 let project = Project::test(fs, ["/project".as_ref()], cx).await;
200 let workspace = init_test_workspace(&project, cx).await;
201 let cx = &mut VisualTestContext::from_window(*workspace, cx);
202
203 let task = project.update(cx, |project, cx| {
204 project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
205 });
206
207 let session = task.await.unwrap();
208 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
209
210 client
211 .on_request::<Threads, _>(move |_, _| {
212 Ok(dap::ThreadsResponse {
213 threads: vec![dap::Thread {
214 id: 1,
215 name: "Thread 1".into(),
216 }],
217 })
218 })
219 .await;
220
221 client
222 .on_request::<StackTrace, _>(move |_, _| {
223 Ok(dap::StackTraceResponse {
224 stack_frames: Vec::default(),
225 total_frames: None,
226 })
227 })
228 .await;
229
230 // assert we have a debug panel item before the session has stopped
231 workspace
232 .update(cx, |workspace, _window, cx| {
233 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
234
235 debug_panel.update(cx, |this, cx| {
236 assert!(this.active_session(cx).is_some());
237 // we have one active session and one inert item
238 assert_eq!(2, this.pane().unwrap().read(cx).items_len());
239 });
240 })
241 .unwrap();
242
243 client
244 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
245 reason: dap::StoppedEventReason::Pause,
246 description: None,
247 thread_id: Some(1),
248 preserve_focus_hint: None,
249 text: None,
250 all_threads_stopped: None,
251 hit_breakpoint_ids: None,
252 }))
253 .await;
254
255 cx.run_until_parked();
256
257 // assert we added a debug panel item
258 workspace
259 .update(cx, |workspace, _window, cx| {
260 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
261 let active_session = debug_panel
262 .update(cx, |this, cx| this.active_session(cx))
263 .unwrap();
264
265 let running_state = active_session.update(cx, |active_session, _| {
266 active_session
267 .mode()
268 .as_running()
269 .expect("Session should be running by this point")
270 .clone()
271 });
272
273 // we have one active session and one inert item
274 assert_eq!(
275 2,
276 debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
277 );
278 assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
279 assert_eq!(
280 ThreadId(1),
281 running_state.read(cx).selected_thread_id().unwrap()
282 );
283 })
284 .unwrap();
285
286 client
287 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
288 reason: dap::StoppedEventReason::Pause,
289 description: None,
290 thread_id: Some(2),
291 preserve_focus_hint: None,
292 text: None,
293 all_threads_stopped: None,
294 hit_breakpoint_ids: None,
295 }))
296 .await;
297
298 cx.run_until_parked();
299
300 workspace
301 .update(cx, |workspace, _window, cx| {
302 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
303 let active_session = debug_panel
304 .update(cx, |this, cx| this.active_session(cx))
305 .unwrap();
306
307 let running_state = active_session.update(cx, |active_session, _| {
308 active_session
309 .mode()
310 .as_running()
311 .expect("Session should be running by this point")
312 .clone()
313 });
314
315 // we have one active session and one inert item
316 assert_eq!(
317 2,
318 debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
319 );
320 assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
321 assert_eq!(
322 ThreadId(1),
323 running_state.read(cx).selected_thread_id().unwrap()
324 );
325 })
326 .unwrap();
327
328 let shutdown_session = project.update(cx, |project, cx| {
329 project.dap_store().update(cx, |dap_store, cx| {
330 dap_store.shutdown_session(session.read(cx).session_id(), cx)
331 })
332 });
333
334 shutdown_session.await.unwrap();
335
336 // assert we still have a debug panel item after the client shutdown
337 workspace
338 .update(cx, |workspace, _window, cx| {
339 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
340 let active_session = debug_panel
341 .update(cx, |this, cx| this.active_session(cx))
342 .unwrap();
343
344 let running_state = active_session.update(cx, |active_session, _| {
345 active_session
346 .mode()
347 .as_running()
348 .expect("Session should be running by this point")
349 .clone()
350 });
351
352 debug_panel.update(cx, |this, cx| {
353 assert!(this.active_session(cx).is_some());
354 assert_eq!(2, this.pane().unwrap().read(cx).items_len());
355 assert_eq!(
356 ThreadId(1),
357 running_state.read(cx).selected_thread_id().unwrap()
358 );
359 });
360 })
361 .unwrap();
362}
363
364#[gpui::test]
365async fn test_handle_successful_run_in_terminal_reverse_request(
366 executor: BackgroundExecutor,
367 cx: &mut TestAppContext,
368) {
369 init_test(cx);
370
371 let send_response = Arc::new(AtomicBool::new(false));
372
373 let fs = FakeFs::new(executor.clone());
374
375 fs.insert_tree(
376 "/project",
377 json!({
378 "main.rs": "First line\nSecond line\nThird line\nFourth line",
379 }),
380 )
381 .await;
382
383 let project = Project::test(fs, ["/project".as_ref()], cx).await;
384 let workspace = init_test_workspace(&project, cx).await;
385 let cx = &mut VisualTestContext::from_window(*workspace, cx);
386
387 let task = project.update(cx, |project, cx| {
388 project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
389 });
390
391 let session = task.await.unwrap();
392 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
393
394 client
395 .on_response::<RunInTerminal, _>({
396 let send_response = send_response.clone();
397 move |response| {
398 send_response.store(true, Ordering::SeqCst);
399
400 assert!(response.success);
401 assert!(response.body.is_some());
402 }
403 })
404 .await;
405
406 client
407 .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
408 kind: None,
409 title: None,
410 cwd: std::env::temp_dir().to_string_lossy().to_string(),
411 args: vec![],
412 env: None,
413 args_can_be_interpreted_by_shell: None,
414 })
415 .await;
416
417 cx.run_until_parked();
418
419 assert!(
420 send_response.load(std::sync::atomic::Ordering::SeqCst),
421 "Expected to receive response from reverse request"
422 );
423
424 workspace
425 .update(cx, |workspace, _window, cx| {
426 let terminal_panel = workspace.panel::<TerminalPanel>(cx).unwrap();
427
428 let panel = terminal_panel.read(cx).pane().unwrap().read(cx);
429
430 assert_eq!(1, panel.items_len());
431 assert!(panel
432 .active_item()
433 .unwrap()
434 .downcast::<TerminalView>()
435 .unwrap()
436 .read(cx)
437 .terminal()
438 .read(cx)
439 .debug_terminal());
440 })
441 .unwrap();
442
443 let shutdown_session = project.update(cx, |project, cx| {
444 project.dap_store().update(cx, |dap_store, cx| {
445 dap_store.shutdown_session(session.read(cx).session_id(), cx)
446 })
447 });
448
449 shutdown_session.await.unwrap();
450}
451
452// // covers that we always send a response back, if something when wrong,
453// // while spawning the terminal
454#[gpui::test]
455async fn test_handle_error_run_in_terminal_reverse_request(
456 executor: BackgroundExecutor,
457 cx: &mut TestAppContext,
458) {
459 init_test(cx);
460
461 let send_response = Arc::new(AtomicBool::new(false));
462
463 let fs = FakeFs::new(executor.clone());
464
465 fs.insert_tree(
466 "/project",
467 json!({
468 "main.rs": "First line\nSecond line\nThird line\nFourth line",
469 }),
470 )
471 .await;
472
473 let project = Project::test(fs, ["/project".as_ref()], cx).await;
474 let workspace = init_test_workspace(&project, cx).await;
475 let cx = &mut VisualTestContext::from_window(*workspace, cx);
476
477 let task = project.update(cx, |project, cx| {
478 project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
479 });
480
481 let session = task.await.unwrap();
482 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
483
484 client
485 .on_response::<RunInTerminal, _>({
486 let send_response = send_response.clone();
487 move |response| {
488 send_response.store(true, Ordering::SeqCst);
489
490 assert!(!response.success);
491 assert!(response.body.is_some());
492 }
493 })
494 .await;
495
496 client
497 .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
498 kind: None,
499 title: None,
500 cwd: "/non-existing/path".into(), // invalid/non-existing path will cause the terminal spawn to fail
501 args: vec![],
502 env: None,
503 args_can_be_interpreted_by_shell: None,
504 })
505 .await;
506
507 cx.run_until_parked();
508
509 assert!(
510 send_response.load(std::sync::atomic::Ordering::SeqCst),
511 "Expected to receive response from reverse request"
512 );
513
514 workspace
515 .update(cx, |workspace, _window, cx| {
516 let terminal_panel = workspace.panel::<TerminalPanel>(cx).unwrap();
517
518 assert_eq!(
519 0,
520 terminal_panel.read(cx).pane().unwrap().read(cx).items_len()
521 );
522 })
523 .unwrap();
524
525 let shutdown_session = project.update(cx, |project, cx| {
526 project.dap_store().update(cx, |dap_store, cx| {
527 dap_store.shutdown_session(session.read(cx).session_id(), cx)
528 })
529 });
530
531 shutdown_session.await.unwrap();
532}
533
534#[gpui::test]
535async fn test_handle_start_debugging_reverse_request(
536 executor: BackgroundExecutor,
537 cx: &mut TestAppContext,
538) {
539 init_test(cx);
540
541 let send_response = Arc::new(AtomicBool::new(false));
542
543 let fs = FakeFs::new(executor.clone());
544
545 fs.insert_tree(
546 "/project",
547 json!({
548 "main.rs": "First line\nSecond line\nThird line\nFourth line",
549 }),
550 )
551 .await;
552
553 let project = Project::test(fs, ["/project".as_ref()], cx).await;
554 let workspace = init_test_workspace(&project, cx).await;
555 let cx = &mut VisualTestContext::from_window(*workspace, cx);
556
557 let task = project.update(cx, |project, cx| {
558 project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
559 });
560
561 let session = task.await.unwrap();
562 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
563
564 client
565 .on_request::<dap::requests::Threads, _>(move |_, _| {
566 Ok(dap::ThreadsResponse {
567 threads: vec![dap::Thread {
568 id: 1,
569 name: "Thread 1".into(),
570 }],
571 })
572 })
573 .await;
574
575 client
576 .on_response::<StartDebugging, _>({
577 let send_response = send_response.clone();
578 move |response| {
579 send_response.store(true, Ordering::SeqCst);
580
581 assert!(response.success);
582 assert!(response.body.is_some());
583 }
584 })
585 .await;
586
587 client
588 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
589 configuration: json!({}),
590 request: StartDebuggingRequestArgumentsRequest::Launch,
591 })
592 .await;
593
594 cx.run_until_parked();
595
596 let child_session = project.update(cx, |project, cx| {
597 project
598 .dap_store()
599 .read(cx)
600 .session_by_id(SessionId(1))
601 .unwrap()
602 });
603 let child_client = child_session.update(cx, |session, _| session.adapter_client().unwrap());
604
605 client
606 .on_request::<dap::requests::Threads, _>(move |_, _| {
607 Ok(dap::ThreadsResponse {
608 threads: vec![dap::Thread {
609 id: 1,
610 name: "Thread 1".into(),
611 }],
612 })
613 })
614 .await;
615
616 child_client
617 .on_request::<Disconnect, _>(move |_, _| Ok(()))
618 .await;
619
620 child_client
621 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
622 reason: dap::StoppedEventReason::Pause,
623 description: None,
624 thread_id: Some(2),
625 preserve_focus_hint: None,
626 text: None,
627 all_threads_stopped: None,
628 hit_breakpoint_ids: None,
629 }))
630 .await;
631
632 cx.run_until_parked();
633
634 assert!(
635 send_response.load(std::sync::atomic::Ordering::SeqCst),
636 "Expected to receive response from reverse request"
637 );
638
639 let shutdown_session = project.update(cx, |project, cx| {
640 project.dap_store().update(cx, |dap_store, cx| {
641 dap_store.shutdown_session(child_session.read(cx).session_id(), cx)
642 })
643 });
644
645 shutdown_session.await.unwrap();
646}
647
648#[gpui::test]
649async fn test_debug_panel_item_thread_status_reset_on_failure(
650 executor: BackgroundExecutor,
651 cx: &mut TestAppContext,
652) {
653 init_test(cx);
654
655 let fs = FakeFs::new(executor.clone());
656
657 fs.insert_tree(
658 "/project",
659 json!({
660 "main.rs": "First line\nSecond line\nThird line\nFourth line",
661 }),
662 )
663 .await;
664
665 let project = Project::test(fs, ["/project".as_ref()], cx).await;
666 let workspace = init_test_workspace(&project, cx).await;
667 let cx = &mut VisualTestContext::from_window(*workspace, cx);
668
669 let task = project.update(cx, |project, cx| {
670 project.start_debug_session(
671 dap::test_config(
672 DebugRequestType::Launch,
673 None,
674 Some(dap::Capabilities {
675 supports_step_back: Some(true),
676 ..Default::default()
677 }),
678 ),
679 cx,
680 )
681 });
682
683 let session = task.await.unwrap();
684 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
685 const THREAD_ID_NUM: u64 = 1;
686
687 client
688 .on_request::<dap::requests::Threads, _>(move |_, _| {
689 Ok(dap::ThreadsResponse {
690 threads: vec![dap::Thread {
691 id: THREAD_ID_NUM,
692 name: "Thread 1".into(),
693 }],
694 })
695 })
696 .await;
697
698 client.on_request::<Launch, _>(move |_, _| Ok(())).await;
699
700 client
701 .on_request::<StackTrace, _>(move |_, _| {
702 Ok(dap::StackTraceResponse {
703 stack_frames: Vec::default(),
704 total_frames: None,
705 })
706 })
707 .await;
708
709 client
710 .on_request::<Next, _>(move |_, _| {
711 Err(ErrorResponse {
712 error: Some(dap::Message {
713 id: 1,
714 format: "error".into(),
715 variables: None,
716 send_telemetry: None,
717 show_user: None,
718 url: None,
719 url_label: None,
720 }),
721 })
722 })
723 .await;
724
725 client
726 .on_request::<StepOut, _>(move |_, _| {
727 Err(ErrorResponse {
728 error: Some(dap::Message {
729 id: 1,
730 format: "error".into(),
731 variables: None,
732 send_telemetry: None,
733 show_user: None,
734 url: None,
735 url_label: None,
736 }),
737 })
738 })
739 .await;
740
741 client
742 .on_request::<StepIn, _>(move |_, _| {
743 Err(ErrorResponse {
744 error: Some(dap::Message {
745 id: 1,
746 format: "error".into(),
747 variables: None,
748 send_telemetry: None,
749 show_user: None,
750 url: None,
751 url_label: None,
752 }),
753 })
754 })
755 .await;
756
757 client
758 .on_request::<StepBack, _>(move |_, _| {
759 Err(ErrorResponse {
760 error: Some(dap::Message {
761 id: 1,
762 format: "error".into(),
763 variables: None,
764 send_telemetry: None,
765 show_user: None,
766 url: None,
767 url_label: None,
768 }),
769 })
770 })
771 .await;
772
773 client
774 .on_request::<Continue, _>(move |_, _| {
775 Err(ErrorResponse {
776 error: Some(dap::Message {
777 id: 1,
778 format: "error".into(),
779 variables: None,
780 send_telemetry: None,
781 show_user: None,
782 url: None,
783 url_label: None,
784 }),
785 })
786 })
787 .await;
788
789 client
790 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
791 reason: dap::StoppedEventReason::Pause,
792 description: None,
793 thread_id: Some(1),
794 preserve_focus_hint: None,
795 text: None,
796 all_threads_stopped: None,
797 hit_breakpoint_ids: None,
798 }))
799 .await;
800
801 let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, _, _| {
802 item.mode()
803 .as_running()
804 .expect("Session should be running by this point")
805 .clone()
806 });
807
808 cx.run_until_parked();
809 let thread_id = ThreadId(1);
810
811 for operation in &[
812 "step_over",
813 "continue_thread",
814 "step_back",
815 "step_in",
816 "step_out",
817 ] {
818 running_state.update(cx, |running_state, cx| match *operation {
819 "step_over" => running_state.step_over(cx),
820 "continue_thread" => running_state.continue_thread(cx),
821 "step_back" => running_state.step_back(cx),
822 "step_in" => running_state.step_in(cx),
823 "step_out" => running_state.step_out(cx),
824 _ => unreachable!(),
825 });
826
827 // Check that we step the thread status to the correct intermediate state
828 running_state.update(cx, |running_state, cx| {
829 assert_eq!(
830 running_state
831 .thread_status(cx)
832 .expect("There should be an active thread selected"),
833 match *operation {
834 "continue_thread" => ThreadStatus::Running,
835 _ => ThreadStatus::Stepping,
836 },
837 "Thread status was not set to correct intermediate state after {} request",
838 operation
839 );
840 });
841
842 cx.run_until_parked();
843
844 running_state.update(cx, |running_state, cx| {
845 assert_eq!(
846 running_state
847 .thread_status(cx)
848 .expect("There should be an active thread selected"),
849 ThreadStatus::Stopped,
850 "Thread status not reset to Stopped after failed {}",
851 operation
852 );
853
854 // update state to running, so we can test it actually changes the status back to stopped
855 running_state
856 .session()
857 .update(cx, |session, cx| session.continue_thread(thread_id, cx));
858 });
859 }
860
861 let shutdown_session = project.update(cx, |project, cx| {
862 project.dap_store().update(cx, |dap_store, cx| {
863 dap_store.shutdown_session(session.read(cx).session_id(), cx)
864 })
865 });
866
867 shutdown_session.await.unwrap();
868}
869
870#[gpui::test]
871async fn test_send_breakpoints_when_editor_has_been_saved(
872 executor: BackgroundExecutor,
873 cx: &mut TestAppContext,
874) {
875 init_test(cx);
876
877 let fs = FakeFs::new(executor.clone());
878
879 fs.insert_tree(
880 path!("/project"),
881 json!({
882 "main.rs": "First line\nSecond line\nThird line\nFourth line",
883 }),
884 )
885 .await;
886
887 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
888 let workspace = init_test_workspace(&project, cx).await;
889 let cx = &mut VisualTestContext::from_window(*workspace, cx);
890 let project_path = Path::new(path!("/project"));
891 let worktree = project
892 .update(cx, |project, cx| project.find_worktree(project_path, cx))
893 .expect("This worktree should exist in project")
894 .0;
895
896 let worktree_id = workspace
897 .update(cx, |_, _, cx| worktree.read(cx).id())
898 .unwrap();
899
900 let task = project.update(cx, |project, cx| {
901 project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
902 });
903
904 let session = task.await.unwrap();
905 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
906
907 let buffer = project
908 .update(cx, |project, cx| {
909 project.open_buffer((worktree_id, "main.rs"), cx)
910 })
911 .await
912 .unwrap();
913
914 let (editor, cx) = cx.add_window_view(|window, cx| {
915 Editor::new(
916 EditorMode::Full,
917 MultiBuffer::build_from_buffer(buffer, cx),
918 Some(project.clone()),
919 window,
920 cx,
921 )
922 });
923
924 client.on_request::<Launch, _>(move |_, _| Ok(())).await;
925
926 client
927 .on_request::<StackTrace, _>(move |_, _| {
928 Ok(dap::StackTraceResponse {
929 stack_frames: Vec::default(),
930 total_frames: None,
931 })
932 })
933 .await;
934
935 client
936 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
937 reason: dap::StoppedEventReason::Pause,
938 description: None,
939 thread_id: Some(1),
940 preserve_focus_hint: None,
941 text: None,
942 all_threads_stopped: None,
943 hit_breakpoint_ids: None,
944 }))
945 .await;
946
947 let called_set_breakpoints = Arc::new(AtomicBool::new(false));
948 client
949 .on_request::<SetBreakpoints, _>({
950 let called_set_breakpoints = called_set_breakpoints.clone();
951 move |_, args| {
952 assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
953 assert_eq!(
954 vec![SourceBreakpoint {
955 line: 2,
956 column: None,
957 condition: None,
958 hit_condition: None,
959 log_message: None,
960 mode: None
961 }],
962 args.breakpoints.unwrap()
963 );
964 assert!(!args.source_modified.unwrap());
965
966 called_set_breakpoints.store(true, Ordering::SeqCst);
967
968 Ok(dap::SetBreakpointsResponse {
969 breakpoints: Vec::default(),
970 })
971 }
972 })
973 .await;
974
975 editor.update_in(cx, |editor, window, cx| {
976 editor.move_down(&actions::MoveDown, window, cx);
977 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
978 });
979
980 cx.run_until_parked();
981
982 assert!(
983 called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
984 "SetBreakpoint request must be called"
985 );
986
987 let called_set_breakpoints = Arc::new(AtomicBool::new(false));
988 client
989 .on_request::<SetBreakpoints, _>({
990 let called_set_breakpoints = called_set_breakpoints.clone();
991 move |_, args| {
992 assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
993 assert_eq!(
994 vec![SourceBreakpoint {
995 line: 3,
996 column: None,
997 condition: None,
998 hit_condition: None,
999 log_message: None,
1000 mode: None
1001 }],
1002 args.breakpoints.unwrap()
1003 );
1004 assert!(args.source_modified.unwrap());
1005
1006 called_set_breakpoints.store(true, Ordering::SeqCst);
1007
1008 Ok(dap::SetBreakpointsResponse {
1009 breakpoints: Vec::default(),
1010 })
1011 }
1012 })
1013 .await;
1014
1015 editor.update_in(cx, |editor, window, cx| {
1016 editor.move_up(&actions::MoveUp, window, cx);
1017 editor.insert("new text\n", window, cx);
1018 });
1019
1020 editor
1021 .update_in(cx, |editor, window, cx| {
1022 editor.save(true, project.clone(), window, cx)
1023 })
1024 .await
1025 .unwrap();
1026
1027 cx.run_until_parked();
1028
1029 assert!(
1030 called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1031 "SetBreakpoint request must be called after editor is saved"
1032 );
1033
1034 let shutdown_session = project.update(cx, |project, cx| {
1035 project.dap_store().update(cx, |dap_store, cx| {
1036 dap_store.shutdown_session(session.read(cx).session_id(), cx)
1037 })
1038 });
1039
1040 shutdown_session.await.unwrap();
1041}
1042
1043#[gpui::test]
1044async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
1045 executor: BackgroundExecutor,
1046 cx: &mut TestAppContext,
1047) {
1048 init_test(cx);
1049
1050 let fs = FakeFs::new(executor.clone());
1051
1052 fs.insert_tree(
1053 "/project",
1054 json!({
1055 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1056 }),
1057 )
1058 .await;
1059
1060 let project = Project::test(fs, ["/project".as_ref()], cx).await;
1061 let workspace = init_test_workspace(&project, cx).await;
1062 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1063
1064 let task = project.update(cx, |project, cx| {
1065 project.start_debug_session(
1066 dap::test_config(DebugRequestType::Launch, Some(true), None),
1067 cx,
1068 )
1069 });
1070
1071 assert!(
1072 task.await.is_err(),
1073 "Session should failed to start if launch request fails"
1074 );
1075
1076 cx.run_until_parked();
1077
1078 project.update(cx, |project, cx| {
1079 assert!(
1080 project.dap_store().read(cx).sessions().count() == 0,
1081 "Session wouldn't exist if it was shutdown"
1082 );
1083 });
1084}