diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2019-01-19 10:06:29 +0000 | 
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2019-01-19 10:06:29 +0000 | 
| commit | 94994d372d014ce4c8758b9605d63fae651bd8aa (patch) | |
| tree | 51c0b708bd59f205d6b35cb2a8c24d62f0c33d77 /packages/Python/lldbsuite/test/tools | |
| parent | 39be7ce23363d12ae3e49aeb1fdb2bfeb892e836 (diff) | |
Notes
Diffstat (limited to 'packages/Python/lldbsuite/test/tools')
30 files changed, 2982 insertions, 13 deletions
diff --git a/packages/Python/lldbsuite/test/tools/lldb-mi/interpreter/TestMiInterpreterExec.py b/packages/Python/lldbsuite/test/tools/lldb-mi/interpreter/TestMiInterpreterExec.py index ccd7eba8bfe31..848bd3852393f 100644 --- a/packages/Python/lldbsuite/test/tools/lldb-mi/interpreter/TestMiInterpreterExec.py +++ b/packages/Python/lldbsuite/test/tools/lldb-mi/interpreter/TestMiInterpreterExec.py @@ -18,6 +18,7 @@ class MiInterpreterExecTestCase(lldbmi_testcase.MiTestCaseBase):      @skipIfWindows  # llvm.org/pr24452: Get lldb-mi tests working on Windows      @skipIfFreeBSD  # llvm.org/pr22411: Failure presumably due to known thread races      @skipIfRemote   # We do not currently support remote debugging via the MI. +    @skipIfDarwin      def test_lldbmi_target_create(self):          """Test that 'lldb-mi --interpreter' can create target by 'target create' command.""" @@ -39,6 +40,7 @@ class MiInterpreterExecTestCase(lldbmi_testcase.MiTestCaseBase):      @skipIfRemote   # We do not currently support remote debugging via the MI.      @skipIfWindows  # llvm.org/pr24452: Get lldb-mi tests working on Windows.      @skipIfFreeBSD  # llvm.org/pr22411: Failure presumably due to known thread races. +    @skipIfDarwin      def test_lldbmi_target_list(self):          """Test that 'lldb-mi --interpreter' can list targets by 'target list' command.""" @@ -58,6 +60,7 @@ class MiInterpreterExecTestCase(lldbmi_testcase.MiTestCaseBase):      @skipIfWindows  # llvm.org/pr24452: Get lldb-mi tests working on Windows      @skipIfFreeBSD  # llvm.org/pr22411: Failure presumably due to known thread races      @skipIfRemote   # We do not currently support remote debugging via the MI. +    @skipIfDarwin      def test_lldbmi_breakpoint_set(self):          """Test that 'lldb-mi --interpreter' can set breakpoint by 'breakpoint set' command.""" @@ -82,6 +85,7 @@ class MiInterpreterExecTestCase(lldbmi_testcase.MiTestCaseBase):      @skipIfFreeBSD  # llvm.org/pr22411: Failure presumably due to known thread races      @expectedFlakeyLinux(bugnumber="llvm.org/pr25470")      @skipIfRemote   # We do not currently support remote debugging via the MI. +    @skipIfDarwin      def test_lldbmi_settings_set_target_run_args_before(self):          """Test that 'lldb-mi --interpreter' can set target arguments by 'setting set target.run-args' command before than target was created.""" @@ -115,6 +119,7 @@ class MiInterpreterExecTestCase(lldbmi_testcase.MiTestCaseBase):      @skipIfWindows  # llvm.org/pr24452: Get lldb-mi tests working on Windows      @skipIfFreeBSD  # llvm.org/pr22411: Failure presumably due to known thread races      @skipIfRemote   # We do not currently support remote debugging via the MI. +    @skipIfDarwin      def test_lldbmi_process_launch(self):          """Test that 'lldb-mi --interpreter' can launch process by "process launch" command.""" @@ -138,6 +143,7 @@ class MiInterpreterExecTestCase(lldbmi_testcase.MiTestCaseBase):      @skipIfWindows  # llvm.org/pr24452: Get lldb-mi tests working on Windows      @skipIfFreeBSD  # llvm.org/pr22411: Failure presumably due to known thread races      @skipIfRemote   # We do not currently support remote debugging via the MI. +    @skipIfDarwin      def test_lldbmi_thread_step_in(self):          """Test that 'lldb-mi --interpreter' can step in by "thread step-in" command.""" @@ -170,6 +176,7 @@ class MiInterpreterExecTestCase(lldbmi_testcase.MiTestCaseBase):      @skipIfWindows  # llvm.org/pr24452: Get lldb-mi tests working on Windows      @skipIfFreeBSD  # llvm.org/pr22411: Failure presumably due to known thread races      @skipIfRemote   # We do not currently support remote debugging via the MI. +    @skipIfDarwin      def test_lldbmi_thread_step_over(self):          """Test that 'lldb-mi --interpreter' can step over by "thread step-over" command.""" @@ -196,6 +203,7 @@ class MiInterpreterExecTestCase(lldbmi_testcase.MiTestCaseBase):      @skipIfFreeBSD  # llvm.org/pr22411: Failure presumably due to known thread races      @expectedFlakeyLinux("llvm.org/pr25470")      @skipIfRemote   # We do not currently support remote debugging via the MI. +    @skipIfDarwin      def test_lldbmi_thread_continue(self):          """Test that 'lldb-mi --interpreter' can continue execution by "thread continue" command.""" diff --git a/packages/Python/lldbsuite/test/tools/lldb-mi/lldbmi_testcase.py b/packages/Python/lldbsuite/test/tools/lldb-mi/lldbmi_testcase.py index 7b6ee55b250e1..59a5b324465fa 100644 --- a/packages/Python/lldbsuite/test/tools/lldb-mi/lldbmi_testcase.py +++ b/packages/Python/lldbsuite/test/tools/lldb-mi/lldbmi_testcase.py @@ -40,7 +40,7 @@ class MiTestCaseBase(Base):                  pass          Base.tearDown(self) -    def spawnLldbMi(self, args=None): +    def spawnLldbMi(self, exe=None, args=None, preconfig=True):          import pexpect          self.child = pexpect.spawn("%s --interpreter %s" % (              self.lldbMiExec, args if args else ""), cwd=self.getBuildDir()) @@ -49,6 +49,14 @@ class MiTestCaseBase(Base):          self.child.logfile_read = open(self.mylog, "w")          # wait until lldb-mi has started up and is ready to go          self.expect(self.child_prompt, exactly=True) +        if preconfig: +            self.runCmd("settings set symbols.enable-external-lookup false") +            self.expect("\^done") +            self.expect(self.child_prompt, exactly=True) +        if exe: +            self.runCmd("-file-exec-and-symbols \"%s\"" % exe) +            # Testcases expect to be able to match output of this command, +            # see test_lldbmi_specialchars.      def runCmd(self, cmd):          self.child.sendline(cmd) diff --git a/packages/Python/lldbsuite/test/tools/lldb-mi/startup_options/TestMiStartupOptions.py b/packages/Python/lldbsuite/test/tools/lldb-mi/startup_options/TestMiStartupOptions.py index 467952db7eba0..b78f4762ddf96 100644 --- a/packages/Python/lldbsuite/test/tools/lldb-mi/startup_options/TestMiStartupOptions.py +++ b/packages/Python/lldbsuite/test/tools/lldb-mi/startup_options/TestMiStartupOptions.py @@ -22,7 +22,7 @@ class MiStartupOptionsTestCase(lldbmi_testcase.MiTestCaseBase):      def test_lldbmi_executable_option_file(self):          """Test that 'lldb-mi --interpreter %s' loads executable file.""" -        self.spawnLldbMi(args="%s" % self.myexe) +        self.spawnLldbMi(exe=self.myexe)          # Test that the executable is loaded when file was specified          self.expect("-file-exec-and-symbols \"%s\"" % self.myexe) @@ -52,7 +52,7 @@ class MiStartupOptionsTestCase(lldbmi_testcase.MiTestCaseBase):          # Prepare path to executable          path = "unknown_file" -        self.spawnLldbMi(args="%s" % path) +        self.spawnLldbMi(exe=path)          # Test that the executable isn't loaded when unknown file was specified          self.expect("-file-exec-and-symbols \"%s\"" % path) @@ -71,7 +71,7 @@ class MiStartupOptionsTestCase(lldbmi_testcase.MiTestCaseBase):          """Test that 'lldb-mi --interpreter %s' loads executable which is specified via absolute path."""          # Prepare path to executable -        self.spawnLldbMi(args="%s" % self.myexe) +        self.spawnLldbMi(exe=self.myexe)          # Test that the executable is loaded when file was specified using          # absolute path @@ -95,7 +95,7 @@ class MiStartupOptionsTestCase(lldbmi_testcase.MiTestCaseBase):          # Prepare path to executable          path = os.path.relpath(self.myexe, self.getBuildDir()) -        self.spawnLldbMi(args="%s" % path) +        self.spawnLldbMi(exe=path)          # Test that the executable is loaded when file was specified using          # relative path @@ -119,7 +119,7 @@ class MiStartupOptionsTestCase(lldbmi_testcase.MiTestCaseBase):          # Prepare path to executable          path = "unknown_dir" + self.myexe -        self.spawnLldbMi(args="%s" % path) +        self.spawnLldbMi(exe=path)          # Test that the executable isn't loaded when file was specified using          # unknown path @@ -237,7 +237,11 @@ class MiStartupOptionsTestCase(lldbmi_testcase.MiTestCaseBase):          # Prepared source file          sourceFile = self.copyScript("start_script_error") -        self.spawnLldbMi(args="--source %s" % sourceFile) +        self.spawnLldbMi(args="--source %s" % sourceFile, preconfig=False) + +        # After 'settings set symbols.enable-external-lookup false' +        self.expect("settings set symbols.enable-external-lookup false") +        self.expect("\^done")          # After '-file-exec-and-symbols a.out'          self.expect("-file-exec-and-symbols %s" % self.myexe) @@ -259,7 +263,7 @@ class MiStartupOptionsTestCase(lldbmi_testcase.MiTestCaseBase):          """Test that 'lldb-mi --log' creates a log file in the current directory."""          logDirectory = self.getBuildDir() -        self.spawnLldbMi(args="%s --log" % self.myexe) +        self.spawnLldbMi(exe=self.myexe, args="--log")          # Test that the executable is loaded when file was specified          self.expect("-file-exec-and-symbols \"%s\"" % self.myexe) @@ -296,9 +300,8 @@ class MiStartupOptionsTestCase(lldbmi_testcase.MiTestCaseBase):          import tempfile          logDirectory = tempfile.gettempdir() -        self.spawnLldbMi( -            args="%s --log --log-dir=%s" % -            (self.myexe, logDirectory)) +        self.spawnLldbMi(exe=self.myexe, +            args="--log --log-dir=%s" % logDirectory)          # Test that the executable is loaded when file was specified          self.expect("-file-exec-and-symbols \"%s\"" % self.myexe) diff --git a/packages/Python/lldbsuite/test/tools/lldb-mi/startup_options/start_script_error b/packages/Python/lldbsuite/test/tools/lldb-mi/startup_options/start_script_error index d834e7407c570..a1c581b08e0e2 100644 --- a/packages/Python/lldbsuite/test/tools/lldb-mi/startup_options/start_script_error +++ b/packages/Python/lldbsuite/test/tools/lldb-mi/startup_options/start_script_error @@ -1,2 +1,3 @@ +settings set symbols.enable-external-lookup false  -file-exec-and-symbols a.out  -break-ins -f main diff --git a/packages/Python/lldbsuite/test/tools/lldb-mi/syntax/TestMiSyntax.py b/packages/Python/lldbsuite/test/tools/lldb-mi/syntax/TestMiSyntax.py index 50a94b53a18b3..74d194e6dee28 100644 --- a/packages/Python/lldbsuite/test/tools/lldb-mi/syntax/TestMiSyntax.py +++ b/packages/Python/lldbsuite/test/tools/lldb-mi/syntax/TestMiSyntax.py @@ -53,7 +53,7 @@ class MiSyntaxTestCase(lldbmi_testcase.MiTestCaseBase):          os.symlink(self.myexe, complicated_myexe)          self.addTearDownHook(lambda: os.unlink(complicated_myexe)) -        self.spawnLldbMi(args="\"%s\"" % complicated_myexe) +        self.spawnLldbMi(exe=complicated_myexe)          # Test that the executable was loaded          self.expect( diff --git a/packages/Python/lldbsuite/test/tools/lldb-server/TestAppleSimulatorOSType.py b/packages/Python/lldbsuite/test/tools/lldb-server/TestAppleSimulatorOSType.py index 8c8fed8e44e01..62c948c36cf7d 100644 --- a/packages/Python/lldbsuite/test/tools/lldb-server/TestAppleSimulatorOSType.py +++ b/packages/Python/lldbsuite/test/tools/lldb-server/TestAppleSimulatorOSType.py @@ -19,7 +19,13 @@ class TestAppleSimulatorOSType(gdbremote_testcase.GdbRemoteTestCaseBase):          sim_devices = json.loads(sim_devices_str)['devices']          # Find an available simulator for the requested platform          deviceUDID = None -        for (runtime,devices) in sim_devices.items(): +        for simulator in sim_devices: +            if isinstance(simulator,dict): +                runtime = simulator['name'] +                devices = simulator['devices'] +            else: +                runtime = simulator +                devices = sim_devices[simulator]              if not platform in runtime.lower():                  continue              for device in devices: @@ -95,18 +101,21 @@ class TestAppleSimulatorOSType(gdbremote_testcase.GdbRemoteTestCaseBase):      @apple_simulator_test('iphone')      @debugserver_test +    @skipIfDarwinEmbedded      def test_simulator_ostype_ios(self):          self.check_simulator_ostype(sdk='iphonesimulator',                                      platform='ios')      @apple_simulator_test('appletv')      @debugserver_test +    @skipIfDarwinEmbedded      def test_simulator_ostype_tvos(self):          self.check_simulator_ostype(sdk='appletvsimulator',                                      platform='tvos')      @apple_simulator_test('watch')      @debugserver_test +    @skipIfDarwinEmbedded      def test_simulator_ostype_watchos(self):          self.check_simulator_ostype(sdk='watchsimulator',                                      platform='watchos', arch='i386') diff --git a/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteGPacket.py b/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteGPacket.py index 76910758fa912..cfadbc8f7d0f0 100644 --- a/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteGPacket.py +++ b/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteGPacket.py @@ -35,6 +35,7 @@ class TestGdbRemoteGPacket(gdbremote_testcase.GdbRemoteTestCaseBase):      @skipIfOutOfTreeDebugserver      @debugserver_test +    @skipIfDarwinEmbedded      def test_g_packet_debugserver(self):          self.init_debugserver_test()          self.run_test_g_packet() diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/.categories b/packages/Python/lldbsuite/test/tools/lldb-vscode/.categories new file mode 100644 index 0000000000000..ce2cfd048e380 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/.categories @@ -0,0 +1 @@ +lldb-vscode diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/Makefile b/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/Makefile new file mode 100644 index 0000000000000..b09a579159d48 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/TestVSCode_attach.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/TestVSCode_attach.py new file mode 100644 index 0000000000000..67cd05b83c3b0 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/TestVSCode_attach.py @@ -0,0 +1,192 @@ +""" +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 +import os +import shutil +import subprocess +import tempfile +import threading +import time + + +def spawn_and_wait(program, delay): +    if delay: +        time.sleep(delay) +    process = subprocess.Popen([program], +                               stdin=subprocess.PIPE, +                               stdout=subprocess.PIPE, +                               stderr=subprocess.PIPE) +    process.wait() + + +class TestVSCode_attach(lldbvscode_testcase.VSCodeTestCaseBase): + +    mydir = TestBase.compute_mydir(__file__) + +    def set_and_hit_breakpoint(self, continueToExit=True): +        source = 'main.c' +        breakpoint1_line = line_number(source, '// breakpoint 1') +        lines = [breakpoint1_line] +        # Set breakoint in the thread function so we can step the threads +        breakpoint_ids = self.set_source_breakpoints(source, lines) +        self.assertEqual(len(breakpoint_ids), len(lines), +                         "expect correct number of breakpoints") +        self.continue_to_breakpoints(breakpoint_ids) +        if continueToExit: +            self.continue_to_exit() + + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_by_pid(self): +        ''' +            Tests attaching to a process by process ID. +        ''' +        self.build_and_create_debug_adaptor() +        program = self.getBuildArtifact("a.out") +        self.process = subprocess.Popen([program], +                                        stdin=subprocess.PIPE, +                                        stdout=subprocess.PIPE, +                                        stderr=subprocess.PIPE) +        self.attach(pid=self.process.pid) +        self.set_and_hit_breakpoint(continueToExit=True) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_by_name(self): +        ''' +            Tests attaching to a process by process name. +        ''' +        self.build_and_create_debug_adaptor() +        orig_program = self.getBuildArtifact("a.out") +        # Since we are going to attach by process name, we need a unique +        # process name that has minimal chance to match a process that is +        # already running. To do this we use tempfile.mktemp() to give us a +        # full path to a location where we can copy our executable. We then +        # run this copy to ensure we don't get the error "more that one +        # process matches 'a.out'". +        program = tempfile.mktemp() +        shutil.copyfile(orig_program, program) +        shutil.copymode(orig_program, program) + +        def cleanup(): +            if os.path.exists(program): +                os.unlink(program) +        # Execute the cleanup function during test case tear down. +        self.addTearDownHook(cleanup) + +        self.process = subprocess.Popen([program], +                                        stdin=subprocess.PIPE, +                                        stdout=subprocess.PIPE, +                                        stderr=subprocess.PIPE) +        # Wait for a bit to ensure the process is launched +        time.sleep(5) +        self.attach(program=program) +        self.set_and_hit_breakpoint(continueToExit=True) + +    @skipUnlessDarwin +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_by_name_waitFor(self): +        ''' +            Tests attaching to a process by process name and waiting for the +            next instance of a process to be launched, ingoring all current +            ones. +        ''' +        self.build_and_create_debug_adaptor() +        program = self.getBuildArtifact("a.out") +        self.spawn_thread = threading.Thread(target=spawn_and_wait, +                                             args=(program, 1.0,)) +        self.spawn_thread.start() +        self.attach(program=program, waitFor=True) +        self.set_and_hit_breakpoint(continueToExit=True) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_commands(self): +        ''' +            Tests the "initCommands", "preRunCommands", "stopCommands", +            "exitCommands", and "attachCommands" that can be passed during +            attach. + +            "initCommands" are a list of LLDB commands that get executed +            before the targt is created. +            "preRunCommands" are a list of LLDB commands that get executed +            after the target has been created and before the launch. +            "stopCommands" are a list of LLDB commands that get executed each +            time the program stops. +            "exitCommands" are a list of LLDB commands that get executed when +            the process exits +            "attachCommands" are a list of LLDB commands that get executed and +            must have a valid process in the selected target in LLDB after +            they are done executing. This allows custom commands to create any +            kind of debug session. +        ''' +        self.build_and_create_debug_adaptor() +        program = self.getBuildArtifact("a.out") +        # Here we just create a target and launch the process as a way to test +        # if we are able to use attach commands to create any kind of a target +        # and use it for debugging +        attachCommands = [ +            'target create -d "%s"' % (program), +            'process launch -- arg1' +        ] +        initCommands = ['target list', 'platform list'] +        preRunCommands = ['image list a.out', 'image dump sections a.out'] +        stopCommands = ['frame variable', 'bt'] +        exitCommands = ['expr 2+3', 'expr 3+4'] +        self.attach(program=program, +                    attachCommands=attachCommands, +                    initCommands=initCommands, +                    preRunCommands=preRunCommands, +                    stopCommands=stopCommands, +                    exitCommands=exitCommands) + +        # Get output from the console. This should contain both the +        # "initCommands" and the "preRunCommands". +        output = self.get_console() +        # Verify all "initCommands" were found in console output +        self.verify_commands('initCommands', output, initCommands) +        # Verify all "preRunCommands" were found in console output +        self.verify_commands('preRunCommands', output, preRunCommands) + +        functions = ['main'] +        breakpoint_ids = self.set_function_breakpoints(functions) +        self.assertTrue(len(breakpoint_ids) == len(functions), +                        "expect one breakpoint") +        self.continue_to_breakpoints(breakpoint_ids) +        output = self.get_console(timeout=1.0) +        self.verify_commands('stopCommands', output, stopCommands) + +        # Continue after launch and hit the "pause()" call and stop the target. +        # Get output from the console. This should contain both the +        # "stopCommands" that were run after we stop. +        self.vscode.request_continue() +        time.sleep(0.5) +        self.vscode.request_pause() +        self.vscode.wait_for_stopped() +        output = self.get_console(timeout=1.0) +        self.verify_commands('stopCommands', output, stopCommands) + +        # Continue until the program exits +        self.continue_to_exit() +        # Get output from the console. This should contain both the +        # "exitCommands" that were run after the second breakpoint was hit +        output = self.get_console(timeout=1.0) +        self.verify_commands('exitCommands', output, exitCommands) diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/main.c b/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/main.c new file mode 100644 index 0000000000000..a078d42203eb8 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/main.c @@ -0,0 +1,8 @@ +#include <stdio.h> +#include <unistd.h> + +int main(int argc, char const *argv[]) { +  printf("pid = %i\n", getpid()); +  sleep(5); +  return 0; // breakpoint 1 +} diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/Makefile b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/Makefile new file mode 100644 index 0000000000000..314f1cb2f077b --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py new file mode 100644 index 0000000000000..1ebf2b8981ab6 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py @@ -0,0 +1,211 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import pprint +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os + + +class TestVSCode_setBreakpoints(lldbvscode_testcase.VSCodeTestCaseBase): + +    mydir = TestBase.compute_mydir(__file__) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @no_debug_info_test +    def test_set_and_clear(self): +        '''Tests setting and clearing source file and line breakpoints. +           This packet is a bit tricky on the debug adaptor side since there +           is no "clearBreakpoints" packet. Source file and line breakpoints +           are set by sending a "setBreakpoints" packet with a source file +           specified and zero or more source lines. If breakpoints have been +           set in the source file before, any exising breakpoints must remain +           set, and any new breakpoints must be created, and any breakpoints +           that were in previous requests and are not in the current request +           must be removed. This function tests this setting and clearing +           and makes sure things happen correctly. It doesn't test hitting +           breakpoints and the functionality of each breakpoint, like +           'conditions' and 'hitCondition' settings.''' +        source_basename = 'main.cpp' +        source_path = os.path.join(os.getcwd(), source_basename) +        first_line = line_number('main.cpp', 'break 12') +        second_line = line_number('main.cpp', 'break 13') +        third_line = line_number('main.cpp', 'break 14') +        lines = [first_line, second_line, third_line] + +        # Visual Studio Code Debug Adaptors have no way to specify the file +        # without launching or attaching to a process, so we must start a +        # process in order to be able to set breakpoints. +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program) + +        # Set 3 breakoints and verify that they got set correctly +        response = self.vscode.request_setBreakpoints(source_path, lines) +        line_to_id = {} +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(lines), +                            "expect %u source breakpoints" % (len(lines))) +            for breakpoint in breakpoints: +                line = breakpoint['line'] +                # Store the "id" of the breakpoint that was set for later +                line_to_id[line] = breakpoint['id'] +                self.assertTrue(line in lines, "line expected in lines array") +                self.assertTrue(breakpoint['verified'], +                                "expect breakpoint verified") + +        # There is no breakpoint delete packet, clients just send another +        # setBreakpoints packet with the same source file with fewer lines. +        # Below we remove the second line entry and call the setBreakpoints +        # function again. We want to verify that any breakpoints that were set +        # before still have the same "id". This means we didn't clear the +        # breakpoint and set it again at the same location. We also need to +        # verify that the second line location was actually removed. +        lines.remove(second_line) +        # Set 2 breakoints and verify that the previous breakoints that were +        # set above are still set. +        response = self.vscode.request_setBreakpoints(source_path, lines) +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(lines), +                            "expect %u source breakpoints" % (len(lines))) +            for breakpoint in breakpoints: +                line = breakpoint['line'] +                # Verify the same breakpoints are still set within LLDB by +                # making sure the breakpoint ID didn't change +                self.assertTrue(line_to_id[line] == breakpoint['id'], +                                "verify previous breakpoints stayed the same") +                self.assertTrue(line in lines, "line expected in lines array") +                self.assertTrue(breakpoint['verified'], +                                "expect breakpoint still verified") + +        # Now get the full list of breakpoints set in the target and verify +        # we have only 2 breakpoints set. The response above could have told +        # us about 2 breakpoints, but we want to make sure we don't have the +        # third one still set in the target +        response = self.vscode.request_testGetTargetBreakpoints() +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(lines), +                            "expect %u source breakpoints" % (len(lines))) +            for breakpoint in breakpoints: +                line = breakpoint['line'] +                # Verify the same breakpoints are still set within LLDB by +                # making sure the breakpoint ID didn't change +                self.assertTrue(line_to_id[line] == breakpoint['id'], +                                "verify previous breakpoints stayed the same") +                self.assertTrue(line in lines, "line expected in lines array") +                self.assertTrue(breakpoint['verified'], +                                "expect breakpoint still verified") + +        # Now clear all breakpoints for the source file by passing down an +        # empty lines array +        lines = [] +        response = self.vscode.request_setBreakpoints(source_path, lines) +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(lines), +                            "expect %u source breakpoints" % (len(lines))) + +        # Verify with the target that all breakpoints have been cleared +        response = self.vscode.request_testGetTargetBreakpoints() +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(lines), +                            "expect %u source breakpoints" % (len(lines))) + +        # Now set a breakpoint again in the same source file and verify it +        # was added. +        lines = [second_line] +        response = self.vscode.request_setBreakpoints(source_path, lines) +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(lines), +                            "expect %u source breakpoints" % (len(lines))) +            for breakpoint in breakpoints: +                line = breakpoint['line'] +                self.assertTrue(line in lines, "line expected in lines array") +                self.assertTrue(breakpoint['verified'], +                                "expect breakpoint still verified") + +        # Now get the full list of breakpoints set in the target and verify +        # we have only 2 breakpoints set. The response above could have told +        # us about 2 breakpoints, but we want to make sure we don't have the +        # third one still set in the target +        response = self.vscode.request_testGetTargetBreakpoints() +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(lines), +                            "expect %u source breakpoints" % (len(lines))) +            for breakpoint in breakpoints: +                line = breakpoint['line'] +                self.assertTrue(line in lines, "line expected in lines array") +                self.assertTrue(breakpoint['verified'], +                                "expect breakpoint still verified") + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @no_debug_info_test +    def test_functionality(self): +        '''Tests hitting breakpoints and the functionality of a single +           breakpoint, like 'conditions' and 'hitCondition' settings.''' +        source_basename = 'main.cpp' +        source_path = os.path.join(os.getcwd(), source_basename) +        loop_line = line_number('main.cpp', '// break loop') + +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program) +        # Set a breakpoint at the loop line with no condition and no +        # hitCondition +        breakpoint_ids = self.set_source_breakpoints(source_path, [loop_line]) +        self.assertTrue(len(breakpoint_ids) == 1, "expect one breakpoint") +        self.vscode.request_continue() + +        # Verify we hit the breakpoint we just set +        self.verify_breakpoint_hit(breakpoint_ids) + +        # Make sure i is zero at first breakpoint +        i = int(self.vscode.get_local_variable_value('i')) +        self.assertTrue(i == 0, 'i != 0 after hitting breakpoint') + +        # Update the condition on our breakpoint +        new_breakpoint_ids = self.set_source_breakpoints(source_path, +                                                         [loop_line], +                                                         condition="i==4") +        self.assertTrue(breakpoint_ids == new_breakpoint_ids, +                        "existing breakpoint should have its condition " +                        "updated") + +        self.continue_to_breakpoints(breakpoint_ids) +        i = int(self.vscode.get_local_variable_value('i')) +        self.assertTrue(i == 4, +                        'i != 4 showing conditional works') + +        new_breakpoint_ids = self.set_source_breakpoints(source_path, +                                                         [loop_line], +                                                         hitCondition="2") + +        self.assertTrue(breakpoint_ids == new_breakpoint_ids, +                        "existing breakpoint should have its condition " +                        "updated") + +        # Continue with a hitContidtion of 2 and expect it to skip 1 value +        self.continue_to_breakpoints(breakpoint_ids) +        i = int(self.vscode.get_local_variable_value('i')) +        self.assertTrue(i == 6, +                        'i != 6 showing hitCondition works') + +        # continue after hitting our hitCondition and make sure it only goes +        # up by 1 +        self.continue_to_breakpoints(breakpoint_ids) +        i = int(self.vscode.get_local_variable_value('i')) +        self.assertTrue(i == 7, +                        'i != 7 showing post hitCondition hits every time') diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setExceptionBreakpoints.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setExceptionBreakpoints.py new file mode 100644 index 0000000000000..fdba683760e0c --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setExceptionBreakpoints.py @@ -0,0 +1,51 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import pprint +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os + + +class TestVSCode_setExceptionBreakpoints( +        lldbvscode_testcase.VSCodeTestCaseBase): + +    mydir = TestBase.compute_mydir(__file__) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @no_debug_info_test +    def test_functionality(self): +        '''Tests setting and clearing exception breakpoints. +           This packet is a bit tricky on the debug adaptor side since there +           is no "clear exception breakpoints" packet. Exception breakpoints +           are set by sending a "setExceptionBreakpoints" packet with zero or +           more exception filters. If exception breakpoints have been set +           before, any exising breakpoints must remain set, and any new +           breakpoints must be created, and any breakpoints that were in +           previous requests and are not in the current request must be +           removed. This exception tests this setting and clearing and makes +           sure things happen correctly. It doesn't test hitting breakpoints +           and the functionality of each breakpoint, like 'conditions' and +           x'hitCondition' settings. +        ''' +        # Visual Studio Code Debug Adaptors have no way to specify the file +        # without launching or attaching to a process, so we must start a +        # process in order to be able to set breakpoints. +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program) + +        filters = ['cpp_throw', 'cpp_catch'] +        response = self.vscode.request_setExceptionBreakpoints(filters) +        if response: +            self.assertTrue(response['success']) + +        self.continue_to_exception_breakpoint('C++ Throw') +        self.continue_to_exception_breakpoint('C++ Catch') diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setFunctionBreakpoints.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setFunctionBreakpoints.py new file mode 100644 index 0000000000000..c7c67597c86b0 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setFunctionBreakpoints.py @@ -0,0 +1,167 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import pprint +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os + + +class TestVSCode_setFunctionBreakpoints( +        lldbvscode_testcase.VSCodeTestCaseBase): + +    mydir = TestBase.compute_mydir(__file__) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # test hangs on linux under heavy load +    @no_debug_info_test +    def test_set_and_clear(self): +        '''Tests setting and clearing function breakpoints. +           This packet is a bit tricky on the debug adaptor side since there +           is no "clearFunction Breakpoints" packet. Function breakpoints +           are set by sending a "setFunctionBreakpoints" packet with zero or +           more function names. If function breakpoints have been set before, +           any exising breakpoints must remain set, and any new breakpoints +           must be created, and any breakpoints that were in previous requests +           and are not in the current request must be removed. This function +           tests this setting and clearing and makes sure things happen +           correctly. It doesn't test hitting breakpoints and the functionality +           of each breakpoint, like 'conditions' and 'hitCondition' settings. +        ''' +        # Visual Studio Code Debug Adaptors have no way to specify the file +        # without launching or attaching to a process, so we must start a +        # process in order to be able to set breakpoints. +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program) +        bp_id_12 = None +        functions = ['twelve'] +        # Set a function breakpoint at 'twelve' +        response = self.vscode.request_setFunctionBreakpoints(functions) +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(functions), +                            "expect %u source breakpoints" % (len(functions))) +            for breakpoint in breakpoints: +                bp_id_12 = breakpoint['id'] +                self.assertTrue(breakpoint['verified'], +                                "expect breakpoint verified") + +        # Add an extra name and make sure we have two breakpoints after this +        functions.append('thirteen') +        response = self.vscode.request_setFunctionBreakpoints(functions) +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(functions), +                            "expect %u source breakpoints" % (len(functions))) +            for breakpoint in breakpoints: +                self.assertTrue(breakpoint['verified'], +                                "expect breakpoint verified") + +        # There is no breakpoint delete packet, clients just send another +        # setFunctionBreakpoints packet with the different function names. +        functions.remove('thirteen') +        response = self.vscode.request_setFunctionBreakpoints(functions) +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(functions), +                            "expect %u source breakpoints" % (len(functions))) +            for breakpoint in breakpoints: +                bp_id = breakpoint['id'] +                self.assertTrue(bp_id == bp_id_12, +                                'verify "twelve" breakpoint ID is same') +                self.assertTrue(breakpoint['verified'], +                                "expect breakpoint still verified") + +        # Now get the full list of breakpoints set in the target and verify +        # we have only 1 breakpoints set. The response above could have told +        # us about 1 breakpoints, but we want to make sure we don't have the +        # second one still set in the target +        response = self.vscode.request_testGetTargetBreakpoints() +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(functions), +                            "expect %u source breakpoints" % (len(functions))) +            for breakpoint in breakpoints: +                bp_id = breakpoint['id'] +                self.assertTrue(bp_id == bp_id_12, +                                'verify "twelve" breakpoint ID is same') +                self.assertTrue(breakpoint['verified'], +                                "expect breakpoint still verified") + +        # Now clear all breakpoints for the source file by passing down an +        # empty lines array +        functions = [] +        response = self.vscode.request_setFunctionBreakpoints(functions) +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(functions), +                            "expect %u source breakpoints" % (len(functions))) + +        # Verify with the target that all breakpoints have been cleared +        response = self.vscode.request_testGetTargetBreakpoints() +        if response: +            breakpoints = response['body']['breakpoints'] +            self.assertTrue(len(breakpoints) == len(functions), +                            "expect %u source breakpoints" % (len(functions))) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @no_debug_info_test +    def test_functionality(self): +        '''Tests hitting breakpoints and the functionality of a single +           breakpoint, like 'conditions' and 'hitCondition' settings.''' + +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program) +        # Set a breakpoint on "twelve" with no condition and no hitCondition +        functions = ['twelve'] +        breakpoint_ids = self.set_function_breakpoints(functions) + +        self.assertTrue(len(breakpoint_ids) == len(functions), +                        "expect one breakpoint") + +        # Verify we hit the breakpoint we just set +        self.continue_to_breakpoints(breakpoint_ids) + +        # Make sure i is zero at first breakpoint +        i = int(self.vscode.get_local_variable_value('i')) +        self.assertTrue(i == 0, 'i != 0 after hitting breakpoint') + +        # Update the condition on our breakpoint +        new_breakpoint_ids = self.set_function_breakpoints(functions, +                                                           condition="i==4") +        self.assertTrue(breakpoint_ids == new_breakpoint_ids, +                        "existing breakpoint should have its condition " +                        "updated") + +        self.continue_to_breakpoints(breakpoint_ids) +        i = int(self.vscode.get_local_variable_value('i')) +        self.assertTrue(i == 4, +                        'i != 4 showing conditional works') +        new_breakpoint_ids = self.set_function_breakpoints(functions, +                                                           hitCondition="2") + +        self.assertTrue(breakpoint_ids == new_breakpoint_ids, +                        "existing breakpoint should have its condition " +                        "updated") + +        # Continue with a hitContidtion of 2 and expect it to skip 1 value +        self.continue_to_breakpoints(breakpoint_ids) +        i = int(self.vscode.get_local_variable_value('i')) +        self.assertTrue(i == 6, +                        'i != 6 showing hitCondition works') + +        # continue after hitting our hitCondition and make sure it only goes +        # up by 1 +        self.continue_to_breakpoints(breakpoint_ids) +        i = int(self.vscode.get_local_variable_value('i')) +        self.assertTrue(i == 7, +                        'i != 7 showing post hitCondition hits every time') diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/main.cpp b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/main.cpp new file mode 100644 index 0000000000000..e859b04e3a999 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/main.cpp @@ -0,0 +1,27 @@ +#include <stdio.h> +#include <stdexcept> + +int twelve(int i) { +  return 12 + i; // break 12 +} + +int thirteen(int i) { +  return 13 + i; // break 13 +} + +namespace a { +  int fourteen(int i) { +    return 14 + i; // break 14 +  } +} +int main(int argc, char const *argv[]) { +  for (int i=0; i<10; ++i) { +    int x = twelve(i) + thirteen(i) + a::fourteen(i); // break loop +  } +  try { +    throw std::invalid_argument( "throwing exception for testing" ); +  } catch (...) { +    puts("caught exception..."); +  } +  return 0; +} diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/Makefile b/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/Makefile new file mode 100644 index 0000000000000..b09a579159d48 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/TestVSCode_launch.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/TestVSCode_launch.py new file mode 100644 index 0000000000000..064cf9a6783ee --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/TestVSCode_launch.py @@ -0,0 +1,352 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import pprint +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os +import time + + +class TestVSCode_launch(lldbvscode_testcase.VSCodeTestCaseBase): + +    mydir = TestBase.compute_mydir(__file__) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_default(self): +        ''' +            Tests the default launch of a simple program. No arguments, +            environment, or anything else is specified. +        ''' +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program) +        self.continue_to_exit() +        # Now get the STDOUT and verify our program argument is correct +        output = self.get_stdout() +        self.assertTrue(output and len(output) > 0, +                        "expect program output") +        lines = output.splitlines() +        self.assertTrue(program in lines[0], +                        "make sure program path is in first argument") + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_stopOnEntry(self): +        ''' +            Tests the default launch of a simple program that stops at the +            entry point instead of continuing. +        ''' +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program, stopOnEntry=True) +        self.set_function_breakpoints(['main']) +        stopped_events = self.continue_to_next_stop() +        for stopped_event in stopped_events: +            if 'body' in stopped_event: +                body = stopped_event['body'] +                if 'reason' in body: +                    reason = body['reason'] +                    self.assertTrue( +                        reason != 'breakpoint', +                        'verify stop isn\'t "main" breakpoint') + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_cwd(self): +        ''' +            Tests the default launch of a simple program with a current working +            directory. +        ''' +        program = self.getBuildArtifact("a.out") +        program_parent_dir = os.path.split(os.path.split(program)[0])[0] +        self.build_and_launch(program, +                              cwd=program_parent_dir) +        self.continue_to_exit() +        # Now get the STDOUT and verify our program argument is correct +        output = self.get_stdout() +        self.assertTrue(output and len(output) > 0, +                        "expect program output") +        lines = output.splitlines() +        found = False +        for line in lines: +            if line.startswith('cwd = \"'): +                quote_path = '"%s"' % (program_parent_dir) +                found = True +                self.assertTrue(quote_path in line, +                                "working directory '%s' not in '%s'" % ( +                                    program_parent_dir, line)) +        self.assertTrue(found, "verified program working directory") + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_debuggerRoot(self): +        ''' +            Tests the "debuggerRoot" will change the working directory of +            the lldb-vscode debug adaptor. +        ''' +        program = self.getBuildArtifact("a.out") +        program_parent_dir = os.path.split(os.path.split(program)[0])[0] +        commands = ['platform shell echo cwd = $PWD'] +        self.build_and_launch(program, +                              debuggerRoot=program_parent_dir, +                              initCommands=commands) +        output = self.get_console() +        self.assertTrue(output and len(output) > 0, +                        "expect console output") +        lines = output.splitlines() +        prefix = 'cwd = ' +        found = False +        for line in lines: +            if line.startswith(prefix): +                found = True +                self.assertTrue(program_parent_dir == line[len(prefix):], +                                "lldb-vscode working dir '%s' == '%s'" % ( +                                    program_parent_dir, line[6:])) +        self.assertTrue(found, "verified lldb-vscode working directory") +        self.continue_to_exit() + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_sourcePath(self): +        ''' +            Tests the "sourcePath" will set the target.source-map. +        ''' +        program = self.getBuildArtifact("a.out") +        program_dir = os.path.split(program)[0] +        self.build_and_launch(program, +                              sourcePath=program_dir) +        output = self.get_console() +        self.assertTrue(output and len(output) > 0, +                        "expect console output") +        lines = output.splitlines() +        prefix = '(lldb) settings set target.source-map "." ' +        found = False +        for line in lines: +            if line.startswith(prefix): +                found = True +                quoted_path = '"%s"' % (program_dir) +                self.assertTrue(quoted_path == line[len(prefix):], +                                "lldb-vscode working dir %s == %s" % ( +                                    quoted_path, line[6:])) +        self.assertTrue(found, 'found "sourcePath" in console output') +        self.continue_to_exit() + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_disableSTDIO(self): +        ''' +            Tests the default launch of a simple program with STDIO disabled. +        ''' +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program, +                              disableSTDIO=True) +        self.continue_to_exit() +        # Now get the STDOUT and verify our program argument is correct +        output = self.get_stdout() +        self.assertTrue(output is None or len(output) == 0, +                        "expect no program output") + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_shellExpandArguments_enabled(self): +        ''' +            Tests the default launch of a simple program with shell expansion +            enabled. +        ''' +        program = self.getBuildArtifact("a.out") +        program_dir = os.path.split(program)[0] +        glob = os.path.join(program_dir, '*.out') +        self.build_and_launch(program, args=[glob], shellExpandArguments=True) +        self.continue_to_exit() +        # Now get the STDOUT and verify our program argument is correct +        output = self.get_stdout() +        self.assertTrue(output and len(output) > 0, +                        "expect no program output") +        lines = output.splitlines() +        for line in lines: +            quote_path = '"%s"' % (program) +            if line.startswith("arg[1] ="): +                self.assertTrue(quote_path in line, +                                'verify "%s" expanded to "%s"' % ( +                                    glob, program)) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_shellExpandArguments_disabled(self): +        ''' +            Tests the default launch of a simple program with shell expansion +            disabled. +        ''' +        program = self.getBuildArtifact("a.out") +        program_dir = os.path.split(program)[0] +        glob = os.path.join(program_dir, '*.out') +        self.build_and_launch(program, +                              args=[glob], +                              shellExpandArguments=False) +        self.continue_to_exit() +        # Now get the STDOUT and verify our program argument is correct +        output = self.get_stdout() +        self.assertTrue(output and len(output) > 0, +                        "expect no program output") +        lines = output.splitlines() +        for line in lines: +            quote_path = '"%s"' % (glob) +            if line.startswith("arg[1] ="): +                self.assertTrue(quote_path in line, +                                'verify "%s" stayed to "%s"' % ( +                                    glob, glob)) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_args(self): +        ''' +            Tests launch of a simple program with arguments +        ''' +        program = self.getBuildArtifact("a.out") +        args = ["one", "with space", "'with single quotes'", +                '"with double quotes"'] +        self.build_and_launch(program, +                              args=args) +        self.continue_to_exit() + +        # Now get the STDOUT and verify our arguments got passed correctly +        output = self.get_stdout() +        self.assertTrue(output and len(output) > 0, +                        "expect program output") +        lines = output.splitlines() +        # Skip the first argument that contains the program name +        lines.pop(0) +        # Make sure arguments we specified are correct +        for (i, arg) in enumerate(args): +            quoted_arg = '"%s"' % (arg) +            self.assertTrue(quoted_arg in lines[i], +                            'arg[%i] "%s" not in "%s"' % (i+1, quoted_arg, lines[i])) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_environment(self): +        ''' +            Tests launch of a simple program with environment variables +        ''' +        program = self.getBuildArtifact("a.out") +        env = ["NO_VALUE", "WITH_VALUE=BAR", "EMPTY_VALUE=", +               "SPACE=Hello World"] +        self.build_and_launch(program, +                              env=env) +        self.continue_to_exit() + +        # Now get the STDOUT and verify our arguments got passed correctly +        output = self.get_stdout() +        self.assertTrue(output and len(output) > 0, +                        "expect program output") +        lines = output.splitlines() +        # Skip the all arguments so we have only environment vars left +        while len(lines) and lines[0].startswith("arg["): +            lines.pop(0) +        # Make sure each environment variable in "env" is actually set in the +        # program environment that was printed to STDOUT +        for var in env: +            found = False +            for program_var in lines: +                if var in program_var: +                    found = True +                    break +            self.assertTrue(found, +                            '"%s" must exist in program environment (%s)' % ( +                                var, lines)) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @skipIfLinux # This test is timing out and/or failing on Linux as well as Darwin +    @no_debug_info_test +    def test_commands(self): +        ''' +            Tests the "initCommands", "preRunCommands", "stopCommands" and +            "exitCommands" that can be passed during launch. + +            "initCommands" are a list of LLDB commands that get executed +            before the targt is created. +            "preRunCommands" are a list of LLDB commands that get executed +            after the target has been created and before the launch. +            "stopCommands" are a list of LLDB commands that get executed each +            time the program stops. +            "exitCommands" are a list of LLDB commands that get executed when +            the process exits +        ''' +        program = self.getBuildArtifact("a.out") +        initCommands = ['target list', 'platform list'] +        preRunCommands = ['image list a.out', 'image dump sections a.out'] +        stopCommands = ['frame variable', 'bt'] +        exitCommands = ['expr 2+3', 'expr 3+4'] +        self.build_and_launch(program, +                              initCommands=initCommands, +                              preRunCommands=preRunCommands, +                              stopCommands=stopCommands, +                              exitCommands=exitCommands) + +        # Get output from the console. This should contain both the +        # "initCommands" and the "preRunCommands". +        output = self.get_console() +        # Verify all "initCommands" were found in console output +        self.verify_commands('initCommands', output, initCommands) +        # Verify all "preRunCommands" were found in console output +        self.verify_commands('preRunCommands', output, preRunCommands) + +        source = 'main.c' +        first_line = line_number(source, '// breakpoint 1') +        second_line = line_number(source, '// breakpoint 2') +        lines = [first_line, second_line] + +        # Set 2 breakoints so we can verify that "stopCommands" get run as the +        # breakpoints get hit +        breakpoint_ids = self.set_source_breakpoints(source, lines) +        self.assertTrue(len(breakpoint_ids) == len(lines), +                        "expect correct number of breakpoints") + +        # Continue after launch and hit the first breakpoint. +        # Get output from the console. This should contain both the +        # "stopCommands" that were run after the first breakpoint was hit +        self.continue_to_breakpoints(breakpoint_ids) +        output = self.get_console(timeout=1.0) +        self.verify_commands('stopCommands', output, stopCommands) + +        # Continue again and hit the second breakpoint. +        # Get output from the console. This should contain both the +        # "stopCommands" that were run after the second breakpoint was hit +        self.continue_to_breakpoints(breakpoint_ids) +        output = self.get_console(timeout=1.0) +        self.verify_commands('stopCommands', output, stopCommands) + +        # Continue until the program exits +        self.continue_to_exit() +        # Get output from the console. This should contain both the +        # "exitCommands" that were run after the second breakpoint was hit +        output = self.get_console(timeout=1.0) +        self.verify_commands('exitCommands', output, exitCommands) diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/main.c b/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/main.c new file mode 100644 index 0000000000000..aed2af9828f34 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/main.c @@ -0,0 +1,15 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int main(int argc, char const *argv[], char const *envp[]) { +  for (int i=0; i<argc; ++i) +    printf("arg[%i] = \"%s\"\n", i, argv[i]); +  for (int i=0; envp[i]; ++i) +    printf("env[%i] = \"%s\"\n", i, envp[i]); +  char *cwd = getcwd(NULL, 0); +  printf("cwd = \"%s\"\n", cwd); // breakpoint 1 +  free(cwd); +  cwd = NULL; +  return 0;  // breakpoint 2 +} diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py new file mode 100644 index 0000000000000..c66c6bb7af6bc --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py @@ -0,0 +1,288 @@ +from __future__ import print_function + +from lldbsuite.test.lldbtest import * +import os +import vscode + + +class VSCodeTestCaseBase(TestBase): + +    def create_debug_adaptor(self): +        '''Create the Visual Studio Code debug adaptor''' +        self.assertTrue(os.path.exists(self.lldbVSCodeExec), +                        'lldb-vscode must exist') +        self.vscode = vscode.DebugAdaptor(executable=self.lldbVSCodeExec) + +    def build_and_create_debug_adaptor(self): +        self.build() +        self.create_debug_adaptor() + +    def set_source_breakpoints(self, source_path, lines, condition=None, +                               hitCondition=None): +        '''Sets source breakpoints and returns an array of strings containing +           the breakpoint location IDs ("1.1", "1.2") for each breakpoint +           that was set. +        ''' +        response = self.vscode.request_setBreakpoints( +            source_path, lines, condition=condition, hitCondition=hitCondition) +        if response is None: +            return [] +        breakpoints = response['body']['breakpoints'] +        breakpoint_ids = [] +        for breakpoint in breakpoints: +            response_id = breakpoint['id'] +            bp_id = response_id >> 32 +            bp_loc_id = response_id & 0xffffffff +            breakpoint_ids.append('%i.%i' % (bp_id, bp_loc_id)) +        return breakpoint_ids + +    def set_function_breakpoints(self, functions, condition=None, +                                 hitCondition=None): +        '''Sets breakpoints by function name given an array of function names +           and returns an array of strings containing the breakpoint location +           IDs ("1.1", "1.2") for each breakpoint that was set. +        ''' +        response = self.vscode.request_setFunctionBreakpoints( +            functions, condition=condition, hitCondition=hitCondition) +        if response is None: +            return [] +        breakpoints = response['body']['breakpoints'] +        breakpoint_ids = [] +        for breakpoint in breakpoints: +            response_id = breakpoint['id'] +            bp_id = response_id >> 32 +            bp_loc_id = response_id & 0xffffffff +            breakpoint_ids.append('%i.%i' % (bp_id, bp_loc_id)) +        return breakpoint_ids + +    def verify_breakpoint_hit(self, breakpoint_ids): +        '''Wait for the process we are debugging to stop, and verify we hit +           any breakpoint location in the "breakpoint_ids" array. +           "breakpoint_ids" should be a list of breakpoint location ID strings +           (["1.1", "2.1"]). The return value from +           self.set_source_breakpoints() can be passed to this function''' +        stopped_events = self.vscode.wait_for_stopped() +        for stopped_event in stopped_events: +            if 'body' in stopped_event: +                body = stopped_event['body'] +                if 'reason' not in body: +                    continue +                if body['reason'] != 'breakpoint': +                    continue +                if 'description' not in body: +                    continue +                # Description is "breakpoint 1.1", so look for any location id +                # ("1.1") in the description field as verification that one of +                # the breakpoint locations was hit +                description = body['description'] +                for breakpoint_id in breakpoint_ids: +                    if breakpoint_id in description: +                        return True +        return False + +    def verify_exception_breakpoint_hit(self, filter_label): +        '''Wait for the process we are debugging to stop, and verify the stop +           reason is 'exception' and that the description matches +           'filter_label' +        ''' +        stopped_events = self.vscode.wait_for_stopped() +        for stopped_event in stopped_events: +            if 'body' in stopped_event: +                body = stopped_event['body'] +                if 'reason' not in body: +                    continue +                if body['reason'] != 'exception': +                    continue +                if 'description' not in body: +                    continue +                description = body['description'] +                if filter_label == description: +                    return True +        return False + +    def verify_commands(self, flavor, output, commands): +        self.assertTrue(output and len(output) > 0, "expect console output") +        lines = output.splitlines() +        prefix = '(lldb) ' +        for cmd in commands: +            found = False +            for line in lines: +                if line.startswith(prefix) and cmd in line: +                    found = True +                    break +            self.assertTrue(found, +                            "verify '%s' found in console output for '%s'" % ( +                                cmd, flavor)) + +    def get_dict_value(self, d, key_path): +        '''Verify each key in the key_path array is in contained in each +           dictionary within "d". Assert if any key isn't in the +           corresponding dictionary. This is handy for grabbing values from VS +           Code response dictionary like getting +           response['body']['stackFrames'] +        ''' +        value = d +        for key in key_path: +            if key in value: +                value = value[key] +            else: +                self.assertTrue(key in value, +                                'key "%s" from key_path "%s" not in "%s"' % ( +                                    key, key_path, d)) +        return value + +    def get_stackFrames(self, threadId=None, startFrame=None, levels=None, +                        dump=False): +        response = self.vscode.request_stackTrace(threadId=threadId, +                                                  startFrame=startFrame, +                                                  levels=levels, +                                                  dump=dump) +        if response: +            return self.get_dict_value(response, ['body', 'stackFrames']) +        return None + +    def get_source_and_line(self, threadId=None, frameIndex=0): +        stackFrames = self.get_stackFrames(threadId=threadId, +                                           startFrame=frameIndex, +                                           levels=1) +        if stackFrames is not None: +            stackFrame = stackFrames[0] +            ['source', 'path'] +            if 'source' in stackFrame: +                source = stackFrame['source'] +                if 'path' in source: +                    if 'line' in stackFrame: +                        return (source['path'], stackFrame['line']) +        return ('', 0) + +    def get_stdout(self, timeout=0.0): +        return self.vscode.get_output('stdout', timeout=timeout) + +    def get_console(self, timeout=0.0): +        return self.vscode.get_output('console', timeout=timeout) + +    def get_local_as_int(self, name, threadId=None): +        value = self.vscode.get_local_variable_value(name, threadId=threadId) +        if value.startswith('0x'): +            return int(value, 16) +        elif value.startswith('0'): +            return int(value, 8) +        else: +            return int(value) + +    def set_local(self, name, value, id=None): +        '''Set a top level local variable only.''' +        return self.vscode.request_setVariable(1, name, str(value), id=id) + +    def set_global(self, name, value, id=None): +        '''Set a top level global variable only.''' +        return self.vscode.request_setVariable(2, name, str(value), id=id) + +    def stepIn(self, threadId=None, waitForStop=True): +        self.vscode.request_stepIn(threadId=threadId) +        if waitForStop: +            return self.vscode.wait_for_stopped() +        return None + +    def stepOver(self, threadId=None, waitForStop=True): +        self.vscode.request_next(threadId=threadId) +        if waitForStop: +            return self.vscode.wait_for_stopped() +        return None + +    def stepOut(self, threadId=None, waitForStop=True): +        self.vscode.request_stepOut(threadId=threadId) +        if waitForStop: +            return self.vscode.wait_for_stopped() +        return None + +    def continue_to_next_stop(self): +        self.vscode.request_continue() +        return self.vscode.wait_for_stopped() + +    def continue_to_breakpoints(self, breakpoint_ids): +        self.vscode.request_continue() +        self.verify_breakpoint_hit(breakpoint_ids) + +    def continue_to_exception_breakpoint(self, filter_label): +        self.vscode.request_continue() +        self.assertTrue(self.verify_exception_breakpoint_hit(filter_label), +                        'verify we got "%s"' % (filter_label)) + +    def continue_to_exit(self, exitCode=0): +        self.vscode.request_continue() +        stopped_events = self.vscode.wait_for_stopped() +        self.assertTrue(len(stopped_events) == 1, +                        "expecting single 'exited' event") +        self.assertTrue(stopped_events[0]['event'] == 'exited', +                        'make sure program ran to completion') +        self.assertTrue(stopped_events[0]['body']['exitCode'] == exitCode, +                        'exitCode == %i' % (exitCode)) + +    def attach(self, program=None, pid=None, waitFor=None, trace=None, +               initCommands=None, preRunCommands=None, stopCommands=None, +               exitCommands=None, attachCommands=None): +        '''Build the default Makefile target, create the VSCode debug adaptor, +           and attach to the process. +        ''' +        # Make sure we disconnect and terminate the VSCode debug adaptor even +        # if we throw an exception during the test case. +        def cleanup(): +            self.vscode.request_disconnect(terminateDebuggee=True) +            self.vscode.terminate() + +        # Execute the cleanup function during test case tear down. +        self.addTearDownHook(cleanup) +        # Initialize and launch the program +        self.vscode.request_initialize() +        response = self.vscode.request_attach( +            program=program, pid=pid, waitFor=waitFor, trace=trace, +            initCommands=initCommands, preRunCommands=preRunCommands, +            stopCommands=stopCommands, exitCommands=exitCommands, +            attachCommands=attachCommands) +        if not (response and response['success']): +            self.assertTrue(response['success'], +                            'attach failed (%s)' % (response['message'])) + +    def build_and_launch(self, program, args=None, cwd=None, env=None, +                         stopOnEntry=False, disableASLR=True, +                         disableSTDIO=False, shellExpandArguments=False, +                         trace=False, initCommands=None, preRunCommands=None, +                         stopCommands=None, exitCommands=None, +                         sourcePath=None, debuggerRoot=None): +        '''Build the default Makefile target, create the VSCode debug adaptor, +           and launch the process. +        ''' +        self.build_and_create_debug_adaptor() +        self.assertTrue(os.path.exists(program), 'executable must exist') + +        # Make sure we disconnect and terminate the VSCode debug adaptor even +        # if we throw an exception during the test case. +        def cleanup(): +            self.vscode.request_disconnect(terminateDebuggee=True) +            self.vscode.terminate() + +        # Execute the cleanup function during test case tear down. +        self.addTearDownHook(cleanup) + +        # Initialize and launch the program +        self.vscode.request_initialize() +        response = self.vscode.request_launch( +            program, +            args=args, +            cwd=cwd, +            env=env, +            stopOnEntry=stopOnEntry, +            disableASLR=disableASLR, +            disableSTDIO=disableSTDIO, +            shellExpandArguments=shellExpandArguments, +            trace=trace, +            initCommands=initCommands, +            preRunCommands=preRunCommands, +            stopCommands=stopCommands, +            exitCommands=exitCommands, +            sourcePath=sourcePath, +            debuggerRoot=debuggerRoot) +        if not (response and response['success']): +            self.assertTrue(response['success'], +                            'launch failed (%s)' % (response['message'])) diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/Makefile b/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/Makefile new file mode 100644 index 0000000000000..b09a579159d48 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/TestVSCode_stackTrace.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/TestVSCode_stackTrace.py new file mode 100644 index 0000000000000..4bb061881c475 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/TestVSCode_stackTrace.py @@ -0,0 +1,160 @@ +""" +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 +import os + + +class TestVSCode_stackTrace(lldbvscode_testcase.VSCodeTestCaseBase): + +    mydir = TestBase.compute_mydir(__file__) +    name_key_path = ['name'] +    source_key_path = ['source', 'path'] +    line_key_path = ['line'] + +    def verify_stackFrames(self, start_idx, stackFrames): +        frame_idx = start_idx +        for stackFrame in stackFrames: +            # Don't care about frame above main +            if frame_idx > 20: +                return +            self.verify_stackFrame(frame_idx, stackFrame) +            frame_idx += 1 + +    def verify_stackFrame(self, frame_idx, stackFrame): +        frame_name = self.get_dict_value(stackFrame, self.name_key_path) +        frame_source = self.get_dict_value(stackFrame, self.source_key_path) +        frame_line = self.get_dict_value(stackFrame, self.line_key_path) +        if frame_idx == 0: +            expected_line = self.recurse_end +            expected_name = 'recurse' +        elif frame_idx < 20: +            expected_line = self.recurse_call +            expected_name = 'recurse' +        else: +            expected_line = self.recurse_invocation +            expected_name = 'main' +        self.assertTrue(frame_name == expected_name, +                        'frame #%i name "%s" == "%s"' % ( +                            frame_idx, frame_name, expected_name)) +        self.assertTrue(frame_source == self.source_path, +                        'frame #%i source "%s" == "%s"' % ( +                            frame_idx, frame_source, self.source_path)) +        self.assertTrue(frame_line == expected_line, +                        'frame #%i line %i == %i' % (frame_idx, frame_line, +                                                     expected_line)) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @no_debug_info_test +    def test_stackTrace(self): +        ''' +            Tests the 'stackTrace' packet and all its variants. +        ''' +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program) +        source = 'main.c' +        self.source_path = os.path.join(os.getcwd(), source) +        self.recurse_end = line_number(source, 'recurse end') +        self.recurse_call = line_number(source, 'recurse call') +        self.recurse_invocation = line_number(source, 'recurse invocation') + +        lines = [self.recurse_end] + +        # Set breakoint at a point of deepest recuusion +        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) +        startFrame = 0 +        # Verify we get all stack frames with no arguments +        stackFrames = self.get_stackFrames() +        frameCount = len(stackFrames) +        self.assertTrue(frameCount >= 20, +                        'verify we get at least 20 frames for all frames') +        self.verify_stackFrames(startFrame, stackFrames) + +        # Verify all stack frames by specifying startFrame = 0 and levels not +        # specified +        stackFrames = self.get_stackFrames(startFrame=startFrame) +        self.assertTrue(frameCount == len(stackFrames), +                        ('verify same number of frames with startFrame=%i') % ( +                            startFrame)) +        self.verify_stackFrames(startFrame, stackFrames) + +        # Verify all stack frames by specifying startFrame = 0 and levels = 0 +        levels = 0 +        stackFrames = self.get_stackFrames(startFrame=startFrame, +                                           levels=levels) +        self.assertTrue(frameCount == len(stackFrames), +                        ('verify same number of frames with startFrame=%i and' +                         ' levels=%i') % (startFrame, levels)) +        self.verify_stackFrames(startFrame, stackFrames) + +        # Get only the first stack frame by sepcifying startFrame = 0 and +        # levels = 1 +        levels = 1 +        stackFrames = self.get_stackFrames(startFrame=startFrame, +                                           levels=levels) +        self.assertTrue(levels == len(stackFrames), +                        ('verify one frame with startFrame=%i and' +                         ' levels=%i') % (startFrame, levels)) +        self.verify_stackFrames(startFrame, stackFrames) + +        # Get only the first 3 stack frames by sepcifying startFrame = 0 and +        # levels = 3 +        levels = 3 +        stackFrames = self.get_stackFrames(startFrame=startFrame, +                                           levels=levels) +        self.assertTrue(levels == len(stackFrames), +                        ('verify %i frames with startFrame=%i and' +                         ' levels=%i') % (levels, startFrame, levels)) +        self.verify_stackFrames(startFrame, stackFrames) + +        # Get only the first 15 stack frames by sepcifying startFrame = 5 and +        # levels = 16 +        startFrame = 5 +        levels = 16 +        stackFrames = self.get_stackFrames(startFrame=startFrame, +                                           levels=levels) +        self.assertTrue(levels == len(stackFrames), +                        ('verify %i frames with startFrame=%i and' +                         ' levels=%i') % (levels, startFrame, levels)) +        self.verify_stackFrames(startFrame, stackFrames) + +        # Verify we cap things correctly when we ask for too many frames +        startFrame = 5 +        levels = 1000 +        stackFrames = self.get_stackFrames(startFrame=startFrame, +                                           levels=levels) +        self.assertTrue(len(stackFrames) == frameCount - startFrame, +                        ('verify less than 1000 frames with startFrame=%i and' +                         ' levels=%i') % (startFrame, levels)) +        self.verify_stackFrames(startFrame, stackFrames) + +        # Verify level=0 works with non-zerp start frame +        startFrame = 5 +        levels = 0 +        stackFrames = self.get_stackFrames(startFrame=startFrame, +                                           levels=levels) +        self.assertTrue(len(stackFrames) == frameCount - startFrame, +                        ('verify less than 1000 frames with startFrame=%i and' +                         ' levels=%i') % (startFrame, levels)) +        self.verify_stackFrames(startFrame, stackFrames) + +        # Verify we get not frames when startFrame is too high +        startFrame = 1000 +        levels = 1 +        stackFrames = self.get_stackFrames(startFrame=startFrame, +                                           levels=levels) +        self.assertTrue(0 == len(stackFrames), +                        'verify zero frames with startFrame out of bounds') diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/main.c b/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/main.c new file mode 100644 index 0000000000000..85b41c492817f --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/main.c @@ -0,0 +1,13 @@ +#include <stdio.h> +#include <unistd.h> + +int recurse(int x) { +  if (x <= 1) +    return 1; // recurse end +  return recurse(x-1) + x; // recurse call +} + +int main(int argc, char const *argv[]) { +  recurse(20); // recurse invocation +  return 0; +} diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/step/Makefile b/packages/Python/lldbsuite/test/tools/lldb-vscode/step/Makefile new file mode 100644 index 0000000000000..f24bb9f4d267e --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/step/Makefile @@ -0,0 +1,7 @@ +LEVEL = ../../../make + +ENABLE_THREADS := YES + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/step/TestVSCode_step.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/step/TestVSCode_step.py new file mode 100644 index 0000000000000..87ec71a513f6d --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/step/TestVSCode_step.py @@ -0,0 +1,79 @@ +""" +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 +import os + + +class TestVSCode_step(lldbvscode_testcase.VSCodeTestCaseBase): + +    mydir = TestBase.compute_mydir(__file__) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @no_debug_info_test +    def test_step(self): +        ''' +            Tests the stepping in/out/over in threads. +        ''' +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program) +        source = 'main.cpp' +        # source_path = os.path.join(os.getcwd(), source) +        breakpoint1_line = line_number(source, '// breakpoint 1') +        lines = [breakpoint1_line] +        # Set breakoint 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) +        threads = self.vscode.get_threads() +        for thread in threads: +            if 'reason' in thread: +                reason = thread['reason'] +                if reason == 'breakpoint': +                    # We have a thread that is stopped at our breakpoint. +                    # Get the value of "x" and get the source file and line. +                    # These will help us determine if we are stepping +                    # correctly. If we step a thread correctly we will verify +                    # the correct falue for x as it progresses through the +                    # program. +                    tid = thread['id'] +                    x1 = self.get_local_as_int('x', threadId=tid) +                    (src1, line1) = self.get_source_and_line(threadId=tid) + +                    # Now step into the "recurse()" function call again and +                    # verify, using the new value of "x" and the source file +                    # and line if we stepped correctly +                    self.stepIn(threadId=tid, waitForStop=True) +                    x2 = self.get_local_as_int('x', threadId=tid) +                    (src2, line2) = self.get_source_and_line(threadId=tid) +                    self.assertTrue(x1 == x2 + 1, 'verify step in variable') +                    self.assertTrue(line2 < line1, 'verify step in line') +                    self.assertTrue(src1 == src2, 'verify step in source') + +                    # Now step out and verify +                    self.stepOut(threadId=tid, waitForStop=True) +                    x3 = self.get_local_as_int('x', threadId=tid) +                    (src3, line3) = self.get_source_and_line(threadId=tid) +                    self.assertTrue(x1 == x3, 'verify step out variable') +                    self.assertTrue(line3 >= line1, 'verify step out line') +                    self.assertTrue(src1 == src3, 'verify step in source') + +                    # Step over and verify +                    self.stepOver(threadId=tid, waitForStop=True) +                    x4 = self.get_local_as_int('x', threadId=tid) +                    (src4, line4) = self.get_source_and_line(threadId=tid) +                    self.assertTrue(x4 == x3, 'verify step over variable') +                    self.assertTrue(line4 > line3, 'verify step over line') +                    self.assertTrue(src1 == src4, 'verify step over source') +                    # only step one thread that is at the breakpoint and stop +                    break diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/step/main.cpp b/packages/Python/lldbsuite/test/tools/lldb-vscode/step/main.cpp new file mode 100644 index 0000000000000..2fd063113875f --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/step/main.cpp @@ -0,0 +1,16 @@ +#include <thread> + +int function(int x) { +  if ((x % 2) == 0) +    return function(x-1) + x; // breakpoint 1 +  else +    return x; +} + +int main(int argc, char const *argv[]) { +  std::thread thread1(function, 2); +  std::thread thread2(function, 4); +  thread1.join(); +  thread2.join(); +  return 0; +} diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/Makefile b/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/Makefile new file mode 100644 index 0000000000000..314f1cb2f077b --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py new file mode 100644 index 0000000000000..0a8906f1c6c81 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py @@ -0,0 +1,225 @@ +""" +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 +import os + + +def make_buffer_verify_dict(start_idx, count, offset=0): +    verify_dict = {} +    for i in range(start_idx, start_idx + count): +        verify_dict['[%i]' % (i)] = {'type': 'int', 'value': str(i+offset)} +    return verify_dict + + +class TestVSCode_variables(lldbvscode_testcase.VSCodeTestCaseBase): + +    mydir = TestBase.compute_mydir(__file__) + +    def verify_values(self, verify_dict, actual, varref_dict=None): +        if 'equals' in verify_dict: +            verify = verify_dict['equals'] +            for key in verify: +                verify_value = verify[key] +                actual_value = actual[key] +                self.assertTrue(verify_value == actual_value, +                                '"%s" keys don\'t match (%s != %s)' % ( +                                    key, actual_value, verify_value)) +        if 'startswith' in verify_dict: +            verify = verify_dict['startswith'] +            for key in verify: +                verify_value = verify[key] +                actual_value = actual[key] +                startswith = actual_value.startswith(verify_value) +                self.assertTrue(startswith, +                                ('"%s" value "%s" doesn\'t start with' +                                 ' "%s")') % ( +                                    key, actual_value, +                                    verify_value)) +        hasVariablesReference = 'variablesReference' in actual +        varRef = None +        if hasVariablesReference: +            # Remember variable references in case we want to test further +            # by using the evaluate name. +            varRef = actual['variablesReference'] +            if varRef != 0 and varref_dict is not None: +                varref_dict[actual['evaluateName']] = varRef +        if ('hasVariablesReference' in verify_dict and +                verify_dict['hasVariablesReference']): +            self.assertTrue(hasVariablesReference, +                            "verify variable reference") +        if 'children' in verify_dict: +            self.assertTrue(hasVariablesReference and varRef is not None and +                            varRef != 0, +                            ("children verify values specified for " +                             "variable without children")) + +            response = self.vscode.request_variables(varRef) +            self.verify_variables(verify_dict['children'], +                                  response['body']['variables'], +                                  varref_dict) + +    def verify_variables(self, verify_dict, variables, varref_dict=None): +        for variable in variables: +            name = variable['name'] +            self.assertTrue(name in verify_dict, +                            'variable "%s" in verify dictionary' % (name)) +            self.verify_values(verify_dict[name], variable, varref_dict) + +    @skipIfWindows +    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots +    @no_debug_info_test +    def test_scopes_variables_setVariable_evaluate(self): +        ''' +            Tests the "scopes", "variables", "setVariable", and "evaluate" +            packets. +        ''' +        program = self.getBuildArtifact("a.out") +        self.build_and_launch(program) +        source = 'main.cpp' +        breakpoint1_line = line_number(source, '// breakpoint 1') +        lines = [breakpoint1_line] +        # Set breakoint 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) +        locals = self.vscode.get_local_variables() +        globals = self.vscode.get_global_variables() +        buffer_children = make_buffer_verify_dict(0, 32) +        verify_locals = { +            'argc': { +                'equals': {'type': 'int', 'value': '1'} +            }, +            'argv': { +                'equals': {'type': 'const char **'}, +                'startswith': {'value': '0x'}, +                'hasVariablesReference': True +            }, +            'pt': { +                'equals': {'type': 'PointType'}, +                'hasVariablesReference': True, +                'children': { +                    'x': {'equals': {'type': 'int', 'value': '11'}}, +                    'y': {'equals': {'type': 'int', 'value': '22'}}, +                    'buffer': {'children': buffer_children} +                } +            } +        } +        verify_globals = { +            's_local': { +                'equals': {'type': 'float', 'value': '2.25'} +            }, +            '::g_global': { +                'equals': {'type': 'int', 'value': '123'} +            }, +            's_global': { +                'equals': {'type': 'int', 'value': '234'} +            }, +        } +        varref_dict = {} +        self.verify_variables(verify_locals, locals, varref_dict) +        self.verify_variables(verify_globals, globals, varref_dict) +        # pprint.PrettyPrinter(indent=4).pprint(varref_dict) +        # We need to test the functionality of the "variables" request as it +        # has optional parameters like "start" and "count" to limit the number +        # of variables that are fetched +        varRef = varref_dict['pt.buffer'] +        response = self.vscode.request_variables(varRef) +        self.verify_variables(buffer_children, response['body']['variables']) +        # Verify setting start=0 in the arguments still gets all children +        response = self.vscode.request_variables(varRef, start=0) +        self.verify_variables(buffer_children, response['body']['variables']) +        # Verify setting count=0 in the arguments still gets all children. +        # If count is zero, it means to get all children. +        response = self.vscode.request_variables(varRef, count=0) +        self.verify_variables(buffer_children, response['body']['variables']) +        # Verify setting count to a value that is too large in the arguments +        # still gets all children, and no more +        response = self.vscode.request_variables(varRef, count=1000) +        self.verify_variables(buffer_children, response['body']['variables']) +        # Verify setting the start index and count gets only the children we +        # want +        response = self.vscode.request_variables(varRef, start=5, count=5) +        self.verify_variables(make_buffer_verify_dict(5, 5), +                              response['body']['variables']) +        # Verify setting the start index to a value that is out of range +        # results in an empty list +        response = self.vscode.request_variables(varRef, start=32, count=1) +        self.assertTrue(len(response['body']['variables']) == 0, +                        'verify we get no variable back for invalid start') + +        # Test evaluate +        expressions = { +            'pt.x': { +                'equals': {'result': '11', 'type': 'int'}, +                'hasVariablesReference': False +            }, +            'pt.buffer[2]': { +                'equals': {'result': '2', 'type': 'int'}, +                'hasVariablesReference': False +            }, +            'pt': { +                'equals': {'type': 'PointType'}, +                'startswith': {'result': 'PointType @ 0x'}, +                'hasVariablesReference': True +            }, +            'pt.buffer': { +                'equals': {'type': 'int [32]'}, +                'startswith': {'result': 'int [32] @ 0x'}, +                'hasVariablesReference': True +            }, +            'argv': { +                'equals': {'type': 'const char **'}, +                'startswith': {'result': '0x'}, +                'hasVariablesReference': True +            }, +            'argv[0]': { +                'equals': {'type': 'const char *'}, +                'startswith': {'result': '0x'}, +                'hasVariablesReference': True +            }, +            '2+3': { +                'equals': {'result': '5', 'type': 'int'}, +                'hasVariablesReference': False +            }, +        } +        for expression in expressions: +            response = self.vscode.request_evaluate(expression) +            self.verify_values(expressions[expression], response['body']) + +        # Test setting variables +        self.set_local('argc', 123) +        argc = self.get_local_as_int('argc') +        self.assertTrue(argc == 123, +                        'verify argc was set to 123 (123 != %i)' % (argc)) + +        self.set_local('argv', 0x1234) +        argv = self.get_local_as_int('argv') +        self.assertTrue(argv == 0x1234, +                        'verify argv was set to 0x1234 (0x1234 != %#x)' % ( +                            argv)) + +        # Set a variable value whose name is synthetic, like a variable index +        # and verify the value by reading it +        self.vscode.request_setVariable(varRef, "[0]", 100) +        response = self.vscode.request_variables(varRef, start=0, count=1) +        self.verify_variables(make_buffer_verify_dict(0, 1, 100), +                              response['body']['variables']) + +        # Set a variable value whose name is a real child value, like "pt.x" +        # and verify the value by reading it +        varRef = varref_dict['pt'] +        self.vscode.request_setVariable(varRef, "x", 111) +        response = self.vscode.request_variables(varRef, start=0, count=1) +        value = response['body']['variables'][0]['value'] +        self.assertTrue(value == '111', +                        'verify pt.x got set to 111 (111 != %s)' % (value)) diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/main.cpp b/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/main.cpp new file mode 100644 index 0000000000000..0223bd0a75ea8 --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/main.cpp @@ -0,0 +1,18 @@ + +#define BUFFER_SIZE 32 +struct PointType { +  int x; +  int y; +  int buffer[BUFFER_SIZE]; +}; + +int g_global = 123; +static int s_global = 234; + +int main(int argc, char const *argv[]) { +  static float s_local = 2.25; +  PointType pt = { 11,22, {0}}; +  for (int i=0; i<BUFFER_SIZE; ++i) +    pt.buffer[i] = i; +  return s_global - g_global - pt.y; // breakpoint 1 +} diff --git a/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py new file mode 100644 index 0000000000000..066158d0877fb --- /dev/null +++ b/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py @@ -0,0 +1,1084 @@ +#!/usr/bin/env python + +import binascii +import json +import optparse +import os +import pprint +import socket +import string +import subprocess +import sys +import threading + + +def dump_memory(base_addr, data, num_per_line, outfile): + +    data_len = len(data) +    hex_string = binascii.hexlify(data) +    addr = base_addr +    ascii_str = '' +    i = 0 +    while i < data_len: +        outfile.write('0x%8.8x: ' % (addr + i)) +        bytes_left = data_len - i +        if bytes_left >= num_per_line: +            curr_data_len = num_per_line +        else: +            curr_data_len = bytes_left +        hex_start_idx = i * 2 +        hex_end_idx = hex_start_idx + curr_data_len * 2 +        curr_hex_str = hex_string[hex_start_idx:hex_end_idx] +        # 'curr_hex_str' now contains the hex byte string for the +        # current line with no spaces between bytes +        t = iter(curr_hex_str) +        # Print hex bytes separated by space +        outfile.write(' '.join(a + b for a, b in zip(t, t))) +        # Print two spaces +        outfile.write('  ') +        # Calculate ASCII string for bytes into 'ascii_str' +        ascii_str = '' +        for j in range(i, i + curr_data_len): +            ch = data[j] +            if ch in string.printable and ch not in string.whitespace: +                ascii_str += '%c' % (ch) +            else: +                ascii_str += '.' +        # Print ASCII representation and newline +        outfile.write(ascii_str) +        i = i + curr_data_len +        outfile.write('\n') + + +def read_packet(f, verbose=False, trace_file=None): +    '''Decode a JSON packet that starts with the content length and is +       followed by the JSON bytes from a file 'f' +    ''' +    line = f.readline() +    if len(line) == 0: +        return None + +    # Watch for line that starts with the prefix +    prefix = 'Content-Length: ' +    if line.startswith(prefix): +        # Decode length of JSON bytes +        if verbose: +            print('content: "%s"' % (line)) +        length = int(line[len(prefix):]) +        if verbose: +            print('length: "%u"' % (length)) +        # Skip empty line +        line = f.readline() +        if verbose: +            print('empty: "%s"' % (line)) +        # Read JSON bytes +        json_str = f.read(length) +        if verbose: +            print('json: "%s"' % (json_str)) +        if trace_file: +            trace_file.write('from adaptor:\n%s\n' % (json_str)) +        # Decode the JSON bytes into a python dictionary +        return json.loads(json_str) + +    return None + + +def packet_type_is(packet, packet_type): +    return 'type' in packet and packet['type'] == packet_type + + +def read_packet_thread(vs_comm): +    done = False +    while not done: +        packet = read_packet(vs_comm.recv, trace_file=vs_comm.trace_file) +        if packet: +            done = not vs_comm.handle_recv_packet(packet) +        else: +            done = True + + +class DebugCommunication(object): + +    def __init__(self, recv, send): +        self.trace_file = None +        self.send = send +        self.recv = recv +        self.recv_packets = [] +        self.recv_condition = threading.Condition() +        self.recv_thread = threading.Thread(target=read_packet_thread, +                                            args=(self,)) +        self.process_event_body = None +        self.exit_status = None +        self.initialize_body = None +        self.thread_stop_reasons = {} +        self.sequence = 1 +        self.threads = None +        self.recv_thread.start() +        self.output_condition = threading.Condition() +        self.output = {} +        self.configuration_done_sent = False +        self.frame_scopes = {} + +    @classmethod +    def encode_content(cls, s): +        return "Content-Length: %u\r\n\r\n%s" % (len(s), s) + +    @classmethod +    def validate_response(cls, command, response): +        if command['command'] != response['command']: +            raise ValueError('command mismatch in response') +        if command['seq'] != response['request_seq']: +            raise ValueError('seq mismatch in response') + +    def get_output(self, category, timeout=0.0, clear=True): +        self.output_condition.acquire() +        output = None +        if category in self.output: +            output = self.output[category] +            if clear: +                del self.output[category] +        elif timeout != 0.0: +            self.output_condition.wait(timeout) +            if category in self.output: +                output = self.output[category] +                if clear: +                    del self.output[category] +        self.output_condition.release() +        return output + +    def handle_recv_packet(self, packet): +        '''Called by the read thread that is waiting for all incoming packets +           to store the incoming packet in "self.recv_packets" in a thread safe +           way. This function will then signal the "self.recv_condition" to +           indicate a new packet is available. Returns True if the caller +           should keep calling this function for more packets. +        ''' +        # Check the packet to see if is an event packet +        keepGoing = True +        packet_type = packet['type'] +        if packet_type == 'event': +            event = packet['event'] +            body = None +            if 'body' in packet: +                body = packet['body'] +            # Handle the event packet and cache information from these packets +            # as they come in +            if event == 'output': +                # Store any output we receive so clients can retrieve it later. +                category = body['category'] +                output = body['output'] +                self.output_condition.acquire() +                if category in self.output: +                    self.output[category] += output +                else: +                    self.output[category] = output +                self.output_condition.notify() +                self.output_condition.release() +                # no need to add 'output' packets to our packets list +                return keepGoing +            elif event == 'process': +                # When a new process is attached or launched, remember the +                # details that are available in the body of the event +                self.process_event_body = body +            elif event == 'stopped': +                # Each thread that stops with a reason will send a +                # 'stopped' event. We need to remember the thread stop +                # reasons since the 'threads' command doesn't return +                # that information. +                self._process_stopped() +                tid = body['threadId'] +                self.thread_stop_reasons[tid] = body +        elif packet_type == 'response': +            if packet['command'] == 'disconnect': +                keepGoing = False +        self.recv_condition.acquire() +        self.recv_packets.append(packet) +        self.recv_condition.notify() +        self.recv_condition.release() +        return keepGoing + +    def send_packet(self, command_dict, set_sequence=True): +        '''Take the "command_dict" python dictionary and encode it as a JSON +           string and send the contents as a packet to the VSCode debug +           adaptor''' +        # Set the sequence ID for this command automatically +        if set_sequence: +            command_dict['seq'] = self.sequence +            self.sequence += 1 +        # Encode our command dictionary as a JSON string +        json_str = json.dumps(command_dict, separators=(',', ':')) +        if self.trace_file: +            self.trace_file.write('to adaptor:\n%s\n' % (json_str)) +        length = len(json_str) +        if length > 0: +            # Send the encoded JSON packet and flush the 'send' file +            self.send.write(self.encode_content(json_str)) +            self.send.flush() + +    def recv_packet(self, filter_type=None, filter_event=None, timeout=None): +        '''Get a JSON packet from the VSCode debug adaptor. This function +           assumes a thread that reads packets is running and will deliver +           any received packets by calling handle_recv_packet(...). This +           function will wait for the packet to arrive and return it when +           it does.''' +        while True: +            self.recv_condition.acquire() +            packet = None +            while True: +                for (i, curr_packet) in enumerate(self.recv_packets): +                    packet_type = curr_packet['type'] +                    if filter_type is None or packet_type in filter_type: +                        if (filter_event is None or +                            (packet_type == 'event' and +                             curr_packet['event'] in filter_event)): +                            packet = self.recv_packets.pop(i) +                            break +                if packet: +                    break +                # Sleep until packet is received +                len_before = len(self.recv_packets) +                self.recv_condition.wait(timeout) +                len_after = len(self.recv_packets) +                if len_before == len_after: +                    return None  # Timed out +            self.recv_condition.release() +            return packet + +        return None + +    def send_recv(self, command): +        '''Send a command python dictionary as JSON and receive the JSON +           response. Validates that the response is the correct sequence and +           command in the reply. Any events that are received are added to the +           events list in this object''' +        self.send_packet(command) +        done = False +        while not done: +            response = self.recv_packet(filter_type='response') +            if response is None: +                desc = 'no response for "%s"' % (command['command']) +                raise ValueError(desc) +            self.validate_response(command, response) +            return response +        return None + +    def wait_for_event(self, filter=None, timeout=None): +        while True: +            return self.recv_packet(filter_type='event', filter_event=filter, +                                    timeout=timeout) +        return None + +    def wait_for_stopped(self, timeout=None): +        stopped_events = [] +        stopped_event = self.wait_for_event(filter=['stopped', 'exited'], +                                            timeout=timeout) +        exited = False +        while stopped_event: +            stopped_events.append(stopped_event) +            # If we exited, then we are done +            if stopped_event['event'] == 'exited': +                self.exit_status = stopped_event['body']['exitCode'] +                exited = True +                break +            # Otherwise we stopped and there might be one or more 'stopped' +            # events for each thread that stopped with a reason, so keep +            # checking for more 'stopped' events and return all of them +            stopped_event = self.wait_for_event(filter='stopped', timeout=0.25) +        if exited: +            self.threads = [] +        return stopped_events + +    def wait_for_exited(self): +        event_dict = self.wait_for_event('exited') +        if event_dict is None: +            raise ValueError("didn't get stopped event") +        return event_dict + +    def get_initialize_value(self, key): +        '''Get a value for the given key if it there is a key/value pair in +           the "initialize" request response body. +        ''' +        if self.initialize_body and key in self.initialize_body: +            return self.initialize_body[key] +        return None + +    def get_threads(self): +        if self.threads is None: +            self.request_threads() +        return self.threads + +    def get_thread_id(self, threadIndex=0): +        '''Utility function to get the first thread ID in the thread list. +           If the thread list is empty, then fetch the threads. +        ''' +        if self.threads is None: +            self.request_threads() +        if self.threads and threadIndex < len(self.threads): +            return self.threads[threadIndex]['id'] +        return None + +    def get_stackFrame(self, frameIndex=0, threadId=None): +        '''Get a single "StackFrame" object from a "stackTrace" request and +           return the "StackFrame as a python dictionary, or None on failure +        ''' +        if threadId is None: +            threadId = self.get_thread_id() +        if threadId is None: +            print('invalid threadId') +            return None +        response = self.request_stackTrace(threadId, startFrame=frameIndex, +                                           levels=1) +        if response: +            return response['body']['stackFrames'][0] +        print('invalid response') +        return None + +    def get_scope_variables(self, scope_name, frameIndex=0, threadId=None): +        stackFrame = self.get_stackFrame(frameIndex=frameIndex, +                                         threadId=threadId) +        if stackFrame is None: +            return [] +        frameId = stackFrame['id'] +        if frameId in self.frame_scopes: +            frame_scopes = self.frame_scopes[frameId] +        else: +            scopes_response = self.request_scopes(frameId) +            frame_scopes = scopes_response['body']['scopes'] +            self.frame_scopes[frameId] = frame_scopes +        for scope in frame_scopes: +            if scope['name'] == scope_name: +                varRef = scope['variablesReference'] +                variables_response = self.request_variables(varRef) +                if variables_response: +                    if 'body' in variables_response: +                        body = variables_response['body'] +                        if 'variables' in body: +                            vars = body['variables'] +                            return vars +        return [] + +    def get_global_variables(self, frameIndex=0, threadId=None): +        return self.get_scope_variables('Globals', frameIndex=frameIndex, +                                        threadId=threadId) + +    def get_local_variables(self, frameIndex=0, threadId=None): +        return self.get_scope_variables('Locals', frameIndex=frameIndex, +                                        threadId=threadId) + +    def get_local_variable(self, name, frameIndex=0, threadId=None): +        locals = self.get_local_variables(frameIndex=frameIndex, +                                          threadId=threadId) +        for local in locals: +            if 'name' in local and local['name'] == name: +                return local +        return None + +    def get_local_variable_value(self, name, frameIndex=0, threadId=None): +        variable = self.get_local_variable(name, frameIndex=frameIndex, +                                           threadId=threadId) +        if variable and 'value' in variable: +            return variable['value'] +        return None + +    def replay_packets(self, replay_file_path): +        f = open(replay_file_path, 'r') +        mode = 'invalid' +        set_sequence = False +        command_dict = None +        while mode != 'eof': +            if mode == 'invalid': +                line = f.readline() +                if line.startswith('to adapter:'): +                    mode = 'send' +                elif line.startswith('from adapter:'): +                    mode = 'recv' +            elif mode == 'send': +                command_dict = read_packet(f) +                # Skip the end of line that follows the JSON +                f.readline() +                if command_dict is None: +                    raise ValueError('decode packet failed from replay file') +                print('Sending:') +                pprint.PrettyPrinter(indent=2).pprint(command_dict) +                # raw_input('Press ENTER to send:') +                self.send_packet(command_dict, set_sequence) +                mode = 'invalid' +            elif mode == 'recv': +                print('Replay response:') +                replay_response = read_packet(f) +                # Skip the end of line that follows the JSON +                f.readline() +                pprint.PrettyPrinter(indent=2).pprint(replay_response) +                actual_response = self.recv_packet() +                if actual_response: +                    type = actual_response['type'] +                    print('Actual response:') +                    if type == 'response': +                        self.validate_response(command_dict, actual_response) +                    pprint.PrettyPrinter(indent=2).pprint(actual_response) +                else: +                    print("error: didn't get a valid response") +                mode = 'invalid' + +    def request_attach(self, program=None, pid=None, waitFor=None, trace=None, +                       initCommands=None, preRunCommands=None, +                       stopCommands=None, exitCommands=None, +                       attachCommands=None): +        args_dict = {} +        if pid is not None: +            args_dict['pid'] = pid +        if program is not None: +            args_dict['program'] = program +        if waitFor is not None: +            args_dict['waitFor'] = waitFor +        if trace: +            args_dict['trace'] = trace +        if initCommands: +            args_dict['initCommands'] = initCommands +        if preRunCommands: +            args_dict['preRunCommands'] = preRunCommands +        if stopCommands: +            args_dict['stopCommands'] = stopCommands +        if exitCommands: +            args_dict['exitCommands'] = exitCommands +        if attachCommands: +            args_dict['attachCommands'] = attachCommands +        command_dict = { +            'command': 'attach', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_configurationDone(self): +        command_dict = { +            'command': 'configurationDone', +            'type': 'request', +            'arguments': {} +        } +        response = self.send_recv(command_dict) +        if response: +            self.configuration_done_sent = True +        return response + +    def _process_stopped(self): +        self.threads = None +        self.frame_scopes = {} + +    def request_continue(self, threadId=None): +        if self.exit_status is not None: +            raise ValueError('request_continue called after process exited') +        # If we have launched or attached, then the first continue is done by +        # sending the 'configurationDone' request +        if not self.configuration_done_sent: +            return self.request_configurationDone() +        args_dict = {} +        if threadId is None: +            threadId = self.get_thread_id() +        args_dict['threadId'] = threadId +        command_dict = { +            'command': 'continue', +            'type': 'request', +            'arguments': args_dict +        } +        response = self.send_recv(command_dict) +        recv_packets = [] +        self.recv_condition.acquire() +        for event in self.recv_packets: +            if event['event'] != 'stopped': +                recv_packets.append(event) +        self.recv_packets = recv_packets +        self.recv_condition.release() +        return response + +    def request_disconnect(self, terminateDebuggee=None): +        args_dict = {} +        if terminateDebuggee is not None: +            if terminateDebuggee: +                args_dict['terminateDebuggee'] = True +            else: +                args_dict['terminateDebuggee'] = False +        command_dict = { +            'command': 'disconnect', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_evaluate(self, expression, frameIndex=0, threadId=None): +        stackFrame = self.get_stackFrame(frameIndex=frameIndex, +                                         threadId=threadId) +        if stackFrame is None: +            return [] +        args_dict = { +            'expression': expression, +            'frameId': stackFrame['id'], +        } +        command_dict = { +            'command': 'evaluate', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_initialize(self): +        command_dict = { +            'command': 'initialize', +            'type': 'request', +            'arguments': { +                'adapterID': 'lldb-native', +                'clientID': 'vscode', +                'columnsStartAt1': True, +                'linesStartAt1': True, +                'locale': 'en-us', +                'pathFormat': 'path', +                'supportsRunInTerminalRequest': True, +                'supportsVariablePaging': True, +                'supportsVariableType': True +            } +        } +        response = self.send_recv(command_dict) +        if response: +            if 'body' in response: +                self.initialize_body = response['body'] +        return response + +    def request_launch(self, program, args=None, cwd=None, env=None, +                       stopOnEntry=False, disableASLR=True, +                       disableSTDIO=False, shellExpandArguments=False, +                       trace=False, initCommands=None, preRunCommands=None, +                       stopCommands=None, exitCommands=None, sourcePath=None, +                       debuggerRoot=None): +        args_dict = { +            'program': program +        } +        if args: +            args_dict['args'] = args +        if cwd: +            args_dict['cwd'] = cwd +        if env: +            args_dict['env'] = env +        if stopOnEntry: +            args_dict['stopOnEntry'] = stopOnEntry +        if disableASLR: +            args_dict['disableASLR'] = disableASLR +        if disableSTDIO: +            args_dict['disableSTDIO'] = disableSTDIO +        if shellExpandArguments: +            args_dict['shellExpandArguments'] = shellExpandArguments +        if trace: +            args_dict['trace'] = trace +        if initCommands: +            args_dict['initCommands'] = initCommands +        if preRunCommands: +            args_dict['preRunCommands'] = preRunCommands +        if stopCommands: +            args_dict['stopCommands'] = stopCommands +        if exitCommands: +            args_dict['exitCommands'] = exitCommands +        if sourcePath: +            args_dict['sourcePath'] = sourcePath +        if debuggerRoot: +            args_dict['debuggerRoot'] = debuggerRoot +        command_dict = { +            'command': 'launch', +            'type': 'request', +            'arguments': args_dict +        } +        response = self.send_recv(command_dict) + +        # Wait for a 'process' and 'initialized' event in any order +        self.wait_for_event(filter=['process', 'initialized']) +        self.wait_for_event(filter=['process', 'initialized']) +        return response + +    def request_next(self, threadId): +        if self.exit_status is not None: +            raise ValueError('request_continue called after process exited') +        args_dict = {'threadId': threadId} +        command_dict = { +            'command': 'next', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_stepIn(self, threadId): +        if self.exit_status is not None: +            raise ValueError('request_continue called after process exited') +        args_dict = {'threadId': threadId} +        command_dict = { +            'command': 'stepIn', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_stepOut(self, threadId): +        if self.exit_status is not None: +            raise ValueError('request_continue called after process exited') +        args_dict = {'threadId': threadId} +        command_dict = { +            'command': 'stepOut', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_pause(self, threadId=None): +        if self.exit_status is not None: +            raise ValueError('request_continue called after process exited') +        if threadId is None: +            threadId = self.get_thread_id() +        args_dict = {'threadId': threadId} +        command_dict = { +            'command': 'pause', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_scopes(self, frameId): +        args_dict = {'frameId': frameId} +        command_dict = { +            'command': 'scopes', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_setBreakpoints(self, file_path, line_array, condition=None, +                               hitCondition=None): +        (dir, base) = os.path.split(file_path) +        breakpoints = [] +        for line in line_array: +            bp = {'line': line} +            if condition is not None: +                bp['condition'] = condition +            if hitCondition is not None: +                bp['hitCondition'] = hitCondition +            breakpoints.append(bp) +        source_dict = { +            'name': base, +            'path': file_path +        } +        args_dict = { +            'source': source_dict, +            'breakpoints': breakpoints, +            'lines': '%s' % (line_array), +            'sourceModified': False, +        } +        command_dict = { +            'command': 'setBreakpoints', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_setExceptionBreakpoints(self, filters): +        args_dict = {'filters': filters} +        command_dict = { +            'command': 'setExceptionBreakpoints', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_setFunctionBreakpoints(self, names, condition=None, +                                       hitCondition=None): +        breakpoints = [] +        for name in names: +            bp = {'name': name} +            if condition is not None: +                bp['condition'] = condition +            if hitCondition is not None: +                bp['hitCondition'] = hitCondition +            breakpoints.append(bp) +        args_dict = {'breakpoints': breakpoints} +        command_dict = { +            'command': 'setFunctionBreakpoints', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_stackTrace(self, threadId=None, startFrame=None, levels=None, +                           dump=False): +        if threadId is None: +            threadId = self.get_thread_id() +        args_dict = {'threadId': threadId} +        if startFrame is not None: +            args_dict['startFrame'] = startFrame +        if levels is not None: +            args_dict['levels'] = levels +        command_dict = { +            'command': 'stackTrace', +            'type': 'request', +            'arguments': args_dict +        } +        response = self.send_recv(command_dict) +        if dump: +            for (idx, frame) in enumerate(response['body']['stackFrames']): +                name = frame['name'] +                if 'line' in frame and 'source' in frame: +                    source = frame['source'] +                    if 'sourceReference' not in source: +                        if 'name' in source: +                            source_name = source['name'] +                            line = frame['line'] +                            print("[%3u] %s @ %s:%u" % (idx, name, source_name, +                                                        line)) +                            continue +                print("[%3u] %s" % (idx, name)) +        return response + +    def request_threads(self): +        '''Request a list of all threads and combine any information from any +           "stopped" events since those contain more information about why a +           thread actually stopped. Returns an array of thread dictionaries +           with information about all threads''' +        command_dict = { +            'command': 'threads', +            'type': 'request', +            'arguments': {} +        } +        response = self.send_recv(command_dict) +        body = response['body'] +        # Fill in "self.threads" correctly so that clients that call +        # self.get_threads() or self.get_thread_id(...) can get information +        # on threads when the process is stopped. +        if 'threads' in body: +            self.threads = body['threads'] +            for thread in self.threads: +                # Copy the thread dictionary so we can add key/value pairs to +                # it without affecfting the original info from the "threads" +                # command. +                tid = thread['id'] +                if tid in self.thread_stop_reasons: +                    thread_stop_info = self.thread_stop_reasons[tid] +                    copy_keys = ['reason', 'description', 'text'] +                    for key in copy_keys: +                        if key in thread_stop_info: +                            thread[key] = thread_stop_info[key] +        else: +            self.threads = None +        return response + +    def request_variables(self, variablesReference, start=None, count=None): +        args_dict = {'variablesReference': variablesReference} +        if start is not None: +            args_dict['start'] = start +        if count is not None: +            args_dict['count'] = count +        command_dict = { +            'command': 'variables', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_setVariable(self, containingVarRef, name, value, id=None): +        args_dict = { +            'variablesReference': containingVarRef, +            'name': name, +            'value': str(value) +        } +        if id is not None: +            args_dict['id'] = id +        command_dict = { +            'command': 'setVariable', +            'type': 'request', +            'arguments': args_dict +        } +        return self.send_recv(command_dict) + +    def request_testGetTargetBreakpoints(self): +        '''A request packet used in the LLDB test suite to get all currently +           set breakpoint infos for all breakpoints currently set in the +           target. +        ''' +        command_dict = { +            'command': '_testGetTargetBreakpoints', +            'type': 'request', +            'arguments': {} +        } +        return self.send_recv(command_dict) + +    def terminate(self): +        self.send.close() +        # self.recv.close() + + +class DebugAdaptor(DebugCommunication): +    def __init__(self, executable=None, port=None): +        self.process = None +        if executable is not None: +            self.process = subprocess.Popen([executable], +                                            stdin=subprocess.PIPE, +                                            stdout=subprocess.PIPE, +                                            stderr=subprocess.PIPE) +            DebugCommunication.__init__(self, self.process.stdout, +                                        self.process.stdin) +        elif port is not None: +            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +            s.connect(('127.0.0.1', port)) +            DebugCommunication.__init__(self, s.makefile('r'), s.makefile('w')) + +    def get_pid(self): +        if self.process: +            return self.process.pid +        return -1 + +    def terminate(self): +        super(DebugAdaptor, self).terminate() +        if self.process is not None: +            self.process.terminate() +            self.process.wait() +            self.process = None + + +def attach_options_specified(options): +    if options.pid is not None: +        return True +    if options.waitFor: +        return True +    if options.attach: +        return True +    if options.attachCmds: +        return True +    return False + + +def run_vscode(dbg, args, options): +    dbg.request_initialize() +    if attach_options_specified(options): +        response = dbg.request_attach(program=options.program, +                                      pid=options.pid, +                                      waitFor=options.waitFor, +                                      attachCommands=options.attachCmds, +                                      initCommands=options.initCmds, +                                      preRunCommands=options.preRunCmds, +                                      stopCommands=options.stopCmds, +                                      exitCommands=options.exitCmds) +    else: +        response = dbg.request_launch(options.program, +                                      args=args, +                                      env=options.envs, +                                      cwd=options.workingDir, +                                      debuggerRoot=options.debuggerRoot, +                                      sourcePath=options.sourcePath, +                                      initCommands=options.initCmds, +                                      preRunCommands=options.preRunCmds, +                                      stopCommands=options.stopCmds, +                                      exitCommands=options.exitCmds) + +    if response['success']: +        if options.sourceBreakpoints: +            source_to_lines = {} +            for file_line in options.sourceBreakpoints: +                (path, line) = file_line.split(':') +                if len(path) == 0 or len(line) == 0: +                    print('error: invalid source with line "%s"' % +                          (file_line)) + +                else: +                    if path in source_to_lines: +                        source_to_lines[path].append(int(line)) +                    else: +                        source_to_lines[path] = [int(line)] +            for source in source_to_lines: +                dbg.request_setBreakpoints(source, source_to_lines[source]) +        if options.funcBreakpoints: +            dbg.request_setFunctionBreakpoints(options.funcBreakpoints) +        dbg.request_configurationDone() +        dbg.wait_for_stopped() +    else: +        if 'message' in response: +            print(response['message']) +    dbg.request_disconnect(terminateDebuggee=True) + + +def main(): +    parser = optparse.OptionParser( +        description=('A testing framework for the Visual Studio Code Debug ' +                     'Adaptor protocol')) + +    parser.add_option( +        '--vscode', +        type='string', +        dest='vscode_path', +        help=('The path to the command line program that implements the ' +              'Visual Studio Code Debug Adaptor protocol.'), +        default=None) + +    parser.add_option( +        '--program', +        type='string', +        dest='program', +        help='The path to the program to debug.', +        default=None) + +    parser.add_option( +        '--workingDir', +        type='string', +        dest='workingDir', +        default=None, +        help='Set the working directory for the process we launch.') + +    parser.add_option( +        '--sourcePath', +        type='string', +        dest='sourcePath', +        default=None, +        help=('Set the relative source root for any debug info that has ' +              'relative paths in it.')) + +    parser.add_option( +        '--debuggerRoot', +        type='string', +        dest='debuggerRoot', +        default=None, +        help=('Set the working directory for lldb-vscode for any object files ' +              'with relative paths in the Mach-o debug map.')) + +    parser.add_option( +        '-r', '--replay', +        type='string', +        dest='replay', +        help=('Specify a file containing a packet log to replay with the ' +              'current Visual Studio Code Debug Adaptor executable.'), +        default=None) + +    parser.add_option( +        '-g', '--debug', +        action='store_true', +        dest='debug', +        default=False, +        help='Pause waiting for a debugger to attach to the debug adaptor') + +    parser.add_option( +        '--port', +        type='int', +        dest='port', +        help="Attach a socket to a port instead of using STDIN for VSCode", +        default=None) + +    parser.add_option( +        '--pid', +        type='int', +        dest='pid', +        help="The process ID to attach to", +        default=None) + +    parser.add_option( +        '--attach', +        action='store_true', +        dest='attach', +        default=False, +        help=('Specify this option to attach to a process by name. The ' +              'process name is the basanme of the executable specified with ' +              'the --program option.')) + +    parser.add_option( +        '-f', '--function-bp', +        type='string', +        action='append', +        dest='funcBreakpoints', +        help=('Specify the name of a function to break at. ' +              'Can be specified more than once.'), +        default=[]) + +    parser.add_option( +        '-s', '--source-bp', +        type='string', +        action='append', +        dest='sourceBreakpoints', +        default=[], +        help=('Specify source breakpoints to set in the format of ' +              '<source>:<line>. ' +              'Can be specified more than once.')) + +    parser.add_option( +        '--attachCommand', +        type='string', +        action='append', +        dest='attachCmds', +        default=[], +        help=('Specify a LLDB command that will attach to a process. ' +              'Can be specified more than once.')) + +    parser.add_option( +        '--initCommand', +        type='string', +        action='append', +        dest='initCmds', +        default=[], +        help=('Specify a LLDB command that will be executed before the target ' +              'is created. Can be specified more than once.')) + +    parser.add_option( +        '--preRunCommand', +        type='string', +        action='append', +        dest='preRunCmds', +        default=[], +        help=('Specify a LLDB command that will be executed after the target ' +              'has been created. Can be specified more than once.')) + +    parser.add_option( +        '--stopCommand', +        type='string', +        action='append', +        dest='stopCmds', +        default=[], +        help=('Specify a LLDB command that will be executed each time the' +              'process stops. Can be specified more than once.')) + +    parser.add_option( +        '--exitCommand', +        type='string', +        action='append', +        dest='exitCmds', +        default=[], +        help=('Specify a LLDB command that will be executed when the process ' +              'exits. Can be specified more than once.')) + +    parser.add_option( +        '--env', +        type='string', +        action='append', +        dest='envs', +        default=[], +        help=('Specify environment variables to pass to the launched ' +              'process.')) + +    parser.add_option( +        '--waitFor', +        action='store_true', +        dest='waitFor', +        default=False, +        help=('Wait for the next process to be launched whose name matches ' +              'the basename of the program specified with the --program ' +              'option')) + +    (options, args) = parser.parse_args(sys.argv[1:]) + +    if options.vscode_path is None and options.port is None: +        print('error: must either specify a path to a Visual Studio Code ' +              'Debug Adaptor vscode executable path using the --vscode ' +              'option, or a port to attach to for an existing lldb-vscode ' +              'using the --port option') +        return +    dbg = DebugAdaptor(executable=options.vscode_path, port=options.port) +    if options.debug: +        raw_input('Waiting for debugger to attach pid "%i"' % ( +                  dbg.get_pid())) +    if options.replay: +        dbg.replay_packets(options.replay) +    else: +        run_vscode(dbg, args, options) +    dbg.terminate() + + +if __name__ == '__main__': +    main()  | 
