use std::sync::{ Arc, atomic::{AtomicBool, Ordering},}; use crate::{ DebugPanel, persistence::DebuggerPaneItem, session::running::variable_list::{ AddWatch, CollapseSelectedEntry, ExpandSelectedEntry, RemoveWatch, }, tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},};use collections::HashMap;use dap::{ Scope, StackFrame, Variable, requests::{Evaluate, Initialize, Launch, Scopes, StackTrace, Variables},};use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};use menu::{SelectFirst, SelectNext, SelectPrevious};use project::{FakeFs, Project};use serde_json::json;use ui::SharedString;use unindent::Unindent as _;use util::path; /// This only tests fetching one scope and 2 variables for a single stackframe#[gpui::test]async fn test_basic_fetch_initial_scope_and_variables( executor: BackgroundExecutor, cx: &mut TestAppContext,) { init_test(cx); let fs = FakeFs::new(executor.clone()); let test_file_content = r#" const variable1 = "Value 1"; const variable2 = "Value 2"; "# .unindent(); fs.insert_tree( path!("/project"), json!({ "src": { "test.js": test_file_content, } }), ) .await; let project = Project::test(fs, [path!("/project").as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; workspace .update(cx, |workspace, window, cx| { workspace.focus_panel::<DebugPanel>(window, cx); }) .unwrap(); let cx = &mut VisualTestContext::from_window(*workspace, cx); let session = start_debug_session(&workspace, cx, |_| {}).unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); client.on_request::<dap::requests::Threads, _>(move |_, _| { Ok(dap::ThreadsResponse { threads: vec![dap::Thread { id: 1, name: "Thread 1".into(), }], }) }); let stack_frames = vec![StackFrame { id: 1, name: "Stack Frame 1".into(), source: Some(dap::Source { name: Some("test.js".into()), path: Some(path!("/project/src/test.js").into()), source_reference: None, presentation_hint: None, origin: None, sources: None, adapter_data: None, checksums: None, }), line: 1, column: 1, end_line: None, end_column: None, can_restart: None, instruction_pointer_reference: None, module_id: None, presentation_hint: None, }]; client.on_request::<StackTrace, _>({ let stack_frames = Arc::new(stack_frames.clone()); move |_, args| { assert_eq!(1, args.thread_id); Ok(dap::StackTraceResponse { stack_frames: (*stack_frames).clone(), total_frames: None, }) } }); let scopes = vec![Scope { name: "Scope 1".into(), presentation_hint: None, variables_reference: 2, named_variables: None, indexed_variables: None, expensive: false, source: None, line: None, column: None, end_line: None, end_column: None, }]; client.on_request::<Scopes, _>({ let scopes = Arc::new(scopes.clone()); move |_, args| { assert_eq!(1, args.frame_id); Ok(dap::ScopesResponse { scopes: (*scopes).clone(), }) } }); let variables = vec![ Variable { name: "variable1".into(), value: "value 1".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }, Variable { name: "variable2".into(), value: "value 2".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }, ]; client.on_request::<Variables, _>({ let variables = Arc::new(variables.clone()); move |_, args| { assert_eq!(2, args.variables_reference); Ok(dap::VariablesResponse { variables: (*variables).clone(), }) } }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { reason: dap::StoppedEventReason::Pause, description: None, thread_id: Some(1), preserve_focus_hint: None, text: None, all_threads_stopped: None, hit_breakpoint_ids: None, })) .await; cx.run_until_parked(); let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| { cx.focus_self(window); item.running_state().clone() }); cx.run_until_parked(); running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { ( list.flatten_entries(true, true), list.opened_stack_frame_id(), ) }); assert_eq!(stack_frames, stack_frame_list); assert_eq!(Some(1), stack_frame_id); running_state .variable_list() .update(cx, |variable_list, _| { assert_eq!(scopes, variable_list.scopes()); assert_eq!( vec![variables[0].clone(), variables[1].clone(),], variable_list.variables() ); variable_list.assert_visual_entries(vec![ "v Scope 1", " > variable1", " > variable2", ]); }); });} /// This tests fetching multiple scopes and variables for them with a single stackframe#[gpui::test]async fn test_fetch_variables_for_multiple_scopes( executor: BackgroundExecutor, cx: &mut TestAppContext,) { init_test(cx); let fs = FakeFs::new(executor.clone()); let test_file_content = r#" const variable1 = { nested1: "Nested 1", nested2: "Nested 2", }; const variable2 = "Value 2"; const variable3 = "Value 3"; "# .unindent(); fs.insert_tree( path!("/project"), json!({ "src": { "test.js": test_file_content, } }), ) .await; let project = Project::test(fs, [path!("/project").as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; workspace .update(cx, |workspace, window, cx| { workspace.focus_panel::<DebugPanel>(window, cx); }) .unwrap(); let cx = &mut VisualTestContext::from_window(*workspace, cx); let session = start_debug_session(&workspace, cx, |_| {}).unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); client.on_request::<dap::requests::Threads, _>(move |_, _| { Ok(dap::ThreadsResponse { threads: vec![dap::Thread { id: 1, name: "Thread 1".into(), }], }) }); client.on_request::<Initialize, _>(move |_, _| { Ok(dap::Capabilities { supports_step_back: Some(false), ..Default::default() }) }); client.on_request::<Launch, _>(move |_, _| Ok(())); let stack_frames = vec![StackFrame { id: 1, name: "Stack Frame 1".into(), source: Some(dap::Source { name: Some("test.js".into()), path: Some(path!("/project/src/test.js").into()), source_reference: None, presentation_hint: None, origin: None, sources: None, adapter_data: None, checksums: None, }), line: 1, column: 1, end_line: None, end_column: None, can_restart: None, instruction_pointer_reference: None, module_id: None, presentation_hint: None, }]; client.on_request::<StackTrace, _>({ let stack_frames = Arc::new(stack_frames.clone()); move |_, args| { assert_eq!(1, args.thread_id); Ok(dap::StackTraceResponse { stack_frames: (*stack_frames).clone(), total_frames: None, }) } }); let scopes = vec![ Scope { name: "Scope 1".into(), presentation_hint: Some(dap::ScopePresentationHint::Locals), variables_reference: 2, named_variables: None, indexed_variables: None, expensive: false, source: None, line: None, column: None, end_line: None, end_column: None, }, Scope { name: "Scope 2".into(), presentation_hint: None, variables_reference: 3, named_variables: None, indexed_variables: None, expensive: false, source: None, line: None, column: None, end_line: None, end_column: None, }, ]; client.on_request::<Scopes, _>({ let scopes = Arc::new(scopes.clone()); move |_, args| { assert_eq!(1, args.frame_id); Ok(dap::ScopesResponse { scopes: (*scopes).clone(), }) } }); let mut variables = HashMap::default(); variables.insert( 2, vec![ Variable { name: "variable1".into(), value: "{nested1: \"Nested 1\", nested2: \"Nested 2\"}".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }, Variable { name: "variable2".into(), value: "Value 2".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }, ], ); variables.insert( 3, vec![Variable { name: "variable3".into(), value: "Value 3".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }], ); client.on_request::<Variables, _>({ let variables = Arc::new(variables.clone()); move |_, args| { Ok(dap::VariablesResponse { variables: variables.get(&args.variables_reference).unwrap().clone(), }) } }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { reason: dap::StoppedEventReason::Pause, description: None, thread_id: Some(1), preserve_focus_hint: None, text: None, all_threads_stopped: None, hit_breakpoint_ids: None, })) .await; cx.run_until_parked(); let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| { cx.focus_self(window); item.running_state().clone() }); cx.run_until_parked(); running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { ( list.flatten_entries(true, true), list.opened_stack_frame_id(), ) }); assert_eq!(Some(1), stack_frame_id); assert_eq!(stack_frames, stack_frame_list); running_state .variable_list() .update(cx, |variable_list, _| { assert_eq!(2, variable_list.scopes().len()); assert_eq!(scopes, variable_list.scopes()); let variables_by_scope = variable_list.variables_per_scope(); // scope 1 assert_eq!( vec![ variables.get(&2).unwrap()[0].clone(), variables.get(&2).unwrap()[1].clone(), ], variables_by_scope[0].1 ); // scope 2 let empty_vec: Vec<dap::Variable> = vec![]; assert_eq!(empty_vec, variables_by_scope[1].1); variable_list.assert_visual_entries(vec![ "v Scope 1", " > variable1", " > variable2", "> Scope 2", ]); }); });} // tests that toggling a variable will fetch its children and shows it#[gpui::test]async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(executor.clone()); let test_file_content = r#" const variable1 = { nested1: "Nested 1", nested2: "Nested 2", }; const variable2 = "Value 2"; const variable3 = "Value 3"; "# .unindent(); fs.insert_tree( path!("/project"), json!({ "src": {Lines 1–500 of 2342. Load more
use std::sync::{ Arc, atomic::{AtomicBool, Ordering}, }; use crate::{ DebugPanel, persistence::DebuggerPaneItem, session::running::variable_list::{ AddWatch, CollapseSelectedEntry, ExpandSelectedEntry, RemoveWatch, }, tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session}, }; use collections::HashMap; use dap::{ Scope, StackFrame, Variable, requests::{Evaluate, Initialize, Launch, Scopes, StackTrace, Variables}, }; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use menu::{SelectFirst, SelectNext, SelectPrevious}; use project::{FakeFs, Project}; use serde_json::json; use ui::SharedString; use unindent::Unindent as _; use util::path; /// This only tests fetching one scope and 2 variables for a single stackframe #[gpui::test] async fn test_basic_fetch_initial_scope_and_variables( executor: BackgroundExecutor, cx: &mut TestAppContext, ) { init_test(cx); let fs = FakeFs::new(executor.clone()); let test_file_content = r#" const variable1 = "Value 1"; const variable2 = "Value 2"; "# .unindent(); fs.insert_tree( path!("/project"), json!({ "src": { "test.js": test_file_content, } }), ) .await; let project = Project::test(fs, [path!("/project").as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; workspace .update(cx, |workspace, window, cx| { workspace.focus_panel::<DebugPanel>(window, cx); }) .unwrap(); let cx = &mut VisualTestContext::from_window(*workspace, cx); let session = start_debug_session(&workspace, cx, |_| {}).unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); client.on_request::<dap::requests::Threads, _>(move |_, _| { Ok(dap::ThreadsResponse { threads: vec![dap::Thread { id: 1, name: "Thread 1".into(), }], }) }); let stack_frames = vec![StackFrame { id: 1, name: "Stack Frame 1".into(), source: Some(dap::Source { name: Some("test.js".into()), path: Some(path!("/project/src/test.js").into()), source_reference: None, presentation_hint: None, origin: None, sources: None, adapter_data: None, checksums: None, }), line: 1, column: 1, end_line: None, end_column: None, can_restart: None, instruction_pointer_reference: None, module_id: None, presentation_hint: None, }]; client.on_request::<StackTrace, _>({ let stack_frames = Arc::new(stack_frames.clone()); move |_, args| { assert_eq!(1, args.thread_id); Ok(dap::StackTraceResponse { stack_frames: (*stack_frames).clone(), total_frames: None, }) } }); let scopes = vec![Scope { name: "Scope 1".into(), presentation_hint: None, variables_reference: 2, named_variables: None, indexed_variables: None, expensive: false, source: None, line: None, column: None, end_line: None, end_column: None, }]; client.on_request::<Scopes, _>({ let scopes = Arc::new(scopes.clone()); move |_, args| { assert_eq!(1, args.frame_id); Ok(dap::ScopesResponse { scopes: (*scopes).clone(), }) } }); let variables = vec![ Variable { name: "variable1".into(), value: "value 1".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }, Variable { name: "variable2".into(), value: "value 2".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }, ]; client.on_request::<Variables, _>({ let variables = Arc::new(variables.clone()); move |_, args| { assert_eq!(2, args.variables_reference); Ok(dap::VariablesResponse { variables: (*variables).clone(), }) } }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { reason: dap::StoppedEventReason::Pause, description: None, thread_id: Some(1), preserve_focus_hint: None, text: None, all_threads_stopped: None, hit_breakpoint_ids: None, })) .await; cx.run_until_parked(); let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| { cx.focus_self(window); item.running_state().clone() }); cx.run_until_parked(); running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { ( list.flatten_entries(true, true), list.opened_stack_frame_id(), ) }); assert_eq!(stack_frames, stack_frame_list); assert_eq!(Some(1), stack_frame_id); running_state .variable_list() .update(cx, |variable_list, _| { assert_eq!(scopes, variable_list.scopes()); assert_eq!( vec![variables[0].clone(), variables[1].clone(),], variable_list.variables() ); variable_list.assert_visual_entries(vec![ "v Scope 1", " > variable1", " > variable2", ]); }); }); } /// This tests fetching multiple scopes and variables for them with a single stackframe #[gpui::test] async fn test_fetch_variables_for_multiple_scopes( executor: BackgroundExecutor, cx: &mut TestAppContext, ) { init_test(cx); let fs = FakeFs::new(executor.clone()); let test_file_content = r#" const variable1 = { nested1: "Nested 1", nested2: "Nested 2", }; const variable2 = "Value 2"; const variable3 = "Value 3"; "# .unindent(); fs.insert_tree( path!("/project"), json!({ "src": { "test.js": test_file_content, } }), ) .await; let project = Project::test(fs, [path!("/project").as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; workspace .update(cx, |workspace, window, cx| { workspace.focus_panel::<DebugPanel>(window, cx); }) .unwrap(); let cx = &mut VisualTestContext::from_window(*workspace, cx); let session = start_debug_session(&workspace, cx, |_| {}).unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); client.on_request::<dap::requests::Threads, _>(move |_, _| { Ok(dap::ThreadsResponse { threads: vec![dap::Thread { id: 1, name: "Thread 1".into(), }], }) }); client.on_request::<Initialize, _>(move |_, _| { Ok(dap::Capabilities { supports_step_back: Some(false), ..Default::default() }) }); client.on_request::<Launch, _>(move |_, _| Ok(())); let stack_frames = vec![StackFrame { id: 1, name: "Stack Frame 1".into(), source: Some(dap::Source { name: Some("test.js".into()), path: Some(path!("/project/src/test.js").into()), source_reference: None, presentation_hint: None, origin: None, sources: None, adapter_data: None, checksums: None, }), line: 1, column: 1, end_line: None, end_column: None, can_restart: None, instruction_pointer_reference: None, module_id: None, presentation_hint: None, }]; client.on_request::<StackTrace, _>({ let stack_frames = Arc::new(stack_frames.clone()); move |_, args| { assert_eq!(1, args.thread_id); Ok(dap::StackTraceResponse { stack_frames: (*stack_frames).clone(), total_frames: None, }) } }); let scopes = vec![ Scope { name: "Scope 1".into(), presentation_hint: Some(dap::ScopePresentationHint::Locals), variables_reference: 2, named_variables: None, indexed_variables: None, expensive: false, source: None, line: None, column: None, end_line: None, end_column: None, }, Scope { name: "Scope 2".into(), presentation_hint: None, variables_reference: 3, named_variables: None, indexed_variables: None, expensive: false, source: None, line: None, column: None, end_line: None, end_column: None, }, ]; client.on_request::<Scopes, _>({ let scopes = Arc::new(scopes.clone()); move |_, args| { assert_eq!(1, args.frame_id); Ok(dap::ScopesResponse { scopes: (*scopes).clone(), }) } }); let mut variables = HashMap::default(); variables.insert( 2, vec![ Variable { name: "variable1".into(), value: "{nested1: \"Nested 1\", nested2: \"Nested 2\"}".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }, Variable { name: "variable2".into(), value: "Value 2".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }, ], ); variables.insert( 3, vec![Variable { name: "variable3".into(), value: "Value 3".into(), type_: None, presentation_hint: None, evaluate_name: None, variables_reference: 0, named_variables: None, indexed_variables: None, memory_reference: None, declaration_location_reference: None, value_location_reference: None, }], ); client.on_request::<Variables, _>({ let variables = Arc::new(variables.clone()); move |_, args| { Ok(dap::VariablesResponse { variables: variables.get(&args.variables_reference).unwrap().clone(), }) } }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { reason: dap::StoppedEventReason::Pause, description: None, thread_id: Some(1), preserve_focus_hint: None, text: None, all_threads_stopped: None, hit_breakpoint_ids: None, })) .await; cx.run_until_parked(); let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| { cx.focus_self(window); item.running_state().clone() }); cx.run_until_parked(); running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { ( list.flatten_entries(true, true), list.opened_stack_frame_id(), ) }); assert_eq!(Some(1), stack_frame_id); assert_eq!(stack_frames, stack_frame_list); running_state .variable_list() .update(cx, |variable_list, _| { assert_eq!(2, variable_list.scopes().len()); assert_eq!(scopes, variable_list.scopes()); let variables_by_scope = variable_list.variables_per_scope(); // scope 1 assert_eq!( vec![ variables.get(&2).unwrap()[0].clone(), variables.get(&2).unwrap()[1].clone(), ], variables_by_scope[0].1 ); // scope 2 let empty_vec: Vec<dap::Variable> = vec![]; assert_eq!(empty_vec, variables_by_scope[1].1); variable_list.assert_visual_entries(vec![ "v Scope 1", " > variable1", " > variable2", "> Scope 2", ]); }); }); } // tests that toggling a variable will fetch its children and shows it #[gpui::test] async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(executor.clone()); let test_file_content = r#" const variable1 = { nested1: "Nested 1", nested2: "Nested 2", }; const variable2 = "Value 2"; const variable3 = "Value 3"; "# .unindent(); fs.insert_tree( path!("/project"), json!({ "src": {