lldbtest.py revision 8e06de96e9d13418a0ea0d4f3311c61c2fa4fa40
1""" 2LLDB module which provides the abstract base class of lldb test case. 3 4The concrete subclass can override lldbtest.TesBase in order to inherit the 5common behavior for unitest.TestCase.setUp/tearDown implemented in this file. 6 7The subclass should override the attribute mydir in order for the python runtime 8to locate the individual test cases when running as part of a large test suite 9or when running each test case as a separate python invocation. 10 11./dotest.py provides a test driver which sets up the environment to run the 12entire test suite. Users who want to run a test case on its own can specify the 13LLDB_TEST and PYTHONPATH environment variables, for example: 14 15$ export LLDB_TEST=$PWD 16$ export PYTHONPATH=/Volumes/data/lldb/svn/trunk/build/Debug/LLDB.framework/Resources/Python:$LLDB_TEST:$LLDB_TEST/plugins 17$ echo $LLDB_TEST 18/Volumes/data/lldb/svn/trunk/test 19$ echo $PYTHONPATH 20/Volumes/data/lldb/svn/trunk/build/Debug/LLDB.framework/Resources/Python:/Volumes/data/lldb/svn/trunk/test:/Volumes/data/lldb/svn/trunk/test/plugins 21$ python function_types/TestFunctionTypes.py 22. 23---------------------------------------------------------------------- 24Ran 1 test in 0.363s 25 26OK 27$ LLDB_COMMAND_TRACE=YES python array_types/TestArrayTypes.py 28 29... 30 31runCmd: breakpoint set -f main.c -l 42 32output: Breakpoint created: 1: file ='main.c', line = 42, locations = 1 33 34runCmd: run 35output: Launching '/Volumes/data/lldb/svn/trunk/test/array_types/a.out' (x86_64) 36 37... 38 39runCmd: frame variable strings 40output: (char *[4]) strings = { 41 (char *) strings[0] = 0x0000000100000f0c "Hello", 42 (char *) strings[1] = 0x0000000100000f12 "Hola", 43 (char *) strings[2] = 0x0000000100000f17 "Bonjour", 44 (char *) strings[3] = 0x0000000100000f1f "Guten Tag" 45} 46 47runCmd: frame variable char_16 48output: (char [16]) char_16 = { 49 (char) char_16[0] = 'H', 50 (char) char_16[1] = 'e', 51 (char) char_16[2] = 'l', 52 (char) char_16[3] = 'l', 53 (char) char_16[4] = 'o', 54 (char) char_16[5] = ' ', 55 (char) char_16[6] = 'W', 56 (char) char_16[7] = 'o', 57 (char) char_16[8] = 'r', 58 (char) char_16[9] = 'l', 59 (char) char_16[10] = 'd', 60 (char) char_16[11] = '\n', 61 (char) char_16[12] = '\0', 62 (char) char_16[13] = '\0', 63 (char) char_16[14] = '\0', 64 (char) char_16[15] = '\0' 65} 66 67runCmd: frame variable ushort_matrix 68output: (unsigned short [2][3]) ushort_matrix = { 69 (unsigned short [3]) ushort_matrix[0] = { 70 (unsigned short) ushort_matrix[0][0] = 0x0001, 71 (unsigned short) ushort_matrix[0][1] = 0x0002, 72 (unsigned short) ushort_matrix[0][2] = 0x0003 73 }, 74 (unsigned short [3]) ushort_matrix[1] = { 75 (unsigned short) ushort_matrix[1][0] = 0x000b, 76 (unsigned short) ushort_matrix[1][1] = 0x0016, 77 (unsigned short) ushort_matrix[1][2] = 0x0021 78 } 79} 80 81runCmd: frame variable long_6 82output: (long [6]) long_6 = { 83 (long) long_6[0] = 1, 84 (long) long_6[1] = 2, 85 (long) long_6[2] = 3, 86 (long) long_6[3] = 4, 87 (long) long_6[4] = 5, 88 (long) long_6[5] = 6 89} 90 91. 92---------------------------------------------------------------------- 93Ran 1 test in 0.349s 94 95OK 96$ 97""" 98 99import os, sys, traceback 100import re 101from subprocess import * 102import time 103import types 104import unittest2 105import lldb 106 107if "LLDB_COMMAND_TRACE" in os.environ and os.environ["LLDB_COMMAND_TRACE"]=="YES": 108 traceAlways = True 109else: 110 traceAlways = False 111 112 113# 114# Some commonly used assert messages. 115# 116 117COMMAND_FAILED_AS_EXPECTED = "Command has failed as expected" 118 119CURRENT_EXECUTABLE_SET = "Current executable set successfully" 120 121PROCESS_IS_VALID = "Process is valid" 122 123PROCESS_KILLED = "Process is killed successfully" 124 125RUN_SUCCEEDED = "Process is launched successfully" 126 127RUN_COMPLETED = "Process exited successfully" 128 129BREAKPOINT_CREATED = "Breakpoint created successfully" 130 131BREAKPOINT_PENDING_CREATED = "Pending breakpoint created successfully" 132 133BREAKPOINT_HIT_ONCE = "Breakpoint resolved with hit cout = 1" 134 135STOPPED_DUE_TO_BREAKPOINT = "Process state is stopped due to breakpoint" 136 137STOPPED_DUE_TO_STEP_IN = "Process state is stopped due to step in" 138 139DATA_TYPES_DISPLAYED_CORRECTLY = "Data type(s) displayed correctly" 140 141VALID_BREAKPOINT = "Got a valid breakpoint" 142 143VALID_FILESPEC = "Got a valid filespec" 144 145VALID_PROCESS = "Got a valid process" 146 147VALID_TARGET = "Got a valid target" 148 149VARIABLES_DISPLAYED_CORRECTLY = "Variable(s) displayed correctly" 150 151 152# 153# And a generic "Command '%s' returns successfully" message generator. 154# 155def CMD_MSG(str, exe): 156 if exe: 157 return "Command '%s' returns successfully" % str 158 else: 159 return "'%s' compares successfully" % str 160 161# 162# Returns the enum from the input string. 163# 164def StopReasonEnum(string): 165 if string == "Invalid": 166 return 0 167 elif string == "None": 168 return 1 169 elif string == "Trace": 170 return 2 171 elif string == "Breakpoint": 172 return 3 173 elif string == "Watchpoint": 174 return 4 175 elif string == "Signal": 176 return 5 177 elif string == "Exception": 178 return 6 179 elif string == "PlanComplete": 180 return 7 181 else: 182 raise Exception("Unknown stopReason string") 183 184# 185# Returns the stopReason string given an enum. 186# 187def StopReasonString(enum): 188 if enum == 0: 189 return "Invalid" 190 elif enum == 1: 191 return "None" 192 elif enum == 2: 193 return "Trace" 194 elif enum == 3: 195 return "Breakpoint" 196 elif enum == 4: 197 return "Watchpoint" 198 elif enum == 5: 199 return "Signal" 200 elif enum == 6: 201 return "Exception" 202 elif enum == 7: 203 return "PlanComplete" 204 else: 205 raise Exception("Unknown stopReason enum") 206 207# 208# Returns an env variable array from the os.environ map object. 209# 210def EnvArray(): 211 return map(lambda k,v: k+"="+v, os.environ.keys(), os.environ.values()) 212 213# From 2.7's subprocess.check_output() convenience function. 214def system(*popenargs, **kwargs): 215 r"""Run command with arguments and return its output as a byte string. 216 217 If the exit code was non-zero it raises a CalledProcessError. The 218 CalledProcessError object will have the return code in the returncode 219 attribute and output in the output attribute. 220 221 The arguments are the same as for the Popen constructor. Example: 222 223 >>> check_output(["ls", "-l", "/dev/null"]) 224 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n' 225 226 The stdout argument is not allowed as it is used internally. 227 To capture standard error in the result, use stderr=STDOUT. 228 229 >>> check_output(["/bin/sh", "-c", 230 ... "ls -l non_existent_file ; exit 0"], 231 ... stderr=STDOUT) 232 'ls: non_existent_file: No such file or directory\n' 233 """ 234 if 'stdout' in kwargs: 235 raise ValueError('stdout argument not allowed, it will be overridden.') 236 process = Popen(stdout=PIPE, *popenargs, **kwargs) 237 output, error = process.communicate() 238 retcode = process.poll() 239 240 if traceAlways: 241 if isinstance(popenargs, types.StringTypes): 242 args = [popenargs] 243 else: 244 args = list(popenargs) 245 print >> sys.stderr 246 print >> sys.stderr, "os command:", args 247 print >> sys.stderr, "stdout:", output 248 print >> sys.stderr, "stderr:", error 249 print >> sys.stderr, "retcode:", retcode 250 print >> sys.stderr 251 252 if retcode: 253 cmd = kwargs.get("args") 254 if cmd is None: 255 cmd = popenargs[0] 256 raise CalledProcessError(retcode, cmd) 257 return output 258 259 260class TestBase(unittest2.TestCase): 261 """This LLDB abstract base class is meant to be subclassed.""" 262 263 # The concrete subclass should override this attribute. 264 mydir = None 265 266 # State pertaining to the inferior process, if any. 267 # This reflects inferior process started through the command interface with 268 # either the lldb "run" or "process launch" command. 269 # See also self.runCmd(). 270 runStarted = False 271 272 # Maximum allowed attempts when launching the inferior process. 273 # Can be overridden by the LLDB_MAX_LAUNCH_COUNT environment variable. 274 maxLaunchCount = 3; 275 276 # Time to wait before the next launching attempt in second(s). 277 # Can be overridden by the LLDB_TIME_WAIT environment variable. 278 timeWait = 1.0; 279 280 # Keep track of the old current working directory. 281 oldcwd = None 282 283 @classmethod 284 def setUpClass(cls): 285 # Fail fast if 'mydir' attribute is not overridden. 286 if not cls.mydir or len(cls.mydir) == 0: 287 raise Exception("Subclasses must override the 'mydir' attribute.") 288 # Save old working directory. 289 cls.oldcwd = os.getcwd() 290 291 # Change current working directory if ${LLDB_TEST} is defined. 292 # See also dotest.py which sets up ${LLDB_TEST}. 293 if ("LLDB_TEST" in os.environ): 294 if traceAlways: 295 print "Change dir to:", os.path.join(os.environ["LLDB_TEST"], cls.mydir) 296 os.chdir(os.path.join(os.environ["LLDB_TEST"], cls.mydir)) 297 298 @classmethod 299 def tearDownClass(cls): 300 """Do class-wide cleanup.""" 301 302 # First, let's do the platform-specific cleanup. 303 module = __import__(sys.platform) 304 if not module.cleanup(): 305 raise Exception("Don't know how to do cleanup") 306 307 # Subclass might have specific cleanup function defined. 308 if getattr(cls, "classCleanup", None): 309 if traceAlways: 310 print "Call class-specific cleanup function for class:", cls 311 try: 312 cls.classCleanup() 313 except: 314 exc_type, exc_value, exc_tb = sys.exc_info() 315 traceback.print_exception(exc_type, exc_value, exc_tb) 316 317 # Restore old working directory. 318 if traceAlways: 319 print "Restore dir to:", cls.oldcwd 320 os.chdir(cls.oldcwd) 321 322 def setUp(self): 323 #import traceback 324 #traceback.print_stack() 325 326 if "LLDB_MAX_LAUNCH_COUNT" in os.environ: 327 self.maxLaunchCount = int(os.environ["LLDB_MAX_LAUNCH_COUNT"]) 328 329 if "LLDB_TIME_WAIT" in os.environ: 330 self.timeWait = float(os.environ["LLDB_TIME_WAIT"]) 331 332 # Create the debugger instance if necessary. 333 try: 334 self.dbg = lldb.DBG 335 except AttributeError: 336 self.dbg = lldb.SBDebugger.Create() 337 338 if not self.dbg.IsValid(): 339 raise Exception('Invalid debugger instance') 340 341 # We want our debugger to be synchronous. 342 self.dbg.SetAsync(False) 343 344 # There is no process associated with the debugger as yet. 345 # See also self.tearDown() where it checks whether self.process has a 346 # valid reference and calls self.process.Kill() to kill the process. 347 self.process = None 348 349 # Retrieve the associated command interpreter instance. 350 self.ci = self.dbg.GetCommandInterpreter() 351 if not self.ci: 352 raise Exception('Could not get the command interpreter') 353 354 # And the result object. 355 self.res = lldb.SBCommandReturnObject() 356 357 def tearDown(self): 358 #import traceback 359 #traceback.print_stack() 360 361 # Terminate the current process being debugged, if any. 362 if self.runStarted: 363 self.runCmd("process kill", PROCESS_KILLED, check=False) 364 elif self.process and self.process.IsValid(): 365 rc = self.process.Kill() 366 self.assertTrue(rc.Success(), PROCESS_KILLED) 367 368 del self.dbg 369 370 def runCmd(self, cmd, msg=None, check=True, trace=False, setCookie=True): 371 """ 372 Ask the command interpreter to handle the command and then check its 373 return status. 374 """ 375 # Fail fast if 'cmd' is not meaningful. 376 if not cmd or len(cmd) == 0: 377 raise Exception("Bad 'cmd' parameter encountered") 378 379 trace = (True if traceAlways else trace) 380 381 running = (cmd.startswith("run") or cmd.startswith("process launch")) 382 383 for i in range(self.maxLaunchCount if running else 1): 384 self.ci.HandleCommand(cmd, self.res) 385 386 if trace: 387 print >> sys.stderr, "runCmd:", cmd 388 if self.res.Succeeded(): 389 print >> sys.stderr, "output:", self.res.GetOutput() 390 else: 391 print >> sys.stderr, self.res.GetError() 392 393 if running: 394 # For process launch, wait some time before possible next try. 395 time.sleep(self.timeWait) 396 397 if self.res.Succeeded(): 398 break 399 elif running: 400 print >> sys.stderr, "Command '" + cmd + "' failed!" 401 402 # Modify runStarted only if "run" or "process launch" was encountered. 403 if running: 404 self.runStarted = running and setCookie 405 406 if check: 407 self.assertTrue(self.res.Succeeded(), 408 msg if msg else CMD_MSG(cmd, True)) 409 410 def expect(self, str, msg=None, patterns=None, startstr=None, substrs=None, trace=False, error=False, matching=True, exe=True): 411 """ 412 Similar to runCmd; with additional expect style output matching ability. 413 414 Ask the command interpreter to handle the command and then check its 415 return status. The 'msg' parameter specifies an informational assert 416 message. We expect the output from running the command to start with 417 'startstr', matches the substrings contained in 'substrs', and regexp 418 matches the patterns contained in 'patterns'. 419 420 If the keyword argument error is set to True, it signifies that the API 421 client is expecting the command to fail. In this case, the error stream 422 from running the command is retrieved and compared against the golden 423 input, instead. 424 425 If the keyword argument matching is set to False, it signifies that the API 426 client is expecting the output of the command not to match the golden 427 input. 428 429 Finally, the required argument 'str' represents the lldb command to be 430 sent to the command interpreter. In case the keyword argument 'exe' is 431 set to False, the 'str' is treated as a string to be matched/not-matched 432 against the golden input. 433 """ 434 trace = (True if traceAlways else trace) 435 436 if exe: 437 # First run the command. If we are expecting error, set check=False. 438 self.runCmd(str, trace = (True if trace else False), check = not error) 439 440 # Then compare the output against expected strings. 441 output = self.res.GetError() if error else self.res.GetOutput() 442 443 # If error is True, the API client expects the command to fail! 444 if error: 445 self.assertFalse(self.res.Succeeded(), 446 "Command '" + str + "' is expected to fail!") 447 else: 448 # No execution required, just compare str against the golden input. 449 output = str 450 if trace: 451 print >> sys.stderr, "look at:", output 452 453 # The heading says either "Expecting" or "Not expecting". 454 if trace: 455 heading = "Expecting" if matching else "Not expecting" 456 457 # Start from the startstr, if specified. 458 # If there's no startstr, set the initial state appropriately. 459 matched = output.startswith(startstr) if startstr else (True if matching else False) 460 461 if startstr and trace: 462 print >> sys.stderr, "%s start string: %s" % (heading, startstr) 463 print >> sys.stderr, "Matched" if matched else "Not matched" 464 print >> sys.stderr 465 466 # Look for sub strings, if specified. 467 keepgoing = matched if matching else not matched 468 if substrs and keepgoing: 469 for str in substrs: 470 matched = output.find(str) > 0 471 if trace: 472 print >> sys.stderr, "%s sub string: %s" % (heading, str) 473 print >> sys.stderr, "Matched" if matched else "Not matched" 474 keepgoing = matched if matching else not matched 475 if not keepgoing: 476 break 477 if trace: 478 print >> sys.stderr 479 480 # Search for regular expression patterns, if specified. 481 keepgoing = matched if matching else not matched 482 if patterns and keepgoing: 483 for pattern in patterns: 484 # Match Objects always have a boolean value of True. 485 matched = bool(re.search(pattern, output)) 486 if trace: 487 print >> sys.stderr, "%s pattern: %s" % (heading, pattern) 488 print >> sys.stderr, "Matched" if matched else "Not matched" 489 keepgoing = matched if matching else not matched 490 if not keepgoing: 491 break 492 if trace: 493 print >> sys.stderr 494 495 self.assertTrue(matched if matching else not matched, 496 msg if msg else CMD_MSG(str, exe)) 497 498 def invoke(self, obj, name, trace=False): 499 """Use reflection to call a method dynamically with no argument.""" 500 trace = (True if traceAlways else trace) 501 502 method = getattr(obj, name) 503 import inspect 504 self.assertTrue(inspect.ismethod(method), 505 name + "is a method name of object: " + str(obj)) 506 result = method() 507 if trace: 508 print str(method) + ":", result 509 return result 510 511 def breakAfterLaunch(self, process, func, trace=False): 512 """ 513 Perform some dancees after LaunchProcess() to break at func name. 514 515 Return True if we can successfully break at the func name in due time. 516 """ 517 trace = (True if traceAlways else trace) 518 519 count = 0 520 while True: 521 # The stop reason of the thread should be breakpoint. 522 thread = process.GetThreadAtIndex(0) 523 SR = thread.GetStopReason() 524 if trace: 525 print >> sys.stderr, "StopReason =", StopReasonString(SR) 526 527 if SR == StopReasonEnum("Breakpoint"): 528 frame = thread.GetFrameAtIndex(0) 529 name = frame.GetFunction().GetName() 530 if trace: 531 print >> sys.stderr, "function =", name 532 if (name == func): 533 # We got what we want; now break out of the loop. 534 return True 535 536 # The inferior is in a transient state; continue the process. 537 time.sleep(1.0) 538 if trace: 539 print >> sys.stderr, "Continuing the process:", process 540 process.Continue() 541 542 count = count + 1 543 if count == 15: 544 if trace: 545 print >> sys.stderr, "Reached 15 iterations, giving up..." 546 # Enough iterations already, break out of the loop. 547 return False 548 549 # End of while loop. 550 551 552 def buildDefault(self, compiler=None): 553 """Platform specific way to build the default binaries.""" 554 module = __import__(sys.platform) 555 if not module.buildDefault(compiler): 556 raise Exception("Don't know how to build default binary") 557 558 def buildDsym(self, compiler=None): 559 """Platform specific way to build binaries with dsym info.""" 560 module = __import__(sys.platform) 561 if not module.buildDsym(compiler): 562 raise Exception("Don't know how to build binary with dsym") 563 564 def buildDwarf(self, compiler=None): 565 """Platform specific way to build binaries with dwarf maps.""" 566 module = __import__(sys.platform) 567 if not module.buildDwarf(compiler): 568 raise Exception("Don't know how to build binary with dwarf") 569 570 def DebugSBValue(self, frame, val): 571 """Debug print a SBValue object, if traceAlways is True.""" 572 if not traceAlways: 573 return 574 575 err = sys.stderr 576 err.write(val.GetName() + ":\n") 577 err.write('\t' + "TypeName -> " + val.GetTypeName() + '\n') 578 err.write('\t' + "ByteSize -> " + str(val.GetByteSize()) + '\n') 579 err.write('\t' + "NumChildren -> " + str(val.GetNumChildren()) + '\n') 580 err.write('\t' + "Value -> " + str(val.GetValue(frame)) + '\n') 581 err.write('\t' + "Summary -> " + str(val.GetSummary(frame)) + '\n') 582 err.write('\t' + "IsPtrType -> " + str(val.TypeIsPtrType()) + '\n') 583 err.write('\t' + "Location -> " + val.GetLocation(frame) + '\n') 584 585