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    client
 427        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 428            request: StartDebuggingRequestArgumentsRequest::Launch,
 429            configuration: fake_config.clone(),
 430        })
 431        .await;
 432
 433    cx.run_until_parked();
 434
 435    workspace
 436        .update(cx, |workspace, _window, cx| {
 437            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 438            let active_session = debug_panel
 439                .read(cx)
 440                .active_session()
 441                .unwrap()
 442                .read(cx)
 443                .session(cx);
 444            let parent_session = active_session.read(cx).parent_session().unwrap();
 445            let mut original_binary = parent_session.read(cx).binary().clone();
 446            original_binary.request_args = StartDebuggingRequestArguments {
 447                request: StartDebuggingRequestArgumentsRequest::Launch,
 448                configuration: fake_config.clone(),
 449            };
 450
 451            assert_eq!(active_session.read(cx).binary(), &original_binary);
 452        })
 453        .unwrap();
 454
 455    assert_eq!(&fake_config, launched_with.lock().as_ref().unwrap());
 456}
 457
 458// // covers that we always send a response back, if something when wrong,
 459// // while spawning the terminal
 460#[gpui::test]
 461async fn test_handle_error_run_in_terminal_reverse_request(
 462    executor: BackgroundExecutor,
 463    cx: &mut TestAppContext,
 464) {
 465    init_test(cx);
 466
 467    let send_response = Arc::new(AtomicBool::new(false));
 468
 469    let fs = FakeFs::new(executor.clone());
 470
 471    fs.insert_tree(
 472        path!("/project"),
 473        json!({
 474            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 475        }),
 476    )
 477    .await;
 478
 479    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 480    let workspace = init_test_workspace(&project, cx).await;
 481    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 482
 483    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 484    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 485
 486    client
 487        .on_response::<RunInTerminal, _>({
 488            let send_response = send_response.clone();
 489            move |response| {
 490                send_response.store(true, Ordering::SeqCst);
 491
 492                assert!(!response.success);
 493                assert!(response.body.is_some());
 494            }
 495        })
 496        .await;
 497
 498    client
 499        .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
 500            kind: None,
 501            title: None,
 502            cwd: "".into(),
 503            args: vec!["oops".into(), "oops".into()],
 504            env: None,
 505            args_can_be_interpreted_by_shell: None,
 506        })
 507        .await;
 508
 509    cx.run_until_parked();
 510
 511    assert!(
 512        send_response.load(std::sync::atomic::Ordering::SeqCst),
 513        "Expected to receive response from reverse request"
 514    );
 515
 516    workspace
 517        .update(cx, |workspace, _window, cx| {
 518            let terminal_panel = workspace.panel::<TerminalPanel>(cx).unwrap();
 519
 520            assert_eq!(
 521                0,
 522                terminal_panel.read(cx).pane().unwrap().read(cx).items_len()
 523            );
 524        })
 525        .unwrap();
 526}
 527
 528#[gpui::test]
 529async fn test_handle_start_debugging_reverse_request(
 530    executor: BackgroundExecutor,
 531    cx: &mut TestAppContext,
 532) {
 533    init_test(cx);
 534
 535    let send_response = Arc::new(AtomicBool::new(false));
 536
 537    let fs = FakeFs::new(executor.clone());
 538
 539    fs.insert_tree(
 540        path!("/project"),
 541        json!({
 542            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 543        }),
 544    )
 545    .await;
 546
 547    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 548    let workspace = init_test_workspace(&project, cx).await;
 549    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 550
 551    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 552    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 553
 554    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 555        Ok(dap::ThreadsResponse {
 556            threads: vec![dap::Thread {
 557                id: 1,
 558                name: "Thread 1".into(),
 559            }],
 560        })
 561    });
 562
 563    client
 564        .on_response::<StartDebugging, _>({
 565            let send_response = send_response.clone();
 566            move |response| {
 567                send_response.store(true, Ordering::SeqCst);
 568
 569                assert!(response.success);
 570                assert!(response.body.is_some());
 571            }
 572        })
 573        .await;
 574    // Set up handlers for sessions spawned with reverse request too.
 575    let _reverse_request_subscription =
 576        project::debugger::test::intercept_debug_sessions(cx, |_| {});
 577    client
 578        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 579            configuration: json!({}),
 580            request: StartDebuggingRequestArgumentsRequest::Launch,
 581        })
 582        .await;
 583
 584    cx.run_until_parked();
 585
 586    let child_session = project.update(cx, |project, cx| {
 587        project
 588            .dap_store()
 589            .read(cx)
 590            .session_by_id(SessionId(1))
 591            .unwrap()
 592    });
 593    let child_client = child_session.update(cx, |session, _| session.adapter_client().unwrap());
 594
 595    child_client.on_request::<dap::requests::Threads, _>(move |_, _| {
 596        Ok(dap::ThreadsResponse {
 597            threads: vec![dap::Thread {
 598                id: 1,
 599                name: "Thread 1".into(),
 600            }],
 601        })
 602    });
 603
 604    child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 605
 606    child_client
 607        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
 608            reason: dap::StoppedEventReason::Pause,
 609            description: None,
 610            thread_id: Some(2),
 611            preserve_focus_hint: None,
 612            text: None,
 613            all_threads_stopped: None,
 614            hit_breakpoint_ids: None,
 615        }))
 616        .await;
 617
 618    cx.run_until_parked();
 619
 620    assert!(
 621        send_response.load(std::sync::atomic::Ordering::SeqCst),
 622        "Expected to receive response from reverse request"
 623    );
 624}
 625
 626#[gpui::test]
 627async fn test_shutdown_children_when_parent_session_shutdown(
 628    executor: BackgroundExecutor,
 629    cx: &mut TestAppContext,
 630) {
 631    init_test(cx);
 632
 633    let fs = FakeFs::new(executor.clone());
 634
 635    fs.insert_tree(
 636        path!("/project"),
 637        json!({
 638            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 639        }),
 640    )
 641    .await;
 642
 643    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 644    let dap_store = project.update(cx, |project, _| project.dap_store());
 645    let workspace = init_test_workspace(&project, cx).await;
 646    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 647
 648    let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 649    let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
 650
 651    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 652        Ok(dap::ThreadsResponse {
 653            threads: vec![dap::Thread {
 654                id: 1,
 655                name: "Thread 1".into(),
 656            }],
 657        })
 658    });
 659
 660    client.on_response::<StartDebugging, _>(move |_| {}).await;
 661    // Set up handlers for sessions spawned with reverse request too.
 662    let _reverse_request_subscription =
 663        project::debugger::test::intercept_debug_sessions(cx, |_| {});
 664    // start first child session
 665    client
 666        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 667            configuration: json!({}),
 668            request: StartDebuggingRequestArgumentsRequest::Launch,
 669        })
 670        .await;
 671
 672    cx.run_until_parked();
 673
 674    // start second child session
 675    client
 676        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 677            configuration: json!({}),
 678            request: StartDebuggingRequestArgumentsRequest::Launch,
 679        })
 680        .await;
 681
 682    cx.run_until_parked();
 683
 684    // configure first child session
 685    let first_child_session = dap_store.read_with(cx, |dap_store, _| {
 686        dap_store.session_by_id(SessionId(1)).unwrap()
 687    });
 688    let first_child_client =
 689        first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 690
 691    first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 692
 693    // configure second child session
 694    let second_child_session = dap_store.read_with(cx, |dap_store, _| {
 695        dap_store.session_by_id(SessionId(2)).unwrap()
 696    });
 697    let second_child_client =
 698        second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 699
 700    second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 701
 702    cx.run_until_parked();
 703
 704    // shutdown parent session
 705    dap_store
 706        .update(cx, |dap_store, cx| {
 707            dap_store.shutdown_session(parent_session.read(cx).session_id(), cx)
 708        })
 709        .await
 710        .unwrap();
 711
 712    // assert parent session and all children sessions are shutdown
 713    dap_store.update(cx, |dap_store, cx| {
 714        assert!(
 715            dap_store
 716                .session_by_id(parent_session.read(cx).session_id())
 717                .is_none()
 718        );
 719        assert!(
 720            dap_store
 721                .session_by_id(first_child_session.read(cx).session_id())
 722                .is_none()
 723        );
 724        assert!(
 725            dap_store
 726                .session_by_id(second_child_session.read(cx).session_id())
 727                .is_none()
 728        );
 729    });
 730}
 731
 732#[gpui::test]
 733async fn test_shutdown_parent_session_if_all_children_are_shutdown(
 734    executor: BackgroundExecutor,
 735    cx: &mut TestAppContext,
 736) {
 737    init_test(cx);
 738
 739    let fs = FakeFs::new(executor.clone());
 740
 741    fs.insert_tree(
 742        path!("/project"),
 743        json!({
 744            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 745        }),
 746    )
 747    .await;
 748
 749    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 750    let dap_store = project.update(cx, |project, _| project.dap_store());
 751    let workspace = init_test_workspace(&project, cx).await;
 752    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 753
 754    let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 755    let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
 756
 757    client.on_response::<StartDebugging, _>(move |_| {}).await;
 758    // Set up handlers for sessions spawned with reverse request too.
 759    let _reverse_request_subscription =
 760        project::debugger::test::intercept_debug_sessions(cx, |_| {});
 761    // start first child session
 762    client
 763        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 764            configuration: json!({}),
 765            request: StartDebuggingRequestArgumentsRequest::Launch,
 766        })
 767        .await;
 768
 769    cx.run_until_parked();
 770
 771    // start second child session
 772    client
 773        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 774            configuration: json!({}),
 775            request: StartDebuggingRequestArgumentsRequest::Launch,
 776        })
 777        .await;
 778
 779    cx.run_until_parked();
 780
 781    // configure first child session
 782    let first_child_session = dap_store.read_with(cx, |dap_store, _| {
 783        dap_store.session_by_id(SessionId(1)).unwrap()
 784    });
 785    let first_child_client =
 786        first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 787
 788    first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 789
 790    // configure second child session
 791    let second_child_session = dap_store.read_with(cx, |dap_store, _| {
 792        dap_store.session_by_id(SessionId(2)).unwrap()
 793    });
 794    let second_child_client =
 795        second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 796
 797    second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 798
 799    cx.run_until_parked();
 800
 801    // shutdown first child session
 802    dap_store
 803        .update(cx, |dap_store, cx| {
 804            dap_store.shutdown_session(first_child_session.read(cx).session_id(), cx)
 805        })
 806        .await
 807        .unwrap();
 808
 809    // assert parent session and second child session still exist
 810    dap_store.update(cx, |dap_store, cx| {
 811        assert!(
 812            dap_store
 813                .session_by_id(parent_session.read(cx).session_id())
 814                .is_some()
 815        );
 816        assert!(
 817            dap_store
 818                .session_by_id(first_child_session.read(cx).session_id())
 819                .is_none()
 820        );
 821        assert!(
 822            dap_store
 823                .session_by_id(second_child_session.read(cx).session_id())
 824                .is_some()
 825        );
 826    });
 827
 828    // shutdown first child session
 829    dap_store
 830        .update(cx, |dap_store, cx| {
 831            dap_store.shutdown_session(second_child_session.read(cx).session_id(), cx)
 832        })
 833        .await
 834        .unwrap();
 835
 836    // assert parent session got shutdown by second child session
 837    // because it was the last child
 838    dap_store.update(cx, |dap_store, cx| {
 839        assert!(
 840            dap_store
 841                .session_by_id(parent_session.read(cx).session_id())
 842                .is_none()
 843        );
 844        assert!(
 845            dap_store
 846                .session_by_id(second_child_session.read(cx).session_id())
 847                .is_none()
 848        );
 849    });
 850}
 851
 852#[gpui::test]
 853async fn test_debug_panel_item_thread_status_reset_on_failure(
 854    executor: BackgroundExecutor,
 855    cx: &mut TestAppContext,
 856) {
 857    init_test(cx);
 858
 859    let fs = FakeFs::new(executor.clone());
 860
 861    fs.insert_tree(
 862        path!("/project"),
 863        json!({
 864            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 865        }),
 866    )
 867    .await;
 868
 869    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 870    let workspace = init_test_workspace(&project, cx).await;
 871    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 872
 873    let session = start_debug_session(&workspace, cx, |client| {
 874        client.on_request::<dap::requests::Initialize, _>(move |_, _| {
 875            Ok(dap::Capabilities {
 876                supports_step_back: Some(true),
 877                ..Default::default()
 878            })
 879        });
 880    })
 881    .unwrap();
 882
 883    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 884    const THREAD_ID_NUM: u64 = 1;
 885
 886    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 887        Ok(dap::ThreadsResponse {
 888            threads: vec![dap::Thread {
 889                id: THREAD_ID_NUM,
 890                name: "Thread 1".into(),
 891            }],
 892        })
 893    });
 894
 895    client.on_request::<Launch, _>(move |_, _| Ok(()));
 896
 897    client.on_request::<StackTrace, _>(move |_, _| {
 898        Ok(dap::StackTraceResponse {
 899            stack_frames: Vec::default(),
 900            total_frames: None,
 901        })
 902    });
 903
 904    client.on_request::<Next, _>(move |_, _| {
 905        Err(ErrorResponse {
 906            error: Some(dap::Message {
 907                id: 1,
 908                format: "error".into(),
 909                variables: None,
 910                send_telemetry: None,
 911                show_user: None,
 912                url: None,
 913                url_label: None,
 914            }),
 915        })
 916    });
 917
 918    client.on_request::<StepOut, _>(move |_, _| {
 919        Err(ErrorResponse {
 920            error: Some(dap::Message {
 921                id: 1,
 922                format: "error".into(),
 923                variables: None,
 924                send_telemetry: None,
 925                show_user: None,
 926                url: None,
 927                url_label: None,
 928            }),
 929        })
 930    });
 931
 932    client.on_request::<StepIn, _>(move |_, _| {
 933        Err(ErrorResponse {
 934            error: Some(dap::Message {
 935                id: 1,
 936                format: "error".into(),
 937                variables: None,
 938                send_telemetry: None,
 939                show_user: None,
 940                url: None,
 941                url_label: None,
 942            }),
 943        })
 944    });
 945
 946    client.on_request::<StepBack, _>(move |_, _| {
 947        Err(ErrorResponse {
 948            error: Some(dap::Message {
 949                id: 1,
 950                format: "error".into(),
 951                variables: None,
 952                send_telemetry: None,
 953                show_user: None,
 954                url: None,
 955                url_label: None,
 956            }),
 957        })
 958    });
 959
 960    client.on_request::<Continue, _>(move |_, _| {
 961        Err(ErrorResponse {
 962            error: Some(dap::Message {
 963                id: 1,
 964                format: "error".into(),
 965                variables: None,
 966                send_telemetry: None,
 967                show_user: None,
 968                url: None,
 969                url_label: None,
 970            }),
 971        })
 972    });
 973
 974    client
 975        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
 976            reason: dap::StoppedEventReason::Pause,
 977            description: None,
 978            thread_id: Some(1),
 979            preserve_focus_hint: None,
 980            text: None,
 981            all_threads_stopped: None,
 982            hit_breakpoint_ids: None,
 983        }))
 984        .await;
 985
 986    cx.run_until_parked();
 987
 988    let running_state = active_debug_session_panel(workspace, cx)
 989        .update(cx, |item, _| item.running_state().clone());
 990
 991    cx.run_until_parked();
 992    let thread_id = ThreadId(1);
 993
 994    for operation in &[
 995        "step_over",
 996        "continue_thread",
 997        "step_back",
 998        "step_in",
 999        "step_out",
1000    ] {
1001        running_state.update(cx, |running_state, cx| match *operation {
1002            "step_over" => running_state.step_over(cx),
1003            "continue_thread" => running_state.continue_thread(cx),
1004            "step_back" => running_state.step_back(cx),
1005            "step_in" => running_state.step_in(cx),
1006            "step_out" => running_state.step_out(cx),
1007            _ => unreachable!(),
1008        });
1009
1010        // Check that we step the thread status to the correct intermediate state
1011        running_state.update(cx, |running_state, cx| {
1012            assert_eq!(
1013                running_state
1014                    .thread_status(cx)
1015                    .expect("There should be an active thread selected"),
1016                match *operation {
1017                    "continue_thread" => ThreadStatus::Running,
1018                    _ => ThreadStatus::Stepping,
1019                },
1020                "Thread status was not set to correct intermediate state after {} request",
1021                operation
1022            );
1023        });
1024
1025        cx.run_until_parked();
1026
1027        running_state.update(cx, |running_state, cx| {
1028            assert_eq!(
1029                running_state
1030                    .thread_status(cx)
1031                    .expect("There should be an active thread selected"),
1032                ThreadStatus::Stopped,
1033                "Thread status not reset to Stopped after failed {}",
1034                operation
1035            );
1036
1037            // update state to running, so we can test it actually changes the status back to stopped
1038            running_state
1039                .session()
1040                .update(cx, |session, cx| session.continue_thread(thread_id, cx));
1041        });
1042    }
1043}
1044
1045#[gpui::test]
1046async fn test_send_breakpoints_when_editor_has_been_saved(
1047    executor: BackgroundExecutor,
1048    cx: &mut TestAppContext,
1049) {
1050    init_test(cx);
1051
1052    let fs = FakeFs::new(executor.clone());
1053
1054    fs.insert_tree(
1055        path!("/project"),
1056        json!({
1057            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1058        }),
1059    )
1060    .await;
1061
1062    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1063    let workspace = init_test_workspace(&project, cx).await;
1064    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1065    let project_path = Path::new(path!("/project"));
1066    let worktree = project
1067        .update(cx, |project, cx| project.find_worktree(project_path, cx))
1068        .expect("This worktree should exist in project")
1069        .0;
1070
1071    let worktree_id = workspace
1072        .update(cx, |_, _, cx| worktree.read(cx).id())
1073        .unwrap();
1074
1075    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1076    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1077
1078    let buffer = project
1079        .update(cx, |project, cx| {
1080            project.open_buffer((worktree_id, "main.rs"), cx)
1081        })
1082        .await
1083        .unwrap();
1084
1085    let (editor, cx) = cx.add_window_view(|window, cx| {
1086        Editor::new(
1087            EditorMode::full(),
1088            MultiBuffer::build_from_buffer(buffer, cx),
1089            Some(project.clone()),
1090            window,
1091            cx,
1092        )
1093    });
1094
1095    client.on_request::<Launch, _>(move |_, _| Ok(()));
1096
1097    client.on_request::<StackTrace, _>(move |_, _| {
1098        Ok(dap::StackTraceResponse {
1099            stack_frames: Vec::default(),
1100            total_frames: None,
1101        })
1102    });
1103
1104    client
1105        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1106            reason: dap::StoppedEventReason::Pause,
1107            description: None,
1108            thread_id: Some(1),
1109            preserve_focus_hint: None,
1110            text: None,
1111            all_threads_stopped: None,
1112            hit_breakpoint_ids: None,
1113        }))
1114        .await;
1115
1116    let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1117    client.on_request::<SetBreakpoints, _>({
1118        let called_set_breakpoints = called_set_breakpoints.clone();
1119        move |_, args| {
1120            assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1121            assert_eq!(
1122                vec![SourceBreakpoint {
1123                    line: 2,
1124                    column: None,
1125                    condition: None,
1126                    hit_condition: None,
1127                    log_message: None,
1128                    mode: None
1129                }],
1130                args.breakpoints.unwrap()
1131            );
1132            assert!(!args.source_modified.unwrap());
1133
1134            called_set_breakpoints.store(true, Ordering::SeqCst);
1135
1136            Ok(dap::SetBreakpointsResponse {
1137                breakpoints: Vec::default(),
1138            })
1139        }
1140    });
1141
1142    editor.update_in(cx, |editor, window, cx| {
1143        editor.move_down(&actions::MoveDown, window, cx);
1144        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1145    });
1146
1147    cx.run_until_parked();
1148
1149    assert!(
1150        called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1151        "SetBreakpoint request must be called"
1152    );
1153
1154    let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1155    client.on_request::<SetBreakpoints, _>({
1156        let called_set_breakpoints = called_set_breakpoints.clone();
1157        move |_, args| {
1158            assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1159            assert_eq!(
1160                vec![SourceBreakpoint {
1161                    line: 3,
1162                    column: None,
1163                    condition: None,
1164                    hit_condition: None,
1165                    log_message: None,
1166                    mode: None
1167                }],
1168                args.breakpoints.unwrap()
1169            );
1170            assert!(args.source_modified.unwrap());
1171
1172            called_set_breakpoints.store(true, Ordering::SeqCst);
1173
1174            Ok(dap::SetBreakpointsResponse {
1175                breakpoints: Vec::default(),
1176            })
1177        }
1178    });
1179
1180    editor.update_in(cx, |editor, window, cx| {
1181        editor.move_up(&actions::MoveUp, window, cx);
1182        editor.insert("new text\n", window, cx);
1183    });
1184
1185    editor
1186        .update_in(cx, |editor, window, cx| {
1187            editor.save(true, project.clone(), window, cx)
1188        })
1189        .await
1190        .unwrap();
1191
1192    cx.run_until_parked();
1193
1194    assert!(
1195        called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1196        "SetBreakpoint request must be called after editor is saved"
1197    );
1198}
1199
1200#[gpui::test]
1201async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
1202    executor: BackgroundExecutor,
1203    cx: &mut TestAppContext,
1204) {
1205    init_test(cx);
1206
1207    let fs = FakeFs::new(executor.clone());
1208
1209    fs.insert_tree(
1210        path!("/project"),
1211        json!({
1212            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1213            "second.rs": "First line\nSecond line\nThird line\nFourth line",
1214            "no_breakpoints.rs": "Used to ensure that we don't unset breakpoint in files with no breakpoints"
1215        }),
1216    )
1217    .await;
1218
1219    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1220    let workspace = init_test_workspace(&project, cx).await;
1221    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1222    let project_path = Path::new(path!("/project"));
1223    let worktree = project
1224        .update(cx, |project, cx| project.find_worktree(project_path, cx))
1225        .expect("This worktree should exist in project")
1226        .0;
1227
1228    let worktree_id = workspace
1229        .update(cx, |_, _, cx| worktree.read(cx).id())
1230        .unwrap();
1231
1232    let first = project
1233        .update(cx, |project, cx| {
1234            project.open_buffer((worktree_id, "main.rs"), cx)
1235        })
1236        .await
1237        .unwrap();
1238
1239    let second = project
1240        .update(cx, |project, cx| {
1241            project.open_buffer((worktree_id, "second.rs"), cx)
1242        })
1243        .await
1244        .unwrap();
1245
1246    let (first_editor, cx) = cx.add_window_view(|window, cx| {
1247        Editor::new(
1248            EditorMode::full(),
1249            MultiBuffer::build_from_buffer(first, cx),
1250            Some(project.clone()),
1251            window,
1252            cx,
1253        )
1254    });
1255
1256    let (second_editor, cx) = cx.add_window_view(|window, cx| {
1257        Editor::new(
1258            EditorMode::full(),
1259            MultiBuffer::build_from_buffer(second, cx),
1260            Some(project.clone()),
1261            window,
1262            cx,
1263        )
1264    });
1265
1266    first_editor.update_in(cx, |editor, window, cx| {
1267        editor.move_down(&actions::MoveDown, window, cx);
1268        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1269        editor.move_down(&actions::MoveDown, window, cx);
1270        editor.move_down(&actions::MoveDown, window, cx);
1271        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1272    });
1273
1274    second_editor.update_in(cx, |editor, window, cx| {
1275        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1276        editor.move_down(&actions::MoveDown, window, cx);
1277        editor.move_down(&actions::MoveDown, window, cx);
1278        editor.move_down(&actions::MoveDown, window, cx);
1279        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1280    });
1281
1282    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1283    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1284
1285    let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1286
1287    client.on_request::<SetBreakpoints, _>({
1288        let called_set_breakpoints = called_set_breakpoints.clone();
1289        move |_, args| {
1290            assert!(
1291                args.breakpoints.is_none_or(|bps| bps.is_empty()),
1292                "Send empty breakpoint sets to clear them from DAP servers"
1293            );
1294
1295            match args
1296                .source
1297                .path
1298                .expect("We should always send a breakpoint's path")
1299                .as_str()
1300            {
1301                path!("/project/main.rs") | path!("/project/second.rs") => {}
1302                _ => {
1303                    panic!("Unset breakpoints for path that doesn't have any")
1304                }
1305            }
1306
1307            called_set_breakpoints.store(true, Ordering::SeqCst);
1308
1309            Ok(dap::SetBreakpointsResponse {
1310                breakpoints: Vec::default(),
1311            })
1312        }
1313    });
1314
1315    cx.dispatch_action(crate::ClearAllBreakpoints);
1316    cx.run_until_parked();
1317}
1318
1319#[gpui::test]
1320async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
1321    executor: BackgroundExecutor,
1322    cx: &mut TestAppContext,
1323) {
1324    init_test(cx);
1325
1326    let fs = FakeFs::new(executor.clone());
1327
1328    fs.insert_tree(
1329        path!("/project"),
1330        json!({
1331            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1332        }),
1333    )
1334    .await;
1335
1336    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1337    let workspace = init_test_workspace(&project, cx).await;
1338    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1339
1340    start_debug_session(&workspace, cx, |client| {
1341        client.on_request::<dap::requests::Initialize, _>(|_, _| {
1342            Err(ErrorResponse {
1343                error: Some(Message {
1344                    format: "failed to launch".to_string(),
1345                    id: 1,
1346                    variables: None,
1347                    send_telemetry: None,
1348                    show_user: None,
1349                    url: None,
1350                    url_label: None,
1351                }),
1352            })
1353        });
1354    })
1355    .ok();
1356
1357    cx.run_until_parked();
1358
1359    project.update(cx, |project, cx| {
1360        assert!(
1361            project.dap_store().read(cx).sessions().count() == 0,
1362            "Session wouldn't exist if it was shutdown"
1363        );
1364    });
1365}
1366
1367#[gpui::test]
1368async fn test_we_send_arguments_from_user_config(
1369    executor: BackgroundExecutor,
1370    cx: &mut TestAppContext,
1371) {
1372    init_test(cx);
1373
1374    let fs = FakeFs::new(executor.clone());
1375
1376    fs.insert_tree(
1377        path!("/project"),
1378        json!({
1379            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1380        }),
1381    )
1382    .await;
1383
1384    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1385    let workspace = init_test_workspace(&project, cx).await;
1386    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1387    let debug_definition = DebugTaskDefinition {
1388        adapter: "fake-adapter".into(),
1389        config: json!({
1390            "request": "launch",
1391            "program": "main.rs".to_owned(),
1392            "args": vec!["arg1".to_owned(), "arg2".to_owned()],
1393            "cwd": path!("/Random_path"),
1394            "env": json!({ "KEY": "VALUE" }),
1395        }),
1396        label: "test".into(),
1397        tcp_connection: None,
1398    };
1399
1400    let launch_handler_called = Arc::new(AtomicBool::new(false));
1401
1402    start_debug_session_with(&workspace, cx, debug_definition.clone(), {
1403        let debug_definition = debug_definition.clone();
1404        let launch_handler_called = launch_handler_called.clone();
1405
1406        move |client| {
1407            let debug_definition = debug_definition.clone();
1408            let launch_handler_called = launch_handler_called.clone();
1409
1410            client.on_request::<dap::requests::Launch, _>(move |_, args| {
1411                launch_handler_called.store(true, Ordering::SeqCst);
1412
1413                let obj = args.raw.as_object().unwrap();
1414                let sent_definition = serde_json::from_value::<DebugTaskDefinition>(
1415                    obj.get(&"raw_request".to_owned()).unwrap().clone(),
1416                )
1417                .unwrap();
1418
1419                assert_eq!(sent_definition, debug_definition);
1420
1421                Ok(())
1422            });
1423        }
1424    })
1425    .ok();
1426
1427    cx.run_until_parked();
1428
1429    assert!(
1430        launch_handler_called.load(Ordering::SeqCst),
1431        "Launch request handler was not called"
1432    );
1433}
1434
1435#[gpui::test]
1436async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut TestAppContext) {
1437    init_test(cx);
1438
1439    let fs = FakeFs::new(executor.clone());
1440
1441    fs.insert_tree(
1442        path!("/project"),
1443        json!({
1444            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1445            "second.rs": "First line\nSecond line\nThird line\nFourth line",
1446        }),
1447    )
1448    .await;
1449
1450    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1451    let workspace = init_test_workspace(&project, cx).await;
1452    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1453    let project_path = Path::new(path!("/project"));
1454    let worktree = project
1455        .update(cx, |project, cx| project.find_worktree(project_path, cx))
1456        .expect("This worktree should exist in project")
1457        .0;
1458
1459    let worktree_id = workspace
1460        .update(cx, |_, _, cx| worktree.read(cx).id())
1461        .unwrap();
1462
1463    let main_buffer = project
1464        .update(cx, |project, cx| {
1465            project.open_buffer((worktree_id, "main.rs"), cx)
1466        })
1467        .await
1468        .unwrap();
1469
1470    let second_buffer = project
1471        .update(cx, |project, cx| {
1472            project.open_buffer((worktree_id, "second.rs"), cx)
1473        })
1474        .await
1475        .unwrap();
1476
1477    let (main_editor, cx) = cx.add_window_view(|window, cx| {
1478        Editor::new(
1479            EditorMode::full(),
1480            MultiBuffer::build_from_buffer(main_buffer, cx),
1481            Some(project.clone()),
1482            window,
1483            cx,
1484        )
1485    });
1486
1487    let (second_editor, cx) = cx.add_window_view(|window, cx| {
1488        Editor::new(
1489            EditorMode::full(),
1490            MultiBuffer::build_from_buffer(second_buffer, cx),
1491            Some(project.clone()),
1492            window,
1493            cx,
1494        )
1495    });
1496
1497    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1498    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1499
1500    client.on_request::<dap::requests::Threads, _>(move |_, _| {
1501        Ok(dap::ThreadsResponse {
1502            threads: vec![dap::Thread {
1503                id: 1,
1504                name: "Thread 1".into(),
1505            }],
1506        })
1507    });
1508
1509    client.on_request::<dap::requests::Scopes, _>(move |_, _| {
1510        Ok(dap::ScopesResponse {
1511            scopes: Vec::default(),
1512        })
1513    });
1514
1515    client.on_request::<StackTrace, _>(move |_, args| {
1516        assert_eq!(args.thread_id, 1);
1517
1518        Ok(dap::StackTraceResponse {
1519            stack_frames: vec![dap::StackFrame {
1520                id: 1,
1521                name: "frame 1".into(),
1522                source: Some(dap::Source {
1523                    name: Some("main.rs".into()),
1524                    path: Some(path!("/project/main.rs").into()),
1525                    source_reference: None,
1526                    presentation_hint: None,
1527                    origin: None,
1528                    sources: None,
1529                    adapter_data: None,
1530                    checksums: None,
1531                }),
1532                line: 2,
1533                column: 0,
1534                end_line: None,
1535                end_column: None,
1536                can_restart: None,
1537                instruction_pointer_reference: None,
1538                module_id: None,
1539                presentation_hint: None,
1540            }],
1541            total_frames: None,
1542        })
1543    });
1544
1545    client
1546        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1547            reason: dap::StoppedEventReason::Breakpoint,
1548            description: None,
1549            thread_id: Some(1),
1550            preserve_focus_hint: None,
1551            text: None,
1552            all_threads_stopped: None,
1553            hit_breakpoint_ids: None,
1554        }))
1555        .await;
1556
1557    cx.run_until_parked();
1558
1559    main_editor.update_in(cx, |editor, window, cx| {
1560        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1561
1562        assert_eq!(
1563            active_debug_lines.len(),
1564            1,
1565            "There should be only one active debug line"
1566        );
1567
1568        let point = editor
1569            .snapshot(window, cx)
1570            .buffer_snapshot
1571            .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1572
1573        assert_eq!(point.row, 1);
1574    });
1575
1576    second_editor.update(cx, |editor, _| {
1577        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1578
1579        assert!(
1580            active_debug_lines.is_empty(),
1581            "There shouldn't be any active debug lines"
1582        );
1583    });
1584
1585    let handled_second_stacktrace = Arc::new(AtomicBool::new(false));
1586    client.on_request::<StackTrace, _>({
1587        let handled_second_stacktrace = handled_second_stacktrace.clone();
1588        move |_, args| {
1589            handled_second_stacktrace.store(true, Ordering::SeqCst);
1590            assert_eq!(args.thread_id, 1);
1591
1592            Ok(dap::StackTraceResponse {
1593                stack_frames: vec![dap::StackFrame {
1594                    id: 2,
1595                    name: "frame 2".into(),
1596                    source: Some(dap::Source {
1597                        name: Some("second.rs".into()),
1598                        path: Some(path!("/project/second.rs").into()),
1599                        source_reference: None,
1600                        presentation_hint: None,
1601                        origin: None,
1602                        sources: None,
1603                        adapter_data: None,
1604                        checksums: None,
1605                    }),
1606                    line: 3,
1607                    column: 0,
1608                    end_line: None,
1609                    end_column: None,
1610                    can_restart: None,
1611                    instruction_pointer_reference: None,
1612                    module_id: None,
1613                    presentation_hint: None,
1614                }],
1615                total_frames: None,
1616            })
1617        }
1618    });
1619
1620    client
1621        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1622            reason: dap::StoppedEventReason::Breakpoint,
1623            description: None,
1624            thread_id: Some(1),
1625            preserve_focus_hint: None,
1626            text: None,
1627            all_threads_stopped: None,
1628            hit_breakpoint_ids: None,
1629        }))
1630        .await;
1631
1632    cx.run_until_parked();
1633
1634    second_editor.update_in(cx, |editor, window, cx| {
1635        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1636
1637        assert_eq!(
1638            active_debug_lines.len(),
1639            1,
1640            "There should be only one active debug line"
1641        );
1642
1643        let point = editor
1644            .snapshot(window, cx)
1645            .buffer_snapshot
1646            .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1647
1648        assert_eq!(point.row, 2);
1649    });
1650
1651    main_editor.update(cx, |editor, _| {
1652        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1653
1654        assert!(
1655            active_debug_lines.is_empty(),
1656            "There shouldn't be any active debug lines"
1657        );
1658    });
1659
1660    assert!(
1661        handled_second_stacktrace.load(Ordering::SeqCst),
1662        "Second stacktrace request handler was not called"
1663    );
1664
1665    client
1666        .fake_event(dap::messages::Events::Continued(dap::ContinuedEvent {
1667            thread_id: 0,
1668            all_threads_continued: Some(true),
1669        }))
1670        .await;
1671
1672    cx.run_until_parked();
1673
1674    second_editor.update(cx, |editor, _| {
1675        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1676
1677        assert!(
1678            active_debug_lines.is_empty(),
1679            "There shouldn't be any active debug lines"
1680        );
1681    });
1682
1683    main_editor.update(cx, |editor, _| {
1684        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1685
1686        assert!(
1687            active_debug_lines.is_empty(),
1688            "There shouldn't be any active debug lines"
1689        );
1690    });
1691
1692    // Clean up
1693    let shutdown_session = project.update(cx, |project, cx| {
1694        project.dap_store().update(cx, |dap_store, cx| {
1695            dap_store.shutdown_session(session.read(cx).session_id(), cx)
1696        })
1697    });
1698
1699    shutdown_session.await.unwrap();
1700
1701    main_editor.update(cx, |editor, _| {
1702        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1703
1704        assert!(
1705            active_debug_lines.is_empty(),
1706            "There shouldn't be any active debug lines after session shutdown"
1707        );
1708    });
1709
1710    second_editor.update(cx, |editor, _| {
1711        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1712
1713        assert!(
1714            active_debug_lines.is_empty(),
1715            "There shouldn't be any active debug lines after session shutdown"
1716        );
1717    });
1718}