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