xref: /openbsd-src/gnu/llvm/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1
2from lldbsuite.test.lldbtest import *
3import os
4import vscode
5import time
6
7
8class VSCodeTestCaseBase(TestBase):
9
10    NO_DEBUG_INFO_TESTCASE = True
11
12    def create_debug_adaptor(self, lldbVSCodeEnv=None):
13        '''Create the Visual Studio Code debug adaptor'''
14        self.assertTrue(is_exe(self.lldbVSCodeExec),
15                        'lldb-vscode must exist and be executable')
16        log_file_path = self.getBuildArtifact('vscode.txt')
17        self.vscode = vscode.DebugAdaptor(
18            executable=self.lldbVSCodeExec, init_commands=self.setUpCommands(),
19            log_file=log_file_path, env=lldbVSCodeEnv)
20
21    def build_and_create_debug_adaptor(self, lldbVSCodeEnv=None):
22        self.build()
23        self.create_debug_adaptor(lldbVSCodeEnv)
24
25    def set_source_breakpoints(self, source_path, lines, data=None):
26        '''Sets source breakpoints and returns an array of strings containing
27           the breakpoint IDs ("1", "2") for each breakpoint that was set.
28           Parameter data is array of data objects for breakpoints.
29           Each object in data is 1:1 mapping with the entry in lines.
30           It contains optional location/hitCondition/logMessage parameters.
31        '''
32        response = self.vscode.request_setBreakpoints(
33            source_path, lines, data)
34        if response is None:
35            return []
36        breakpoints = response['body']['breakpoints']
37        breakpoint_ids = []
38        for breakpoint in breakpoints:
39            breakpoint_ids.append('%i' % (breakpoint['id']))
40        return breakpoint_ids
41
42    def set_function_breakpoints(self, functions, condition=None,
43                                 hitCondition=None):
44        '''Sets breakpoints by function name given an array of function names
45           and returns an array of strings containing the breakpoint IDs
46           ("1", "2") for each breakpoint that was set.
47        '''
48        response = self.vscode.request_setFunctionBreakpoints(
49            functions, condition=condition, hitCondition=hitCondition)
50        if response is None:
51            return []
52        breakpoints = response['body']['breakpoints']
53        breakpoint_ids = []
54        for breakpoint in breakpoints:
55            breakpoint_ids.append('%i' % (breakpoint['id']))
56        return breakpoint_ids
57
58    def waitUntil(self, condition_callback):
59        for _ in range(20):
60            if condition_callback():
61                return True
62            time.sleep(0.5)
63        return False
64
65    def verify_breakpoint_hit(self, breakpoint_ids):
66        '''Wait for the process we are debugging to stop, and verify we hit
67           any breakpoint location in the "breakpoint_ids" array.
68           "breakpoint_ids" should be a list of breakpoint ID strings
69           (["1", "2"]). The return value from self.set_source_breakpoints()
70           or self.set_function_breakpoints() can be passed to this function'''
71        stopped_events = self.vscode.wait_for_stopped()
72        for stopped_event in stopped_events:
73            if 'body' in stopped_event:
74                body = stopped_event['body']
75                if 'reason' not in body:
76                    continue
77                if body['reason'] != 'breakpoint':
78                    continue
79                if 'description' not in body:
80                    continue
81                # Descriptions for breakpoints will be in the form
82                # "breakpoint 1.1", so look for any description that matches
83                # ("breakpoint 1.") in the description field as verification
84                # that one of the breakpoint locations was hit. VSCode doesn't
85                # allow breakpoints to have multiple locations, but LLDB does.
86                # So when looking at the description we just want to make sure
87                # the right breakpoint matches and not worry about the actual
88                # location.
89                description = body['description']
90                for breakpoint_id in breakpoint_ids:
91                    match_desc = 'breakpoint %s.' % (breakpoint_id)
92                    if match_desc in description:
93                        return
94        self.assertTrue(False, "breakpoint not hit")
95
96    def verify_stop_exception_info(self, expected_description):
97        '''Wait for the process we are debugging to stop, and verify the stop
98           reason is 'exception' and that the description matches
99           'expected_description'
100        '''
101        stopped_events = self.vscode.wait_for_stopped()
102        for stopped_event in stopped_events:
103            if 'body' in stopped_event:
104                body = stopped_event['body']
105                if 'reason' not in body:
106                    continue
107                if body['reason'] != 'exception':
108                    continue
109                if 'description' not in body:
110                    continue
111                description = body['description']
112                if expected_description == description:
113                    return True
114        return False
115
116    def verify_commands(self, flavor, output, commands):
117        self.assertTrue(output and len(output) > 0, "expect console output")
118        lines = output.splitlines()
119        prefix = '(lldb) '
120        for cmd in commands:
121            found = False
122            for line in lines:
123                if line.startswith(prefix) and cmd in line:
124                    found = True
125                    break
126            self.assertTrue(found,
127                            "verify '%s' found in console output for '%s'" % (
128                                cmd, flavor))
129
130    def get_dict_value(self, d, key_path):
131        '''Verify each key in the key_path array is in contained in each
132           dictionary within "d". Assert if any key isn't in the
133           corresponding dictionary. This is handy for grabbing values from VS
134           Code response dictionary like getting
135           response['body']['stackFrames']
136        '''
137        value = d
138        for key in key_path:
139            if key in value:
140                value = value[key]
141            else:
142                self.assertTrue(key in value,
143                                'key "%s" from key_path "%s" not in "%s"' % (
144                                    key, key_path, d))
145        return value
146
147    def get_stackFrames_and_totalFramesCount(self, threadId=None, startFrame=None,
148                        levels=None, dump=False):
149        response = self.vscode.request_stackTrace(threadId=threadId,
150                                                  startFrame=startFrame,
151                                                  levels=levels,
152                                                  dump=dump)
153        if response:
154            stackFrames = self.get_dict_value(response, ['body', 'stackFrames'])
155            totalFrames = self.get_dict_value(response, ['body', 'totalFrames'])
156            self.assertTrue(totalFrames > 0,
157                    'verify totalFrames count is provided by extension that supports '
158                    'async frames loading')
159            return (stackFrames, totalFrames)
160        return (None, 0)
161
162    def get_stackFrames(self, threadId=None, startFrame=None, levels=None,
163                        dump=False):
164        (stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount(
165                                                threadId=threadId,
166                                                startFrame=startFrame,
167                                                levels=levels,
168                                                dump=dump)
169        return stackFrames
170
171    def get_source_and_line(self, threadId=None, frameIndex=0):
172        stackFrames = self.get_stackFrames(threadId=threadId,
173                                           startFrame=frameIndex,
174                                           levels=1)
175        if stackFrames is not None:
176            stackFrame = stackFrames[0]
177            ['source', 'path']
178            if 'source' in stackFrame:
179                source = stackFrame['source']
180                if 'path' in source:
181                    if 'line' in stackFrame:
182                        return (source['path'], stackFrame['line'])
183        return ('', 0)
184
185    def get_stdout(self, timeout=0.0):
186        return self.vscode.get_output('stdout', timeout=timeout)
187
188    def get_console(self, timeout=0.0):
189        return self.vscode.get_output('console', timeout=timeout)
190
191    def collect_console(self, duration):
192        return self.vscode.collect_output('console', duration=duration)
193
194    def get_local_as_int(self, name, threadId=None):
195        value = self.vscode.get_local_variable_value(name, threadId=threadId)
196        if value.startswith('0x'):
197            return int(value, 16)
198        elif value.startswith('0'):
199            return int(value, 8)
200        else:
201            return int(value)
202
203    def set_local(self, name, value, id=None):
204        '''Set a top level local variable only.'''
205        return self.vscode.request_setVariable(1, name, str(value), id=id)
206
207    def set_global(self, name, value, id=None):
208        '''Set a top level global variable only.'''
209        return self.vscode.request_setVariable(2, name, str(value), id=id)
210
211    def stepIn(self, threadId=None, waitForStop=True):
212        self.vscode.request_stepIn(threadId=threadId)
213        if waitForStop:
214            return self.vscode.wait_for_stopped()
215        return None
216
217    def stepOver(self, threadId=None, waitForStop=True):
218        self.vscode.request_next(threadId=threadId)
219        if waitForStop:
220            return self.vscode.wait_for_stopped()
221        return None
222
223    def stepOut(self, threadId=None, waitForStop=True):
224        self.vscode.request_stepOut(threadId=threadId)
225        if waitForStop:
226            return self.vscode.wait_for_stopped()
227        return None
228
229    def continue_to_next_stop(self):
230        self.vscode.request_continue()
231        return self.vscode.wait_for_stopped()
232
233    def continue_to_breakpoints(self, breakpoint_ids):
234        self.vscode.request_continue()
235        self.verify_breakpoint_hit(breakpoint_ids)
236
237    def continue_to_exception_breakpoint(self, filter_label):
238        self.vscode.request_continue()
239        self.assertTrue(self.verify_stop_exception_info(filter_label),
240                        'verify we got "%s"' % (filter_label))
241
242    def continue_to_exit(self, exitCode=0):
243        self.vscode.request_continue()
244        stopped_events = self.vscode.wait_for_stopped()
245        self.assertEquals(len(stopped_events), 1,
246                        "stopped_events = {}".format(stopped_events))
247        self.assertEquals(stopped_events[0]['event'], 'exited',
248                        'make sure program ran to completion')
249        self.assertEquals(stopped_events[0]['body']['exitCode'], exitCode,
250                        'exitCode == %i' % (exitCode))
251
252    def attach(self, program=None, pid=None, waitFor=None, trace=None,
253               initCommands=None, preRunCommands=None, stopCommands=None,
254               exitCommands=None, attachCommands=None, coreFile=None,
255               disconnectAutomatically=True, terminateCommands=None,
256               postRunCommands=None, sourceMap=None, sourceInitFile=False):
257        '''Build the default Makefile target, create the VSCode debug adaptor,
258           and attach to the process.
259        '''
260        # Make sure we disconnect and terminate the VSCode debug adaptor even
261        # if we throw an exception during the test case.
262        def cleanup():
263            if disconnectAutomatically:
264                self.vscode.request_disconnect(terminateDebuggee=True)
265            self.vscode.terminate()
266
267        # Execute the cleanup function during test case tear down.
268        self.addTearDownHook(cleanup)
269        # Initialize and launch the program
270        self.vscode.request_initialize(sourceInitFile)
271        response = self.vscode.request_attach(
272            program=program, pid=pid, waitFor=waitFor, trace=trace,
273            initCommands=initCommands, preRunCommands=preRunCommands,
274            stopCommands=stopCommands, exitCommands=exitCommands,
275            attachCommands=attachCommands, terminateCommands=terminateCommands,
276            coreFile=coreFile, postRunCommands=postRunCommands,
277            sourceMap=sourceMap)
278        if not (response and response['success']):
279            self.assertTrue(response['success'],
280                            'attach failed (%s)' % (response['message']))
281
282    def launch(self, program=None, args=None, cwd=None, env=None,
283               stopOnEntry=False, disableASLR=True,
284               disableSTDIO=False, shellExpandArguments=False,
285               trace=False, initCommands=None, preRunCommands=None,
286               stopCommands=None, exitCommands=None, terminateCommands=None,
287               sourcePath=None, debuggerRoot=None, sourceInitFile=False, launchCommands=None,
288               sourceMap=None, disconnectAutomatically=True, runInTerminal=False,
289               expectFailure=False, postRunCommands=None):
290        '''Sending launch request to vscode
291        '''
292
293        # Make sure we disconnect and terminate the VSCode debug adapter,
294        # if we throw an exception during the test case
295        def cleanup():
296            if disconnectAutomatically:
297                self.vscode.request_disconnect(terminateDebuggee=True)
298            self.vscode.terminate()
299
300        # Execute the cleanup function during test case tear down.
301        self.addTearDownHook(cleanup)
302
303        # Initialize and launch the program
304        self.vscode.request_initialize(sourceInitFile)
305        response = self.vscode.request_launch(
306            program,
307            args=args,
308            cwd=cwd,
309            env=env,
310            stopOnEntry=stopOnEntry,
311            disableASLR=disableASLR,
312            disableSTDIO=disableSTDIO,
313            shellExpandArguments=shellExpandArguments,
314            trace=trace,
315            initCommands=initCommands,
316            preRunCommands=preRunCommands,
317            stopCommands=stopCommands,
318            exitCommands=exitCommands,
319            terminateCommands=terminateCommands,
320            sourcePath=sourcePath,
321            debuggerRoot=debuggerRoot,
322            launchCommands=launchCommands,
323            sourceMap=sourceMap,
324            runInTerminal=runInTerminal,
325            expectFailure=expectFailure,
326            postRunCommands=postRunCommands)
327
328        if expectFailure:
329            return response
330
331        if not (response and response['success']):
332            self.assertTrue(response['success'],
333                            'launch failed (%s)' % (response['message']))
334        # We need to trigger a request_configurationDone after we've successfully
335        # attached a runInTerminal process to finish initialization.
336        if runInTerminal:
337            self.vscode.request_configurationDone()
338        return response
339
340
341    def build_and_launch(self, program, args=None, cwd=None, env=None,
342                         stopOnEntry=False, disableASLR=True,
343                         disableSTDIO=False, shellExpandArguments=False,
344                         trace=False, initCommands=None, preRunCommands=None,
345                         stopCommands=None, exitCommands=None,
346                         terminateCommands=None, sourcePath=None,
347                         debuggerRoot=None, sourceInitFile=False, runInTerminal=False,
348                         disconnectAutomatically=True, postRunCommands=None,
349                         lldbVSCodeEnv=None):
350        '''Build the default Makefile target, create the VSCode debug adaptor,
351           and launch the process.
352        '''
353        self.build_and_create_debug_adaptor(lldbVSCodeEnv)
354        self.assertTrue(os.path.exists(program), 'executable must exist')
355
356        return self.launch(program, args, cwd, env, stopOnEntry, disableASLR,
357                    disableSTDIO, shellExpandArguments, trace,
358                    initCommands, preRunCommands, stopCommands, exitCommands,
359                    terminateCommands, sourcePath, debuggerRoot, sourceInitFile,
360                    runInTerminal=runInTerminal,
361                    disconnectAutomatically=disconnectAutomatically,
362                    postRunCommands=postRunCommands)
363