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