diff --git a/lldb/test/API/tools/lldb-vscode/console/Makefile b/lldb/test/API/tools/lldb-vscode/console/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/console/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-vscode/console/TestVSCode_console.py b/lldb/test/API/tools/lldb-vscode/console/TestVSCode_console.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/console/TestVSCode_console.py @@ -0,0 +1,70 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase + + +class TestVSCode_console(lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + def check_lldb_command(self, lldb_command, contains_string, assert_msg): + response = self.vscode.request_evaluate('`%s' % (lldb_command)) + output = response['body']['result'] + self.assertTrue(contains_string in output, + ("""Verify %s by checking the command output:\n""" + """'''\n%s'''\nfor the string: "%s" """ % ( + assert_msg, output, contains_string))) + + @skipIfWindows + @skipIfRemote + def test_scopes_variables_setVariable_evaluate(self): + ''' + Tests that the "scopes" request causes the currently selected + thread and frame to be updated. There are no DAP packets that tell + lldb-vscode which thread and frame are selected other than the + "scopes" request. lldb-vscode will now select the thread and frame + for the latest "scopes" request that it receives. + + The LLDB command interpreter needs to have the right thread and + frame selected so that commands executed in the debug console act + on the right scope. This applies both to the expressions that are + evaluated and the lldb commands that start with the backtick + character. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = 'main.cpp' + breakpoint1_line = line_number(source, '// breakpoint 1') + lines = [breakpoint1_line] + # Set breakpoint in the thread function so we can step the threads + breakpoint_ids = self.set_source_breakpoints(source, lines) + self.assertTrue(len(breakpoint_ids) == len(lines), + "expect correct number of breakpoints") + self.continue_to_breakpoints(breakpoint_ids) + # Cause a "scopes" to be sent for frame zero which should update the + # selected thread and frame to frame 0. + self.vscode.get_local_variables(frameIndex=0) + # Verify frame #0 is selected in the command interpreter by running + # the "frame select" command with no frame index which will print the + # currently selected frame. + self.check_lldb_command("frame select", "frame #0", + "frame 0 is selected") + + # Cause a "scopes" to be sent for frame one which should update the + # selected thread and frame to frame 1. + self.vscode.get_local_variables(frameIndex=1) + # Verify frame #1 is selected in the command interpreter by running + # the "frame select" command with no frame index which will print the + # currently selected frame. + + self.check_lldb_command("frame select", "frame #1", + "frame 1 is selected") diff --git a/lldb/test/API/tools/lldb-vscode/console/main.cpp b/lldb/test/API/tools/lldb-vscode/console/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/console/main.cpp @@ -0,0 +1,9 @@ + +int multiply(int x, int y) { + return x * y; // breakpoint 1 +} + +int main(int argc, char const *argv[]) { + int result = multiply(argc, 20); + return result < 0; +} diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp --- a/lldb/tools/lldb-vscode/lldb-vscode.cpp +++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -959,7 +959,7 @@ for (size_t i = 0; i < count; i++) { std::string match = matches.GetStringAtIndex(i); std::string description = descriptions.GetStringAtIndex(i); - + llvm::json::Object item; llvm::StringRef match_ref = match; @@ -1262,7 +1262,7 @@ // The debug adapter supports the stepInTargetsRequest. body.try_emplace("supportsStepInTargetsRequest", false); // We need to improve the current implementation of completions in order to - // enable it again. For some context, this is how VSCode works: + // enable it again. For some context, this is how VSCode works: // - VSCode sends a completion request whenever chars are added, the user // triggers completion manually via CTRL-space or similar mechanisms, but // not when there's a deletion. Besides, VSCode doesn't let us know which @@ -1595,6 +1595,24 @@ llvm::json::Object body; auto arguments = request.getObject("arguments"); lldb::SBFrame frame = g_vsc.GetLLDBFrame(*arguments); + // As the user selects different stack frames in the GUI, a "scopes" request + // will be sent to the DAP. This is the only way we know that the user has + // selected a frame in a thread. There are no other notifications that are + // sent and VS code doesn't allow multiple frames to show variables + // concurrently. If we select the thread and frame as the "scopes" requests + // are sent, this allows users to type commands in the debugger console + // with a backtick character to run lldb commands and these lldb commands + // will now have the right context selected as they are run. If the user + // types "`bt" into the debugger console and we had another thread selected + // in the LLDB library, we would show the wrong thing to the user. If the + // users switches threads with a lldb command like "`thread select 14", the + // GUI will not update as there are no "event" notification packets that + // allow us to change the currently selected thread or frame in the GUI that + // I am aware of. + if (frame.IsValid()) { + frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread()); + frame.GetThread().SetSelectedFrame(frame.GetFrameID()); + } g_vsc.variables.Clear(); g_vsc.variables.Append(frame.GetVariables(true, // arguments true, // locals