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 client
427 .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
428 request: StartDebuggingRequestArgumentsRequest::Launch,
429 configuration: fake_config.clone(),
430 })
431 .await;
432
433 cx.run_until_parked();
434
435 workspace
436 .update(cx, |workspace, _window, cx| {
437 let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
438 let active_session = debug_panel
439 .read(cx)
440 .active_session()
441 .unwrap()
442 .read(cx)
443 .session(cx);
444 let parent_session = active_session.read(cx).parent_session().unwrap();
445 let mut original_binary = parent_session.read(cx).binary().clone();
446 original_binary.request_args = StartDebuggingRequestArguments {
447 request: StartDebuggingRequestArgumentsRequest::Launch,
448 configuration: fake_config.clone(),
449 };
450
451 assert_eq!(active_session.read(cx).binary(), &original_binary);
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 config: json!({
1390 "request": "launch",
1391 "program": "main.rs".to_owned(),
1392 "args": vec!["arg1".to_owned(), "arg2".to_owned()],
1393 "cwd": path!("/Random_path"),
1394 "env": json!({ "KEY": "VALUE" }),
1395 }),
1396 label: "test".into(),
1397 tcp_connection: None,
1398 };
1399
1400 let launch_handler_called = Arc::new(AtomicBool::new(false));
1401
1402 start_debug_session_with(&workspace, cx, debug_definition.clone(), {
1403 let debug_definition = debug_definition.clone();
1404 let launch_handler_called = launch_handler_called.clone();
1405
1406 move |client| {
1407 let debug_definition = debug_definition.clone();
1408 let launch_handler_called = launch_handler_called.clone();
1409
1410 client.on_request::<dap::requests::Launch, _>(move |_, args| {
1411 launch_handler_called.store(true, Ordering::SeqCst);
1412
1413 let obj = args.raw.as_object().unwrap();
1414 let sent_definition = serde_json::from_value::<DebugTaskDefinition>(
1415 obj.get(&"raw_request".to_owned()).unwrap().clone(),
1416 )
1417 .unwrap();
1418
1419 assert_eq!(sent_definition, debug_definition);
1420
1421 Ok(())
1422 });
1423 }
1424 })
1425 .ok();
1426
1427 cx.run_until_parked();
1428
1429 assert!(
1430 launch_handler_called.load(Ordering::SeqCst),
1431 "Launch request handler was not called"
1432 );
1433}
1434
1435#[gpui::test]
1436async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut TestAppContext) {
1437 init_test(cx);
1438
1439 let fs = FakeFs::new(executor.clone());
1440
1441 fs.insert_tree(
1442 path!("/project"),
1443 json!({
1444 "main.rs": "First line\nSecond line\nThird line\nFourth line",
1445 "second.rs": "First line\nSecond line\nThird line\nFourth line",
1446 }),
1447 )
1448 .await;
1449
1450 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1451 let workspace = init_test_workspace(&project, cx).await;
1452 let cx = &mut VisualTestContext::from_window(*workspace, cx);
1453 let project_path = Path::new(path!("/project"));
1454 let worktree = project
1455 .update(cx, |project, cx| project.find_worktree(project_path, cx))
1456 .expect("This worktree should exist in project")
1457 .0;
1458
1459 let worktree_id = workspace
1460 .update(cx, |_, _, cx| worktree.read(cx).id())
1461 .unwrap();
1462
1463 let main_buffer = project
1464 .update(cx, |project, cx| {
1465 project.open_buffer((worktree_id, "main.rs"), cx)
1466 })
1467 .await
1468 .unwrap();
1469
1470 let second_buffer = project
1471 .update(cx, |project, cx| {
1472 project.open_buffer((worktree_id, "second.rs"), cx)
1473 })
1474 .await
1475 .unwrap();
1476
1477 let (main_editor, cx) = cx.add_window_view(|window, cx| {
1478 Editor::new(
1479 EditorMode::full(),
1480 MultiBuffer::build_from_buffer(main_buffer, cx),
1481 Some(project.clone()),
1482 window,
1483 cx,
1484 )
1485 });
1486
1487 let (second_editor, cx) = cx.add_window_view(|window, cx| {
1488 Editor::new(
1489 EditorMode::full(),
1490 MultiBuffer::build_from_buffer(second_buffer, cx),
1491 Some(project.clone()),
1492 window,
1493 cx,
1494 )
1495 });
1496
1497 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1498 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1499
1500 client.on_request::<dap::requests::Threads, _>(move |_, _| {
1501 Ok(dap::ThreadsResponse {
1502 threads: vec![dap::Thread {
1503 id: 1,
1504 name: "Thread 1".into(),
1505 }],
1506 })
1507 });
1508
1509 client.on_request::<dap::requests::Scopes, _>(move |_, _| {
1510 Ok(dap::ScopesResponse {
1511 scopes: Vec::default(),
1512 })
1513 });
1514
1515 client.on_request::<StackTrace, _>(move |_, args| {
1516 assert_eq!(args.thread_id, 1);
1517
1518 Ok(dap::StackTraceResponse {
1519 stack_frames: vec![dap::StackFrame {
1520 id: 1,
1521 name: "frame 1".into(),
1522 source: Some(dap::Source {
1523 name: Some("main.rs".into()),
1524 path: Some(path!("/project/main.rs").into()),
1525 source_reference: None,
1526 presentation_hint: None,
1527 origin: None,
1528 sources: None,
1529 adapter_data: None,
1530 checksums: None,
1531 }),
1532 line: 2,
1533 column: 0,
1534 end_line: None,
1535 end_column: None,
1536 can_restart: None,
1537 instruction_pointer_reference: None,
1538 module_id: None,
1539 presentation_hint: None,
1540 }],
1541 total_frames: None,
1542 })
1543 });
1544
1545 client
1546 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1547 reason: dap::StoppedEventReason::Breakpoint,
1548 description: None,
1549 thread_id: Some(1),
1550 preserve_focus_hint: None,
1551 text: None,
1552 all_threads_stopped: None,
1553 hit_breakpoint_ids: None,
1554 }))
1555 .await;
1556
1557 cx.run_until_parked();
1558
1559 main_editor.update_in(cx, |editor, window, cx| {
1560 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1561
1562 assert_eq!(
1563 active_debug_lines.len(),
1564 1,
1565 "There should be only one active debug line"
1566 );
1567
1568 let point = editor
1569 .snapshot(window, cx)
1570 .buffer_snapshot
1571 .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1572
1573 assert_eq!(point.row, 1);
1574 });
1575
1576 second_editor.update(cx, |editor, _| {
1577 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1578
1579 assert!(
1580 active_debug_lines.is_empty(),
1581 "There shouldn't be any active debug lines"
1582 );
1583 });
1584
1585 let handled_second_stacktrace = Arc::new(AtomicBool::new(false));
1586 client.on_request::<StackTrace, _>({
1587 let handled_second_stacktrace = handled_second_stacktrace.clone();
1588 move |_, args| {
1589 handled_second_stacktrace.store(true, Ordering::SeqCst);
1590 assert_eq!(args.thread_id, 1);
1591
1592 Ok(dap::StackTraceResponse {
1593 stack_frames: vec![dap::StackFrame {
1594 id: 2,
1595 name: "frame 2".into(),
1596 source: Some(dap::Source {
1597 name: Some("second.rs".into()),
1598 path: Some(path!("/project/second.rs").into()),
1599 source_reference: None,
1600 presentation_hint: None,
1601 origin: None,
1602 sources: None,
1603 adapter_data: None,
1604 checksums: None,
1605 }),
1606 line: 3,
1607 column: 0,
1608 end_line: None,
1609 end_column: None,
1610 can_restart: None,
1611 instruction_pointer_reference: None,
1612 module_id: None,
1613 presentation_hint: None,
1614 }],
1615 total_frames: None,
1616 })
1617 }
1618 });
1619
1620 client
1621 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1622 reason: dap::StoppedEventReason::Breakpoint,
1623 description: None,
1624 thread_id: Some(1),
1625 preserve_focus_hint: None,
1626 text: None,
1627 all_threads_stopped: None,
1628 hit_breakpoint_ids: None,
1629 }))
1630 .await;
1631
1632 cx.run_until_parked();
1633
1634 second_editor.update_in(cx, |editor, window, cx| {
1635 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1636
1637 assert_eq!(
1638 active_debug_lines.len(),
1639 1,
1640 "There should be only one active debug line"
1641 );
1642
1643 let point = editor
1644 .snapshot(window, cx)
1645 .buffer_snapshot
1646 .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1647
1648 assert_eq!(point.row, 2);
1649 });
1650
1651 main_editor.update(cx, |editor, _| {
1652 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1653
1654 assert!(
1655 active_debug_lines.is_empty(),
1656 "There shouldn't be any active debug lines"
1657 );
1658 });
1659
1660 assert!(
1661 handled_second_stacktrace.load(Ordering::SeqCst),
1662 "Second stacktrace request handler was not called"
1663 );
1664
1665 client
1666 .fake_event(dap::messages::Events::Continued(dap::ContinuedEvent {
1667 thread_id: 0,
1668 all_threads_continued: Some(true),
1669 }))
1670 .await;
1671
1672 cx.run_until_parked();
1673
1674 second_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 main_editor.update(cx, |editor, _| {
1684 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1685
1686 assert!(
1687 active_debug_lines.is_empty(),
1688 "There shouldn't be any active debug lines"
1689 );
1690 });
1691
1692 // Clean up
1693 let shutdown_session = project.update(cx, |project, cx| {
1694 project.dap_store().update(cx, |dap_store, cx| {
1695 dap_store.shutdown_session(session.read(cx).session_id(), cx)
1696 })
1697 });
1698
1699 shutdown_session.await.unwrap();
1700
1701 main_editor.update(cx, |editor, _| {
1702 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1703
1704 assert!(
1705 active_debug_lines.is_empty(),
1706 "There shouldn't be any active debug lines after session shutdown"
1707 );
1708 });
1709
1710 second_editor.update(cx, |editor, _| {
1711 let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1712
1713 assert!(
1714 active_debug_lines.is_empty(),
1715 "There shouldn't be any active debug lines after session shutdown"
1716 );
1717 });
1718}