lldbtest.py revision 41998197e9d81cba31ac4645c1ec5f0ba7ca1668
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 135BREAKPOINT_HIT_TWICE = "Breakpoint resolved with hit cout = 2" 136 137STEP_OUT_SUCCEEDED = "Thread step-out succeeded" 138 139STOPPED_DUE_TO_BREAKPOINT = "Process state is stopped due to breakpoint" 140 141STOPPED_DUE_TO_STEP_IN = "Process state is stopped due to step in" 142 143DATA_TYPES_DISPLAYED_CORRECTLY = "Data type(s) displayed correctly" 144 145VALID_BREAKPOINT = "Got a valid breakpoint" 146 147VALID_FILESPEC = "Got a valid filespec" 148 149VALID_PROCESS = "Got a valid process" 150 151VALID_TARGET = "Got a valid target" 152 153VARIABLES_DISPLAYED_CORRECTLY = "Variable(s) displayed correctly" 154 155 156# 157# And a generic "Command '%s' returns successfully" message generator. 158# 159def CMD_MSG(str, exe): 160 if exe: 161 return "Command '%s' returns successfully" % str 162 else: 163 return "'%s' compares successfully" % str 164 165# 166# Returns the enum from the input string. 167# 168def StopReasonEnum(string): 169 if string == "Invalid": 170 return 0 171 elif string == "None": 172 return 1 173 elif string == "Trace": 174 return 2 175 elif string == "Breakpoint": 176 return 3 177 elif string == "Watchpoint": 178 return 4 179 elif string == "Signal": 180 return 5 181 elif string == "Exception": 182 return 6 183 elif string == "PlanComplete": 184 return 7 185 else: 186 raise Exception("Unknown stopReason string") 187 188# 189# Returns the stopReason string given an enum. 190# 191def StopReasonString(enum): 192 if enum == 0: 193 return "Invalid" 194 elif enum == 1: 195 return "None" 196 elif enum == 2: 197 return "Trace" 198 elif enum == 3: 199 return "Breakpoint" 200 elif enum == 4: 201 return "Watchpoint" 202 elif enum == 5: 203 return "Signal" 204 elif enum == 6: 205 return "Exception" 206 elif enum == 7: 207 return "PlanComplete" 208 else: 209 raise Exception("Unknown stopReason enum") 210 211# 212# Returns an env variable array from the os.environ map object. 213# 214def EnvArray(): 215 return map(lambda k,v: k+"="+v, os.environ.keys(), os.environ.values()) 216 217# From 2.7's subprocess.check_output() convenience function. 218def system(*popenargs, **kwargs): 219 r"""Run command with arguments and return its output as a byte string. 220 221 If the exit code was non-zero it raises a CalledProcessError. The 222 CalledProcessError object will have the return code in the returncode 223 attribute and output in the output attribute. 224 225 The arguments are the same as for the Popen constructor. Example: 226 227 >>> check_output(["ls", "-l", "/dev/null"]) 228 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n' 229 230 The stdout argument is not allowed as it is used internally. 231 To capture standard error in the result, use stderr=STDOUT. 232 233 >>> check_output(["/bin/sh", "-c", 234 ... "ls -l non_existent_file ; exit 0"], 235 ... stderr=STDOUT) 236 'ls: non_existent_file: No such file or directory\n' 237 """ 238 if 'stdout' in kwargs: 239 raise ValueError('stdout argument not allowed, it will be overridden.') 240 process = Popen(stdout=PIPE, *popenargs, **kwargs) 241 output, error = process.communicate() 242 retcode = process.poll() 243 244 if traceAlways: 245 if isinstance(popenargs, types.StringTypes): 246 args = [popenargs] 247 else: 248 args = list(popenargs) 249 print >> sys.stderr 250 print >> sys.stderr, "os command:", args 251 print >> sys.stderr, "stdout:", output 252 print >> sys.stderr, "stderr:", error 253 print >> sys.stderr, "retcode:", retcode 254 print >> sys.stderr 255 256 if retcode: 257 cmd = kwargs.get("args") 258 if cmd is None: 259 cmd = popenargs[0] 260 raise CalledProcessError(retcode, cmd) 261 return output 262 263 264class TestBase(unittest2.TestCase): 265 """This LLDB abstract base class is meant to be subclassed.""" 266 267 @classmethod 268 def skipLongRunningTest(cls): 269 """ 270 By default, we skip long running test case. 271 This can be overridden by passing '-l' to the test driver (dotest.py). 272 """ 273 if "LLDB_SKIP_LONG_RUNNING_TEST" in os.environ and "NO" == os.environ["LLDB_SKIP_LONG_RUNNING_TEST"]: 274 return False 275 else: 276 return True 277 278 # The concrete subclass should override this attribute. 279 mydir = None 280 281 # State pertaining to the inferior process, if any. 282 # This reflects inferior process started through the command interface with 283 # either the lldb "run" or "process launch" command. 284 # See also self.runCmd(). 285 runStarted = False 286 287 # Maximum allowed attempts when launching the inferior process. 288 # Can be overridden by the LLDB_MAX_LAUNCH_COUNT environment variable. 289 maxLaunchCount = 3; 290 291 # Time to wait before the next launching attempt in second(s). 292 # Can be overridden by the LLDB_TIME_WAIT environment variable. 293 timeWait = 1.0; 294 295 # Keep track of the old current working directory. 296 oldcwd = None 297 298 @classmethod 299 def setUpClass(cls): 300 """ 301 Python unittest framework class setup fixture. 302 Do current directory manipulation. 303 """ 304 305 # Fail fast if 'mydir' attribute is not overridden. 306 if not cls.mydir or len(cls.mydir) == 0: 307 raise Exception("Subclasses must override the 'mydir' attribute.") 308 # Save old working directory. 309 cls.oldcwd = os.getcwd() 310 311 # Change current working directory if ${LLDB_TEST} is defined. 312 # See also dotest.py which sets up ${LLDB_TEST}. 313 if ("LLDB_TEST" in os.environ): 314 if traceAlways: 315 print >> sys.stderr, "Change dir to:", os.path.join(os.environ["LLDB_TEST"], cls.mydir) 316 os.chdir(os.path.join(os.environ["LLDB_TEST"], cls.mydir)) 317 318 @classmethod 319 def tearDownClass(cls): 320 """ 321 Python unittest framework class teardown fixture. 322 Do class-wide cleanup. 323 """ 324 325 # First, let's do the platform-specific cleanup. 326 module = __import__(sys.platform) 327 if not module.cleanup(): 328 raise Exception("Don't know how to do cleanup") 329 330 # Subclass might have specific cleanup function defined. 331 if getattr(cls, "classCleanup", None): 332 if traceAlways: 333 print >> sys.stderr, "Call class-specific cleanup function for class:", cls 334 try: 335 cls.classCleanup() 336 except: 337 exc_type, exc_value, exc_tb = sys.exc_info() 338 traceback.print_exception(exc_type, exc_value, exc_tb) 339 340 # Restore old working directory. 341 if traceAlways: 342 print >> sys.stderr, "Restore dir to:", cls.oldcwd 343 os.chdir(cls.oldcwd) 344 345 def setUp(self): 346 #import traceback 347 #traceback.print_stack() 348 349 if "LLDB_MAX_LAUNCH_COUNT" in os.environ: 350 self.maxLaunchCount = int(os.environ["LLDB_MAX_LAUNCH_COUNT"]) 351 352 if "LLDB_TIME_WAIT" in os.environ: 353 self.timeWait = float(os.environ["LLDB_TIME_WAIT"]) 354 355 # Create the debugger instance if necessary. 356 try: 357 self.dbg = lldb.DBG 358 except AttributeError: 359 self.dbg = lldb.SBDebugger.Create() 360 361 if not self.dbg.IsValid(): 362 raise Exception('Invalid debugger instance') 363 364 # We want our debugger to be synchronous. 365 self.dbg.SetAsync(False) 366 367 # There is no process associated with the debugger as yet. 368 # See also self.tearDown() where it checks whether self.process has a 369 # valid reference and calls self.process.Kill() to kill the process. 370 self.process = None 371 372 # Retrieve the associated command interpreter instance. 373 self.ci = self.dbg.GetCommandInterpreter() 374 if not self.ci: 375 raise Exception('Could not get the command interpreter') 376 377 # And the result object. 378 self.res = lldb.SBCommandReturnObject() 379 380 # These are for customized teardown cleanup. 381 self.dict = None 382 self.doTearDownCleanup = False 383 384 def setTearDownCleanup(self, dictionary=None): 385 self.dict = dictionary 386 self.doTearDownCleanup = True 387 388 def tearDown(self): 389 #import traceback 390 #traceback.print_stack() 391 392 # Terminate the current process being debugged, if any. 393 if self.runStarted: 394 self.runCmd("process kill", PROCESS_KILLED, check=False) 395 elif self.process and self.process.IsValid(): 396 rc = self.process.Kill() 397 self.assertTrue(rc.Success(), PROCESS_KILLED) 398 399 del self.dbg 400 401 # Perform registered teardown cleanup. 402 if self.doTearDownCleanup: 403 module = __import__(sys.platform) 404 if not module.cleanup(dictionary=self.dict): 405 raise Exception("Don't know how to do cleanup") 406 407 def runCmd(self, cmd, msg=None, check=True, trace=False, setCookie=True): 408 """ 409 Ask the command interpreter to handle the command and then check its 410 return status. 411 """ 412 # Fail fast if 'cmd' is not meaningful. 413 if not cmd or len(cmd) == 0: 414 raise Exception("Bad 'cmd' parameter encountered") 415 416 trace = (True if traceAlways else trace) 417 418 running = (cmd.startswith("run") or cmd.startswith("process launch")) 419 420 for i in range(self.maxLaunchCount if running else 1): 421 self.ci.HandleCommand(cmd, self.res) 422 423 if trace: 424 print >> sys.stderr, "runCmd:", cmd 425 if self.res.Succeeded(): 426 print >> sys.stderr, "output:", self.res.GetOutput() 427 else: 428 print >> sys.stderr, self.res.GetError() 429 430 if running: 431 # For process launch, wait some time before possible next try. 432 time.sleep(self.timeWait) 433 434 if self.res.Succeeded(): 435 break 436 elif running: 437 print >> sys.stderr, "Command '" + cmd + "' failed!" 438 439 # Modify runStarted only if "run" or "process launch" was encountered. 440 if running: 441 self.runStarted = running and setCookie 442 443 if check: 444 self.assertTrue(self.res.Succeeded(), 445 msg if msg else CMD_MSG(cmd, True)) 446 447 def expect(self, str, msg=None, patterns=None, startstr=None, substrs=None, trace=False, error=False, matching=True, exe=True): 448 """ 449 Similar to runCmd; with additional expect style output matching ability. 450 451 Ask the command interpreter to handle the command and then check its 452 return status. The 'msg' parameter specifies an informational assert 453 message. We expect the output from running the command to start with 454 'startstr', matches the substrings contained in 'substrs', and regexp 455 matches the patterns contained in 'patterns'. 456 457 If the keyword argument error is set to True, it signifies that the API 458 client is expecting the command to fail. In this case, the error stream 459 from running the command is retrieved and compared against the golden 460 input, instead. 461 462 If the keyword argument matching is set to False, it signifies that the API 463 client is expecting the output of the command not to match the golden 464 input. 465 466 Finally, the required argument 'str' represents the lldb command to be 467 sent to the command interpreter. In case the keyword argument 'exe' is 468 set to False, the 'str' is treated as a string to be matched/not-matched 469 against the golden input. 470 """ 471 trace = (True if traceAlways else trace) 472 473 if exe: 474 # First run the command. If we are expecting error, set check=False. 475 self.runCmd(str, trace = (True if trace else False), check = not error) 476 477 # Then compare the output against expected strings. 478 output = self.res.GetError() if error else self.res.GetOutput() 479 480 # If error is True, the API client expects the command to fail! 481 if error: 482 self.assertFalse(self.res.Succeeded(), 483 "Command '" + str + "' is expected to fail!") 484 else: 485 # No execution required, just compare str against the golden input. 486 output = str 487 if trace: 488 print >> sys.stderr, "looking at:", output 489 490 # The heading says either "Expecting" or "Not expecting". 491 if trace: 492 heading = "Expecting" if matching else "Not expecting" 493 494 # Start from the startstr, if specified. 495 # If there's no startstr, set the initial state appropriately. 496 matched = output.startswith(startstr) if startstr else (True if matching else False) 497 498 if startstr and trace: 499 print >> sys.stderr, "%s start string: %s" % (heading, startstr) 500 print >> sys.stderr, "Matched" if matched else "Not matched" 501 print >> sys.stderr 502 503 # Look for sub strings, if specified. 504 keepgoing = matched if matching else not matched 505 if substrs and keepgoing: 506 for str in substrs: 507 matched = output.find(str) != -1 508 if trace: 509 print >> sys.stderr, "%s sub string: %s" % (heading, str) 510 print >> sys.stderr, "Matched" if matched else "Not matched" 511 keepgoing = matched if matching else not matched 512 if not keepgoing: 513 break 514 if trace: 515 print >> sys.stderr 516 517 # Search for regular expression patterns, if specified. 518 keepgoing = matched if matching else not matched 519 if patterns and keepgoing: 520 for pattern in patterns: 521 # Match Objects always have a boolean value of True. 522 matched = bool(re.search(pattern, output)) 523 if trace: 524 print >> sys.stderr, "%s pattern: %s" % (heading, pattern) 525 print >> sys.stderr, "Matched" if matched else "Not matched" 526 keepgoing = matched if matching else not matched 527 if not keepgoing: 528 break 529 if trace: 530 print >> sys.stderr 531 532 self.assertTrue(matched if matching else not matched, 533 msg if msg else CMD_MSG(str, exe)) 534 535 def invoke(self, obj, name, trace=False): 536 """Use reflection to call a method dynamically with no argument.""" 537 trace = (True if traceAlways else trace) 538 539 method = getattr(obj, name) 540 import inspect 541 self.assertTrue(inspect.ismethod(method), 542 name + "is a method name of object: " + str(obj)) 543 result = method() 544 if trace: 545 print >> sys.stderr, str(method) + ":", result 546 return result 547 548 def breakAfterLaunch(self, process, func, trace=False): 549 """ 550 Perform some dancees after LaunchProcess() to break at func name. 551 552 Return True if we can successfully break at the func name in due time. 553 """ 554 trace = (True if traceAlways else trace) 555 556 count = 0 557 while True: 558 # The stop reason of the thread should be breakpoint. 559 thread = process.GetThreadAtIndex(0) 560 SR = thread.GetStopReason() 561 if trace: 562 print >> sys.stderr, "StopReason =", StopReasonString(SR) 563 564 if SR == StopReasonEnum("Breakpoint"): 565 frame = thread.GetFrameAtIndex(0) 566 name = frame.GetFunction().GetName() 567 if trace: 568 print >> sys.stderr, "function =", name 569 if (name == func): 570 # We got what we want; now break out of the loop. 571 return True 572 573 # The inferior is in a transient state; continue the process. 574 time.sleep(1.0) 575 if trace: 576 print >> sys.stderr, "Continuing the process:", process 577 process.Continue() 578 579 count = count + 1 580 if count == 15: 581 if trace: 582 print >> sys.stderr, "Reached 15 iterations, giving up..." 583 # Enough iterations already, break out of the loop. 584 return False 585 586 # End of while loop. 587 588 589 def buildDefault(self, architecture=None, compiler=None, dictionary=None): 590 """Platform specific way to build the default binaries.""" 591 module = __import__(sys.platform) 592 if not module.buildDefault(architecture, compiler, dictionary): 593 raise Exception("Don't know how to build default binary") 594 595 def buildDsym(self, architecture=None, compiler=None, dictionary=None): 596 """Platform specific way to build binaries with dsym info.""" 597 module = __import__(sys.platform) 598 if not module.buildDsym(architecture, compiler, dictionary): 599 raise Exception("Don't know how to build binary with dsym") 600 601 def buildDwarf(self, architecture=None, compiler=None, dictionary=None): 602 """Platform specific way to build binaries with dwarf maps.""" 603 module = __import__(sys.platform) 604 if not module.buildDwarf(architecture, compiler, dictionary): 605 raise Exception("Don't know how to build binary with dwarf") 606 607 def DebugSBValue(self, frame, val): 608 """Debug print a SBValue object, if traceAlways is True.""" 609 if not traceAlways: 610 return 611 612 err = sys.stderr 613 err.write(val.GetName() + ":\n") 614 err.write('\t' + "TypeName -> " + val.GetTypeName() + '\n') 615 err.write('\t' + "ByteSize -> " + str(val.GetByteSize()) + '\n') 616 err.write('\t' + "NumChildren -> " + str(val.GetNumChildren()) + '\n') 617 err.write('\t' + "Value -> " + str(val.GetValue(frame)) + '\n') 618 err.write('\t' + "Summary -> " + str(val.GetSummary(frame)) + '\n') 619 err.write('\t' + "IsPtrType -> " + str(val.TypeIsPtrType()) + '\n') 620 err.write('\t' + "Location -> " + val.GetLocation(frame) + '\n') 621 622