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 get_local_as_int(self, name, threadId=None): 183 value = self.vscode.get_local_variable_value(name, threadId=threadId) 184 if value.startswith('0x'): 185 return int(value, 16) 186 elif value.startswith('0'): 187 return int(value, 8) 188 else: 189 return int(value) 190 191 def set_local(self, name, value, id=None): 192 '''Set a top level local variable only.''' 193 return self.vscode.request_setVariable(1, name, str(value), id=id) 194 195 def set_global(self, name, value, id=None): 196 '''Set a top level global variable only.''' 197 return self.vscode.request_setVariable(2, name, str(value), id=id) 198 199 def stepIn(self, threadId=None, waitForStop=True): 200 self.vscode.request_stepIn(threadId=threadId) 201 if waitForStop: 202 return self.vscode.wait_for_stopped() 203 return None 204 205 def stepOver(self, threadId=None, waitForStop=True): 206 self.vscode.request_next(threadId=threadId) 207 if waitForStop: 208 return self.vscode.wait_for_stopped() 209 return None 210 211 def stepOut(self, threadId=None, waitForStop=True): 212 self.vscode.request_stepOut(threadId=threadId) 213 if waitForStop: 214 return self.vscode.wait_for_stopped() 215 return None 216 217 def continue_to_next_stop(self): 218 self.vscode.request_continue() 219 return self.vscode.wait_for_stopped() 220 221 def continue_to_breakpoints(self, breakpoint_ids): 222 self.vscode.request_continue() 223 self.verify_breakpoint_hit(breakpoint_ids) 224 225 def continue_to_exception_breakpoint(self, filter_label): 226 self.vscode.request_continue() 227 self.assertTrue(self.verify_exception_breakpoint_hit(filter_label), 228 'verify we got "%s"' % (filter_label)) 229 230 def continue_to_exit(self, exitCode=0): 231 self.vscode.request_continue() 232 stopped_events = self.vscode.wait_for_stopped() 233 self.assertEquals(len(stopped_events), 1, 234 "stopped_events = {}".format(stopped_events)) 235 self.assertEquals(stopped_events[0]['event'], 'exited', 236 'make sure program ran to completion') 237 self.assertEquals(stopped_events[0]['body']['exitCode'], exitCode, 238 'exitCode == %i' % (exitCode)) 239 240 def attach(self, program=None, pid=None, waitFor=None, trace=None, 241 initCommands=None, preRunCommands=None, stopCommands=None, 242 exitCommands=None, attachCommands=None): 243 '''Build the default Makefile target, create the VSCode debug adaptor, 244 and attach to the process. 245 ''' 246 # Make sure we disconnect and terminate the VSCode debug adaptor even 247 # if we throw an exception during the test case. 248 def cleanup(): 249 self.vscode.request_disconnect(terminateDebuggee=True) 250 self.vscode.terminate() 251 252 # Execute the cleanup function during test case tear down. 253 self.addTearDownHook(cleanup) 254 # Initialize and launch the program 255 self.vscode.request_initialize() 256 response = self.vscode.request_attach( 257 program=program, pid=pid, waitFor=waitFor, trace=trace, 258 initCommands=initCommands, preRunCommands=preRunCommands, 259 stopCommands=stopCommands, exitCommands=exitCommands, 260 attachCommands=attachCommands) 261 if not (response and response['success']): 262 self.assertTrue(response['success'], 263 'attach failed (%s)' % (response['message'])) 264 265 def launch(self, program=None, args=None, cwd=None, env=None, 266 stopOnEntry=False, disableASLR=True, 267 disableSTDIO=False, shellExpandArguments=False, 268 trace=False, initCommands=None, preRunCommands=None, 269 stopCommands=None, exitCommands=None,sourcePath=None, 270 debuggerRoot=None, launchCommands=None, sourceMap=None): 271 '''Sending launch request to vscode 272 ''' 273 274 # Make sure we disconnect and terminate the VSCode debug adapter, 275 # if we throw an exception during the test case 276 def cleanup(): 277 self.vscode.request_disconnect(terminateDebuggee=True) 278 self.vscode.terminate() 279 280 # Execute the cleanup function during test case tear down. 281 self.addTearDownHook(cleanup) 282 283 # Initialize and launch the program 284 self.vscode.request_initialize() 285 response = self.vscode.request_launch( 286 program, 287 args=args, 288 cwd=cwd, 289 env=env, 290 stopOnEntry=stopOnEntry, 291 disableASLR=disableASLR, 292 disableSTDIO=disableSTDIO, 293 shellExpandArguments=shellExpandArguments, 294 trace=trace, 295 initCommands=initCommands, 296 preRunCommands=preRunCommands, 297 stopCommands=stopCommands, 298 exitCommands=exitCommands, 299 sourcePath=sourcePath, 300 debuggerRoot=debuggerRoot, 301 launchCommands=launchCommands, 302 sourceMap=sourceMap) 303 if not (response and response['success']): 304 self.assertTrue(response['success'], 305 'launch failed (%s)' % (response['message'])) 306 307 def build_and_launch(self, program, args=None, cwd=None, env=None, 308 stopOnEntry=False, disableASLR=True, 309 disableSTDIO=False, shellExpandArguments=False, 310 trace=False, initCommands=None, preRunCommands=None, 311 stopCommands=None, exitCommands=None, 312 sourcePath=None, debuggerRoot=None): 313 '''Build the default Makefile target, create the VSCode debug adaptor, 314 and launch the process. 315 ''' 316 self.build_and_create_debug_adaptor() 317 self.assertTrue(os.path.exists(program), 'executable must exist') 318 319 self.launch(program, args, cwd, env, stopOnEntry, disableASLR, 320 disableSTDIO, shellExpandArguments, trace, 321 initCommands, preRunCommands, stopCommands, exitCommands, 322 sourcePath, debuggerRoot) 323