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