debugger_panel.rs

   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}