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