1061da546Spatrick
2061da546Spatrickfrom lldbsuite.test.lldbtest import *
3061da546Spatrickimport os
4061da546Spatrickimport vscode
5dda28197Spatrickimport time
6061da546Spatrick
7061da546Spatrick
8061da546Spatrickclass VSCodeTestCaseBase(TestBase):
9061da546Spatrick
10061da546Spatrick    NO_DEBUG_INFO_TESTCASE = True
11061da546Spatrick
12be691f3bSpatrick    def create_debug_adaptor(self, lldbVSCodeEnv=None):
13061da546Spatrick        '''Create the Visual Studio Code debug adaptor'''
14*f6aab3d8Srobert        self.assertTrue(is_exe(self.lldbVSCodeExec),
15*f6aab3d8Srobert                        'lldb-vscode must exist and be executable')
16dda28197Spatrick        log_file_path = self.getBuildArtifact('vscode.txt')
17061da546Spatrick        self.vscode = vscode.DebugAdaptor(
18dda28197Spatrick            executable=self.lldbVSCodeExec, init_commands=self.setUpCommands(),
19be691f3bSpatrick            log_file=log_file_path, env=lldbVSCodeEnv)
20061da546Spatrick
21be691f3bSpatrick    def build_and_create_debug_adaptor(self, lldbVSCodeEnv=None):
22061da546Spatrick        self.build()
23be691f3bSpatrick        self.create_debug_adaptor(lldbVSCodeEnv)
24061da546Spatrick
25*f6aab3d8Srobert    def set_source_breakpoints(self, source_path, lines, data=None):
26061da546Spatrick        '''Sets source breakpoints and returns an array of strings containing
27dda28197Spatrick           the breakpoint IDs ("1", "2") for each breakpoint that was set.
28*f6aab3d8Srobert           Parameter data is array of data objects for breakpoints.
29*f6aab3d8Srobert           Each object in data is 1:1 mapping with the entry in lines.
30*f6aab3d8Srobert           It contains optional location/hitCondition/logMessage parameters.
31061da546Spatrick        '''
32061da546Spatrick        response = self.vscode.request_setBreakpoints(
33*f6aab3d8Srobert            source_path, lines, data)
34061da546Spatrick        if response is None:
35061da546Spatrick            return []
36061da546Spatrick        breakpoints = response['body']['breakpoints']
37061da546Spatrick        breakpoint_ids = []
38061da546Spatrick        for breakpoint in breakpoints:
39dda28197Spatrick            breakpoint_ids.append('%i' % (breakpoint['id']))
40061da546Spatrick        return breakpoint_ids
41061da546Spatrick
42061da546Spatrick    def set_function_breakpoints(self, functions, condition=None,
43061da546Spatrick                                 hitCondition=None):
44061da546Spatrick        '''Sets breakpoints by function name given an array of function names
45dda28197Spatrick           and returns an array of strings containing the breakpoint IDs
46dda28197Spatrick           ("1", "2") for each breakpoint that was set.
47061da546Spatrick        '''
48061da546Spatrick        response = self.vscode.request_setFunctionBreakpoints(
49061da546Spatrick            functions, condition=condition, hitCondition=hitCondition)
50061da546Spatrick        if response is None:
51061da546Spatrick            return []
52061da546Spatrick        breakpoints = response['body']['breakpoints']
53061da546Spatrick        breakpoint_ids = []
54061da546Spatrick        for breakpoint in breakpoints:
55dda28197Spatrick            breakpoint_ids.append('%i' % (breakpoint['id']))
56061da546Spatrick        return breakpoint_ids
57061da546Spatrick
58be691f3bSpatrick    def waitUntil(self, condition_callback):
59be691f3bSpatrick        for _ in range(20):
60be691f3bSpatrick            if condition_callback():
61be691f3bSpatrick                return True
62dda28197Spatrick            time.sleep(0.5)
63be691f3bSpatrick        return False
64dda28197Spatrick
65061da546Spatrick    def verify_breakpoint_hit(self, breakpoint_ids):
66061da546Spatrick        '''Wait for the process we are debugging to stop, and verify we hit
67061da546Spatrick           any breakpoint location in the "breakpoint_ids" array.
68dda28197Spatrick           "breakpoint_ids" should be a list of breakpoint ID strings
69dda28197Spatrick           (["1", "2"]). The return value from self.set_source_breakpoints()
70dda28197Spatrick           or self.set_function_breakpoints() can be passed to this function'''
71061da546Spatrick        stopped_events = self.vscode.wait_for_stopped()
72061da546Spatrick        for stopped_event in stopped_events:
73061da546Spatrick            if 'body' in stopped_event:
74061da546Spatrick                body = stopped_event['body']
75061da546Spatrick                if 'reason' not in body:
76061da546Spatrick                    continue
77061da546Spatrick                if body['reason'] != 'breakpoint':
78061da546Spatrick                    continue
79061da546Spatrick                if 'description' not in body:
80061da546Spatrick                    continue
81dda28197Spatrick                # Descriptions for breakpoints will be in the form
82dda28197Spatrick                # "breakpoint 1.1", so look for any description that matches
83dda28197Spatrick                # ("breakpoint 1.") in the description field as verification
84dda28197Spatrick                # that one of the breakpoint locations was hit. VSCode doesn't
85dda28197Spatrick                # allow breakpoints to have multiple locations, but LLDB does.
86dda28197Spatrick                # So when looking at the description we just want to make sure
87dda28197Spatrick                # the right breakpoint matches and not worry about the actual
88dda28197Spatrick                # location.
89061da546Spatrick                description = body['description']
90061da546Spatrick                for breakpoint_id in breakpoint_ids:
91dda28197Spatrick                    match_desc = 'breakpoint %s.' % (breakpoint_id)
92dda28197Spatrick                    if match_desc in description:
93dda28197Spatrick                        return
94dda28197Spatrick        self.assertTrue(False, "breakpoint not hit")
95061da546Spatrick
96*f6aab3d8Srobert    def verify_stop_exception_info(self, expected_description):
97061da546Spatrick        '''Wait for the process we are debugging to stop, and verify the stop
98061da546Spatrick           reason is 'exception' and that the description matches
99*f6aab3d8Srobert           'expected_description'
100061da546Spatrick        '''
101061da546Spatrick        stopped_events = self.vscode.wait_for_stopped()
102061da546Spatrick        for stopped_event in stopped_events:
103061da546Spatrick            if 'body' in stopped_event:
104061da546Spatrick                body = stopped_event['body']
105061da546Spatrick                if 'reason' not in body:
106061da546Spatrick                    continue
107061da546Spatrick                if body['reason'] != 'exception':
108061da546Spatrick                    continue
109061da546Spatrick                if 'description' not in body:
110061da546Spatrick                    continue
111061da546Spatrick                description = body['description']
112*f6aab3d8Srobert                if expected_description == description:
113061da546Spatrick                    return True
114061da546Spatrick        return False
115061da546Spatrick
116061da546Spatrick    def verify_commands(self, flavor, output, commands):
117061da546Spatrick        self.assertTrue(output and len(output) > 0, "expect console output")
118061da546Spatrick        lines = output.splitlines()
119061da546Spatrick        prefix = '(lldb) '
120061da546Spatrick        for cmd in commands:
121061da546Spatrick            found = False
122061da546Spatrick            for line in lines:
123061da546Spatrick                if line.startswith(prefix) and cmd in line:
124061da546Spatrick                    found = True
125061da546Spatrick                    break
126061da546Spatrick            self.assertTrue(found,
127061da546Spatrick                            "verify '%s' found in console output for '%s'" % (
128061da546Spatrick                                cmd, flavor))
129061da546Spatrick
130061da546Spatrick    def get_dict_value(self, d, key_path):
131061da546Spatrick        '''Verify each key in the key_path array is in contained in each
132061da546Spatrick           dictionary within "d". Assert if any key isn't in the
133061da546Spatrick           corresponding dictionary. This is handy for grabbing values from VS
134061da546Spatrick           Code response dictionary like getting
135061da546Spatrick           response['body']['stackFrames']
136061da546Spatrick        '''
137061da546Spatrick        value = d
138061da546Spatrick        for key in key_path:
139061da546Spatrick            if key in value:
140061da546Spatrick                value = value[key]
141061da546Spatrick            else:
142061da546Spatrick                self.assertTrue(key in value,
143061da546Spatrick                                'key "%s" from key_path "%s" not in "%s"' % (
144061da546Spatrick                                    key, key_path, d))
145061da546Spatrick        return value
146061da546Spatrick
147061da546Spatrick    def get_stackFrames_and_totalFramesCount(self, threadId=None, startFrame=None,
148061da546Spatrick                        levels=None, dump=False):
149061da546Spatrick        response = self.vscode.request_stackTrace(threadId=threadId,
150061da546Spatrick                                                  startFrame=startFrame,
151061da546Spatrick                                                  levels=levels,
152061da546Spatrick                                                  dump=dump)
153061da546Spatrick        if response:
154061da546Spatrick            stackFrames = self.get_dict_value(response, ['body', 'stackFrames'])
155061da546Spatrick            totalFrames = self.get_dict_value(response, ['body', 'totalFrames'])
156061da546Spatrick            self.assertTrue(totalFrames > 0,
157061da546Spatrick                    'verify totalFrames count is provided by extension that supports '
158061da546Spatrick                    'async frames loading')
159061da546Spatrick            return (stackFrames, totalFrames)
160061da546Spatrick        return (None, 0)
161061da546Spatrick
162061da546Spatrick    def get_stackFrames(self, threadId=None, startFrame=None, levels=None,
163061da546Spatrick                        dump=False):
164061da546Spatrick        (stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount(
165061da546Spatrick                                                threadId=threadId,
166061da546Spatrick                                                startFrame=startFrame,
167061da546Spatrick                                                levels=levels,
168061da546Spatrick                                                dump=dump)
169061da546Spatrick        return stackFrames
170061da546Spatrick
171061da546Spatrick    def get_source_and_line(self, threadId=None, frameIndex=0):
172061da546Spatrick        stackFrames = self.get_stackFrames(threadId=threadId,
173061da546Spatrick                                           startFrame=frameIndex,
174061da546Spatrick                                           levels=1)
175061da546Spatrick        if stackFrames is not None:
176061da546Spatrick            stackFrame = stackFrames[0]
177061da546Spatrick            ['source', 'path']
178061da546Spatrick            if 'source' in stackFrame:
179061da546Spatrick                source = stackFrame['source']
180061da546Spatrick                if 'path' in source:
181061da546Spatrick                    if 'line' in stackFrame:
182061da546Spatrick                        return (source['path'], stackFrame['line'])
183061da546Spatrick        return ('', 0)
184061da546Spatrick
185061da546Spatrick    def get_stdout(self, timeout=0.0):
186061da546Spatrick        return self.vscode.get_output('stdout', timeout=timeout)
187061da546Spatrick
188061da546Spatrick    def get_console(self, timeout=0.0):
189061da546Spatrick        return self.vscode.get_output('console', timeout=timeout)
190061da546Spatrick
191dda28197Spatrick    def collect_console(self, duration):
192dda28197Spatrick        return self.vscode.collect_output('console', duration=duration)
193dda28197Spatrick
194061da546Spatrick    def get_local_as_int(self, name, threadId=None):
195061da546Spatrick        value = self.vscode.get_local_variable_value(name, threadId=threadId)
196061da546Spatrick        if value.startswith('0x'):
197061da546Spatrick            return int(value, 16)
198061da546Spatrick        elif value.startswith('0'):
199061da546Spatrick            return int(value, 8)
200061da546Spatrick        else:
201061da546Spatrick            return int(value)
202061da546Spatrick
203061da546Spatrick    def set_local(self, name, value, id=None):
204061da546Spatrick        '''Set a top level local variable only.'''
205061da546Spatrick        return self.vscode.request_setVariable(1, name, str(value), id=id)
206061da546Spatrick
207061da546Spatrick    def set_global(self, name, value, id=None):
208061da546Spatrick        '''Set a top level global variable only.'''
209061da546Spatrick        return self.vscode.request_setVariable(2, name, str(value), id=id)
210061da546Spatrick
211061da546Spatrick    def stepIn(self, threadId=None, waitForStop=True):
212061da546Spatrick        self.vscode.request_stepIn(threadId=threadId)
213061da546Spatrick        if waitForStop:
214061da546Spatrick            return self.vscode.wait_for_stopped()
215061da546Spatrick        return None
216061da546Spatrick
217061da546Spatrick    def stepOver(self, threadId=None, waitForStop=True):
218061da546Spatrick        self.vscode.request_next(threadId=threadId)
219061da546Spatrick        if waitForStop:
220061da546Spatrick            return self.vscode.wait_for_stopped()
221061da546Spatrick        return None
222061da546Spatrick
223061da546Spatrick    def stepOut(self, threadId=None, waitForStop=True):
224061da546Spatrick        self.vscode.request_stepOut(threadId=threadId)
225061da546Spatrick        if waitForStop:
226061da546Spatrick            return self.vscode.wait_for_stopped()
227061da546Spatrick        return None
228061da546Spatrick
229061da546Spatrick    def continue_to_next_stop(self):
230061da546Spatrick        self.vscode.request_continue()
231061da546Spatrick        return self.vscode.wait_for_stopped()
232061da546Spatrick
233061da546Spatrick    def continue_to_breakpoints(self, breakpoint_ids):
234061da546Spatrick        self.vscode.request_continue()
235061da546Spatrick        self.verify_breakpoint_hit(breakpoint_ids)
236061da546Spatrick
237061da546Spatrick    def continue_to_exception_breakpoint(self, filter_label):
238061da546Spatrick        self.vscode.request_continue()
239*f6aab3d8Srobert        self.assertTrue(self.verify_stop_exception_info(filter_label),
240061da546Spatrick                        'verify we got "%s"' % (filter_label))
241061da546Spatrick
242061da546Spatrick    def continue_to_exit(self, exitCode=0):
243061da546Spatrick        self.vscode.request_continue()
244061da546Spatrick        stopped_events = self.vscode.wait_for_stopped()
245dda28197Spatrick        self.assertEquals(len(stopped_events), 1,
246dda28197Spatrick                        "stopped_events = {}".format(stopped_events))
247dda28197Spatrick        self.assertEquals(stopped_events[0]['event'], 'exited',
248061da546Spatrick                        'make sure program ran to completion')
249dda28197Spatrick        self.assertEquals(stopped_events[0]['body']['exitCode'], exitCode,
250061da546Spatrick                        'exitCode == %i' % (exitCode))
251061da546Spatrick
252061da546Spatrick    def attach(self, program=None, pid=None, waitFor=None, trace=None,
253061da546Spatrick               initCommands=None, preRunCommands=None, stopCommands=None,
254dda28197Spatrick               exitCommands=None, attachCommands=None, coreFile=None,
255be691f3bSpatrick               disconnectAutomatically=True, terminateCommands=None,
256*f6aab3d8Srobert               postRunCommands=None, sourceMap=None, sourceInitFile=False):
257061da546Spatrick        '''Build the default Makefile target, create the VSCode debug adaptor,
258061da546Spatrick           and attach to the process.
259061da546Spatrick        '''
260061da546Spatrick        # Make sure we disconnect and terminate the VSCode debug adaptor even
261061da546Spatrick        # if we throw an exception during the test case.
262061da546Spatrick        def cleanup():
263dda28197Spatrick            if disconnectAutomatically:
264061da546Spatrick                self.vscode.request_disconnect(terminateDebuggee=True)
265061da546Spatrick            self.vscode.terminate()
266061da546Spatrick
267061da546Spatrick        # Execute the cleanup function during test case tear down.
268061da546Spatrick        self.addTearDownHook(cleanup)
269061da546Spatrick        # Initialize and launch the program
270*f6aab3d8Srobert        self.vscode.request_initialize(sourceInitFile)
271061da546Spatrick        response = self.vscode.request_attach(
272061da546Spatrick            program=program, pid=pid, waitFor=waitFor, trace=trace,
273061da546Spatrick            initCommands=initCommands, preRunCommands=preRunCommands,
274061da546Spatrick            stopCommands=stopCommands, exitCommands=exitCommands,
275dda28197Spatrick            attachCommands=attachCommands, terminateCommands=terminateCommands,
276*f6aab3d8Srobert            coreFile=coreFile, postRunCommands=postRunCommands,
277*f6aab3d8Srobert            sourceMap=sourceMap)
278061da546Spatrick        if not (response and response['success']):
279061da546Spatrick            self.assertTrue(response['success'],
280061da546Spatrick                            'attach failed (%s)' % (response['message']))
281061da546Spatrick
282061da546Spatrick    def launch(self, program=None, args=None, cwd=None, env=None,
283061da546Spatrick               stopOnEntry=False, disableASLR=True,
284061da546Spatrick               disableSTDIO=False, shellExpandArguments=False,
285061da546Spatrick               trace=False, initCommands=None, preRunCommands=None,
286dda28197Spatrick               stopCommands=None, exitCommands=None, terminateCommands=None,
287*f6aab3d8Srobert               sourcePath=None, debuggerRoot=None, sourceInitFile=False, launchCommands=None,
288be691f3bSpatrick               sourceMap=None, disconnectAutomatically=True, runInTerminal=False,
289be691f3bSpatrick               expectFailure=False, postRunCommands=None):
290061da546Spatrick        '''Sending launch request to vscode
291061da546Spatrick        '''
292061da546Spatrick
293061da546Spatrick        # Make sure we disconnect and terminate the VSCode debug adapter,
294061da546Spatrick        # if we throw an exception during the test case
295061da546Spatrick        def cleanup():
296dda28197Spatrick            if disconnectAutomatically:
297061da546Spatrick                self.vscode.request_disconnect(terminateDebuggee=True)
298061da546Spatrick            self.vscode.terminate()
299061da546Spatrick
300061da546Spatrick        # Execute the cleanup function during test case tear down.
301061da546Spatrick        self.addTearDownHook(cleanup)
302061da546Spatrick
303061da546Spatrick        # Initialize and launch the program
304*f6aab3d8Srobert        self.vscode.request_initialize(sourceInitFile)
305061da546Spatrick        response = self.vscode.request_launch(
306061da546Spatrick            program,
307061da546Spatrick            args=args,
308061da546Spatrick            cwd=cwd,
309061da546Spatrick            env=env,
310061da546Spatrick            stopOnEntry=stopOnEntry,
311061da546Spatrick            disableASLR=disableASLR,
312061da546Spatrick            disableSTDIO=disableSTDIO,
313061da546Spatrick            shellExpandArguments=shellExpandArguments,
314061da546Spatrick            trace=trace,
315061da546Spatrick            initCommands=initCommands,
316061da546Spatrick            preRunCommands=preRunCommands,
317061da546Spatrick            stopCommands=stopCommands,
318061da546Spatrick            exitCommands=exitCommands,
319dda28197Spatrick            terminateCommands=terminateCommands,
320061da546Spatrick            sourcePath=sourcePath,
321061da546Spatrick            debuggerRoot=debuggerRoot,
322dda28197Spatrick            launchCommands=launchCommands,
323be691f3bSpatrick            sourceMap=sourceMap,
324be691f3bSpatrick            runInTerminal=runInTerminal,
325be691f3bSpatrick            expectFailure=expectFailure,
326be691f3bSpatrick            postRunCommands=postRunCommands)
327be691f3bSpatrick
328be691f3bSpatrick        if expectFailure:
329be691f3bSpatrick            return response
330be691f3bSpatrick
331061da546Spatrick        if not (response and response['success']):
332061da546Spatrick            self.assertTrue(response['success'],
333061da546Spatrick                            'launch failed (%s)' % (response['message']))
334be691f3bSpatrick        # We need to trigger a request_configurationDone after we've successfully
335be691f3bSpatrick        # attached a runInTerminal process to finish initialization.
336be691f3bSpatrick        if runInTerminal:
337be691f3bSpatrick            self.vscode.request_configurationDone()
338be691f3bSpatrick        return response
339be691f3bSpatrick
340061da546Spatrick
341061da546Spatrick    def build_and_launch(self, program, args=None, cwd=None, env=None,
342061da546Spatrick                         stopOnEntry=False, disableASLR=True,
343061da546Spatrick                         disableSTDIO=False, shellExpandArguments=False,
344061da546Spatrick                         trace=False, initCommands=None, preRunCommands=None,
345061da546Spatrick                         stopCommands=None, exitCommands=None,
346dda28197Spatrick                         terminateCommands=None, sourcePath=None,
347*f6aab3d8Srobert                         debuggerRoot=None, sourceInitFile=False, runInTerminal=False,
348be691f3bSpatrick                         disconnectAutomatically=True, postRunCommands=None,
349be691f3bSpatrick                         lldbVSCodeEnv=None):
350061da546Spatrick        '''Build the default Makefile target, create the VSCode debug adaptor,
351061da546Spatrick           and launch the process.
352061da546Spatrick        '''
353be691f3bSpatrick        self.build_and_create_debug_adaptor(lldbVSCodeEnv)
354061da546Spatrick        self.assertTrue(os.path.exists(program), 'executable must exist')
355061da546Spatrick
356be691f3bSpatrick        return self.launch(program, args, cwd, env, stopOnEntry, disableASLR,
357061da546Spatrick                    disableSTDIO, shellExpandArguments, trace,
358061da546Spatrick                    initCommands, preRunCommands, stopCommands, exitCommands,
359*f6aab3d8Srobert                    terminateCommands, sourcePath, debuggerRoot, sourceInitFile,
360*f6aab3d8Srobert                    runInTerminal=runInTerminal,
361be691f3bSpatrick                    disconnectAutomatically=disconnectAutomatically,
362be691f3bSpatrick                    postRunCommands=postRunCommands)
363