dotest.py revision 4f93bf1b34e9828ff630eddb908adfb04e05aadf
1#!/usr/bin/env python 2 3""" 4A simple testing framework for lldb using python's unit testing framework. 5 6Tests for lldb are written as python scripts which take advantage of the script 7bridging provided by LLDB.framework to interact with lldb core. 8 9A specific naming pattern is followed by the .py script to be recognized as 10a module which implements a test scenario, namely, Test*.py. 11 12To specify the directories where "Test*.py" python test scripts are located, 13you need to pass in a list of directory names. By default, the current 14working directory is searched if nothing is specified on the command line. 15 16Type: 17 18./dotest.py -h 19 20for available options. 21""" 22 23import os, signal, sys, time 24import unittest2 25 26class _WritelnDecorator(object): 27 """Used to decorate file-like objects with a handy 'writeln' method""" 28 def __init__(self,stream): 29 self.stream = stream 30 31 def __getattr__(self, attr): 32 if attr in ('stream', '__getstate__'): 33 raise AttributeError(attr) 34 return getattr(self.stream,attr) 35 36 def writeln(self, arg=None): 37 if arg: 38 self.write(arg) 39 self.write('\n') # text-mode streams translate to \r\n if needed 40 41# 42# Global variables: 43# 44 45# The test suite. 46suite = unittest2.TestSuite() 47 48# By default, both command line and Python API tests are performed. 49# See @python_api_test decorator in lldbtest.py. 50dont_do_python_api_test = False 51 52# By default, both command line and Python API tests are performed. 53# This does not work yet as the @lldb_command_test decorator is needed. 54just_do_python_api_test = False 55 56# The blacklist is optional (-b blacklistFile) and allows a central place to skip 57# testclass's and/or testclass.testmethod's. 58blacklist = None 59 60# The dictionary as a result of sourcing blacklistFile. 61blacklistConfig = {} 62 63# The config file is optional. 64configFile = None 65 66# Test suite repeat count. Can be overwritten with '-# count'. 67count = 1 68 69# The dictionary as a result of sourcing configFile. 70config = {} 71 72# Delay startup in order for the debugger to attach. 73delay = False 74 75# By default, failfast is False. Use '-F' to overwrite it. 76failfast = False 77 78# The filter (testclass.testmethod) used to admit tests into our test suite. 79filterspec = None 80 81# If '-g' is specified, the filterspec is not exclusive. If a test module does 82# not contain testclass.testmethod which matches the filterspec, the whole test 83# module is still admitted into our test suite. fs4all flag defaults to True. 84fs4all = True 85 86# Ignore the build search path relative to this script to locate the lldb.py module. 87ignore = False 88 89# By default, we skip long running test case. Use '-l' option to override. 90skipLongRunningTest = True 91 92# The regular expression pattern to match against eligible filenames as our test cases. 93regexp = None 94 95# By default, tests are executed in place and cleanups are performed afterwards. 96# Use '-r dir' option to relocate the tests and their intermediate files to a 97# different directory and to forgo any cleanups. The directory specified must 98# not exist yet. 99rdir = None 100 101# By default, recorded session info for errored/failed test are dumped into its 102# own file under a session directory named after the timestamp of the test suite 103# run. Use '-s session-dir-name' to specify a specific dir name. 104sdir_name = None 105 106# Set this flag if there is any session info dumped during the test run. 107sdir_has_content = False 108 109# Default verbosity is 0. 110verbose = 0 111 112# By default, search from the current working directory. 113testdirs = [ os.getcwd() ] 114 115# Separator string. 116separator = '-' * 70 117 118 119def usage(): 120 print """ 121Usage: dotest.py [option] [args] 122where options: 123-h : print this help message and exit (also --help) 124-a : don't do lldb Python API tests 125 use @python_api_test to decorate a test case as lldb Python API test 126-b : read a blacklist file specified after this option 127-c : read a config file specified after this option 128 (see also lldb-trunk/example/test/usage-config) 129-d : delay startup for 10 seconds (in order for the debugger to attach) 130-F : failfast, stop the test suite on the first error/failure 131-f : specify a filter, which consists of the test class name, a dot, followed by 132 the test method, to only admit such test into the test suite 133 e.g., -f 'ClassTypesTestCase.test_with_dwarf_and_python_api' 134-g : if specified, the filterspec by -f is not exclusive, i.e., if a test module 135 does not match the filterspec (testclass.testmethod), the whole module is 136 still admitted to the test suite 137-i : ignore (don't bailout) if 'lldb.py' module cannot be located in the build 138 tree relative to this script; use PYTHONPATH to locate the module 139-l : don't skip long running test 140-p : specify a regexp filename pattern for inclusion in the test suite 141-r : specify a dir to relocate the tests and their intermediate files to; 142 the directory must not exist before running this test driver; 143 no cleanup of intermediate test files is performed in this case 144-s : specify the name of the dir created to store the session files of tests 145 with errored or failed status; if not specified, the test driver uses the 146 timestamp as the session dir name 147-t : trace lldb command execution and result 148-v : do verbose mode of unittest framework 149-w : insert some wait time (currently 0.5 sec) between consecutive test cases 150-# : Repeat the test suite for a specified number of times 151 152and: 153args : specify a list of directory names to search for test modules named after 154 Test*.py (test discovery) 155 if empty, search from the curret working directory, instead 156 157Examples: 158 159This is an example of using the -f option to pinpoint to a specfic test class 160and test method to be run: 161 162$ ./dotest.py -f ClassTypesTestCase.test_with_dsym_and_run_command 163---------------------------------------------------------------------- 164Collected 1 test 165 166test_with_dsym_and_run_command (TestClassTypes.ClassTypesTestCase) 167Test 'frame variable this' when stopped on a class constructor. ... ok 168 169---------------------------------------------------------------------- 170Ran 1 test in 1.396s 171 172OK 173 174And this is an example of using the -p option to run a single file (the filename 175matches the pattern 'ObjC' and it happens to be 'TestObjCMethods.py'): 176 177$ ./dotest.py -v -p ObjC 178---------------------------------------------------------------------- 179Collected 4 tests 180 181test_break_with_dsym (TestObjCMethods.FoundationTestCase) 182Test setting objc breakpoints using 'regexp-break' and 'breakpoint set'. ... ok 183test_break_with_dwarf (TestObjCMethods.FoundationTestCase) 184Test setting objc breakpoints using 'regexp-break' and 'breakpoint set'. ... ok 185test_data_type_and_expr_with_dsym (TestObjCMethods.FoundationTestCase) 186Lookup objective-c data types and evaluate expressions. ... ok 187test_data_type_and_expr_with_dwarf (TestObjCMethods.FoundationTestCase) 188Lookup objective-c data types and evaluate expressions. ... ok 189 190---------------------------------------------------------------------- 191Ran 4 tests in 16.661s 192 193OK 194 195Running of this script also sets up the LLDB_TEST environment variable so that 196individual test cases can locate their supporting files correctly. The script 197tries to set up Python's search paths for modules by looking at the build tree 198relative to this script. See also the '-i' option in the following example. 199 200Finally, this is an example of using the lldb.py module distributed/installed by 201Xcode4 to run against the tests under the 'forward' directory, and with the '-w' 202option to add some delay between two tests. It uses ARCH=x86_64 to specify that 203as the architecture and CC=clang to specify the compiler used for the test run: 204 205$ PYTHONPATH=/Xcode4/Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python ARCH=x86_64 CC=clang ./dotest.py -v -w -i forward 206 207Session logs for test failures/errors will go into directory '2010-11-11-13_56_16' 208---------------------------------------------------------------------- 209Collected 2 tests 210 211test_with_dsym_and_run_command (TestForwardDeclaration.ForwardDeclarationTestCase) 212Display *bar_ptr when stopped on a function with forward declaration of struct bar. ... ok 213test_with_dwarf_and_run_command (TestForwardDeclaration.ForwardDeclarationTestCase) 214Display *bar_ptr when stopped on a function with forward declaration of struct bar. ... ok 215 216---------------------------------------------------------------------- 217Ran 2 tests in 5.659s 218 219OK 220 221The 'Session ...' verbiage is recently introduced (see also the '-s' option) to 222notify the directory containing the session logs for test failures or errors. 223In case there is any test failure/error, a similar message is appended at the 224end of the stderr output for your convenience. 225 226Environment variables related to loggings: 227 228o LLDB_LOG: if defined, specifies the log file pathname for the 'lldb' subsystem 229 with a default option of 'event process' if LLDB_LOG_OPTION is not defined. 230 231o GDB_REMOTE_LOG: if defined, specifies the log file pathname for the 232 'process.gdb-remote' subsystem with a default option of 'packets' if 233 GDB_REMOTE_LOG_OPTION is not defined. 234""" 235 sys.exit(0) 236 237 238def parseOptionsAndInitTestdirs(): 239 """Initialize the list of directories containing our unittest scripts. 240 241 '-h/--help as the first option prints out usage info and exit the program. 242 """ 243 244 global dont_do_python_api_test 245 global just_do_python_api_test 246 global blacklist 247 global blacklistConfig 248 global configFile 249 global count 250 global delay 251 global failfast 252 global filterspec 253 global fs4all 254 global ignore 255 global skipLongRunningTest 256 global regexp 257 global rdir 258 global sdir_name 259 global verbose 260 global testdirs 261 262 if len(sys.argv) == 1: 263 return 264 265 # Process possible trace and/or verbose flag, among other things. 266 index = 1 267 while index < len(sys.argv): 268 if sys.argv[index].startswith('-') or sys.argv[index].startswith('+'): 269 # We should continue processing... 270 pass 271 else: 272 # End of option processing. 273 break 274 275 if sys.argv[index].find('-h') != -1: 276 usage() 277 elif sys.argv[index].startswith('-a'): 278 dont_do_python_api_test = True 279 index += 1 280 elif sys.argv[index].startswith('+a'): 281 just_do_python_api_test = True 282 index += 1 283 elif sys.argv[index].startswith('-b'): 284 # Increment by 1 to fetch the blacklist file name option argument. 285 index += 1 286 if index >= len(sys.argv) or sys.argv[index].startswith('-'): 287 usage() 288 blacklistFile = sys.argv[index] 289 if not os.path.isfile(blacklistFile): 290 print "Blacklist file:", blacklistFile, "does not exist!" 291 usage() 292 index += 1 293 # Now read the blacklist contents and assign it to blacklist. 294 execfile(blacklistFile, globals(), blacklistConfig) 295 blacklist = blacklistConfig.get('blacklist') 296 elif sys.argv[index].startswith('-c'): 297 # Increment by 1 to fetch the config file name option argument. 298 index += 1 299 if index >= len(sys.argv) or sys.argv[index].startswith('-'): 300 usage() 301 configFile = sys.argv[index] 302 if not os.path.isfile(configFile): 303 print "Config file:", configFile, "does not exist!" 304 usage() 305 index += 1 306 elif sys.argv[index].startswith('-d'): 307 delay = True 308 index += 1 309 elif sys.argv[index].startswith('-F'): 310 failfast = True 311 index += 1 312 elif sys.argv[index].startswith('-f'): 313 # Increment by 1 to fetch the filter spec. 314 index += 1 315 if index >= len(sys.argv) or sys.argv[index].startswith('-'): 316 usage() 317 filterspec = sys.argv[index] 318 index += 1 319 elif sys.argv[index].startswith('-g'): 320 fs4all = False 321 index += 1 322 elif sys.argv[index].startswith('-i'): 323 ignore = True 324 index += 1 325 elif sys.argv[index].startswith('-l'): 326 skipLongRunningTest = False 327 index += 1 328 elif sys.argv[index].startswith('-p'): 329 # Increment by 1 to fetch the reg exp pattern argument. 330 index += 1 331 if index >= len(sys.argv) or sys.argv[index].startswith('-'): 332 usage() 333 regexp = sys.argv[index] 334 index += 1 335 elif sys.argv[index].startswith('-r'): 336 # Increment by 1 to fetch the relocated directory argument. 337 index += 1 338 if index >= len(sys.argv) or sys.argv[index].startswith('-'): 339 usage() 340 rdir = os.path.abspath(sys.argv[index]) 341 if os.path.exists(rdir): 342 print "Relocated directory:", rdir, "must not exist!" 343 usage() 344 index += 1 345 elif sys.argv[index].startswith('-s'): 346 # Increment by 1 to fetch the session dir name. 347 index += 1 348 if index >= len(sys.argv) or sys.argv[index].startswith('-'): 349 usage() 350 sdir_name = sys.argv[index] 351 index += 1 352 elif sys.argv[index].startswith('-t'): 353 os.environ["LLDB_COMMAND_TRACE"] = "YES" 354 index += 1 355 elif sys.argv[index].startswith('-v'): 356 verbose = 2 357 index += 1 358 elif sys.argv[index].startswith('-w'): 359 os.environ["LLDB_WAIT_BETWEEN_TEST_CASES"] = 'YES' 360 index += 1 361 elif sys.argv[index].startswith('-#'): 362 # Increment by 1 to fetch the repeat count argument. 363 index += 1 364 if index >= len(sys.argv) or sys.argv[index].startswith('-'): 365 usage() 366 count = int(sys.argv[index]) 367 index += 1 368 else: 369 print "Unknown option: ", sys.argv[index] 370 usage() 371 372 # Gather all the dirs passed on the command line. 373 if len(sys.argv) > index: 374 testdirs = map(os.path.abspath, sys.argv[index:]) 375 376 # If '-r dir' is specified, the tests should be run under the relocated 377 # directory. Let's copy the testdirs over. 378 if rdir: 379 from shutil import copytree, ignore_patterns 380 381 tmpdirs = [] 382 for srcdir in testdirs: 383 dstdir = os.path.join(rdir, os.path.basename(srcdir)) 384 # Don't copy the *.pyc and .svn stuffs. 385 copytree(srcdir, dstdir, ignore=ignore_patterns('*.pyc', '.svn')) 386 tmpdirs.append(dstdir) 387 388 # This will be our modified testdirs. 389 testdirs = tmpdirs 390 391 # With '-r dir' specified, there's no cleanup of intermediate test files. 392 os.environ["LLDB_DO_CLEANUP"] = 'NO' 393 394 # If testdirs is ['test'], the make directory has already been copied 395 # recursively and is contained within the rdir/test dir. For anything 396 # else, we would need to copy over the make directory and its contents, 397 # so that, os.listdir(rdir) looks like, for example: 398 # 399 # array_types conditional_break make 400 # 401 # where the make directory contains the Makefile.rules file. 402 if len(testdirs) != 1 or os.path.basename(testdirs[0]) != 'test': 403 # Don't copy the .svn stuffs. 404 copytree('make', os.path.join(rdir, 'make'), 405 ignore=ignore_patterns('.svn')) 406 407 #print "testdirs:", testdirs 408 409 # Source the configFile if specified. 410 # The side effect, if any, will be felt from this point on. An example 411 # config file may be these simple two lines: 412 # 413 # sys.stderr = open("/tmp/lldbtest-stderr", "w") 414 # sys.stdout = open("/tmp/lldbtest-stdout", "w") 415 # 416 # which will reassign the two file objects to sys.stderr and sys.stdout, 417 # respectively. 418 # 419 # See also lldb-trunk/example/test/usage-config. 420 global config 421 if configFile: 422 # Pass config (a dictionary) as the locals namespace for side-effect. 423 execfile(configFile, globals(), config) 424 #print "config:", config 425 #print "sys.stderr:", sys.stderr 426 #print "sys.stdout:", sys.stdout 427 428 429def setupSysPath(): 430 """Add LLDB.framework/Resources/Python to the search paths for modules.""" 431 432 global rdir 433 global testdirs 434 435 # Get the directory containing the current script. 436 scriptPath = sys.path[0] 437 if not scriptPath.endswith('test'): 438 print "This script expects to reside in lldb's test directory." 439 sys.exit(-1) 440 441 if rdir: 442 # Set up the LLDB_TEST environment variable appropriately, so that the 443 # individual tests can be located relatively. 444 # 445 # See also lldbtest.TestBase.setUpClass(cls). 446 if len(testdirs) == 1 and os.path.basename(testdirs[0]) == 'test': 447 os.environ["LLDB_TEST"] = os.path.join(rdir, 'test') 448 else: 449 os.environ["LLDB_TEST"] = rdir 450 else: 451 os.environ["LLDB_TEST"] = scriptPath 452 pluginPath = os.path.join(scriptPath, 'plugins') 453 454 # Append script dir and plugin dir to the sys.path. 455 sys.path.append(scriptPath) 456 sys.path.append(pluginPath) 457 458 global ignore 459 460 # The '-i' option is used to skip looking for lldb.py in the build tree. 461 if ignore: 462 return 463 464 base = os.path.abspath(os.path.join(scriptPath, os.pardir)) 465 dbgPath = os.path.join(base, 'build', 'Debug', 'LLDB.framework', 466 'Resources', 'Python') 467 relPath = os.path.join(base, 'build', 'Release', 'LLDB.framework', 468 'Resources', 'Python') 469 baiPath = os.path.join(base, 'build', 'BuildAndIntegration', 470 'LLDB.framework', 'Resources', 'Python') 471 472 lldbPath = None 473 if os.path.isfile(os.path.join(dbgPath, 'lldb.py')): 474 lldbPath = dbgPath 475 elif os.path.isfile(os.path.join(relPath, 'lldb.py')): 476 lldbPath = relPath 477 elif os.path.isfile(os.path.join(baiPath, 'lldb.py')): 478 lldbPath = baiPath 479 480 if not lldbPath: 481 print 'This script requires lldb.py to be in either ' + dbgPath + ',', 482 print relPath + ', or ' + baiPath 483 sys.exit(-1) 484 485 # This is to locate the lldb.py module. Insert it right after sys.path[0]. 486 sys.path[1:1] = [lldbPath] 487 488 489def doDelay(delta): 490 """Delaying startup for delta-seconds to facilitate debugger attachment.""" 491 def alarm_handler(*args): 492 raise Exception("timeout") 493 494 signal.signal(signal.SIGALRM, alarm_handler) 495 signal.alarm(delta) 496 sys.stdout.write("pid=%d\n" % os.getpid()) 497 sys.stdout.write("Enter RET to proceed (or timeout after %d seconds):" % 498 delta) 499 sys.stdout.flush() 500 try: 501 text = sys.stdin.readline() 502 except: 503 text = "" 504 signal.alarm(0) 505 sys.stdout.write("proceeding...\n") 506 pass 507 508 509def visit(prefix, dir, names): 510 """Visitor function for os.path.walk(path, visit, arg).""" 511 512 global suite 513 global regexp 514 global filterspec 515 global fs4all 516 517 for name in names: 518 if os.path.isdir(os.path.join(dir, name)): 519 continue 520 521 if '.py' == os.path.splitext(name)[1] and name.startswith(prefix): 522 # Try to match the regexp pattern, if specified. 523 if regexp: 524 import re 525 if re.search(regexp, name): 526 #print "Filename: '%s' matches pattern: '%s'" % (name, regexp) 527 pass 528 else: 529 #print "Filename: '%s' does not match pattern: '%s'" % (name, regexp) 530 continue 531 532 # We found a match for our test. Add it to the suite. 533 534 # Update the sys.path first. 535 if not sys.path.count(dir): 536 sys.path.insert(0, dir) 537 base = os.path.splitext(name)[0] 538 539 # Thoroughly check the filterspec against the base module and admit 540 # the (base, filterspec) combination only when it makes sense. 541 if filterspec: 542 # Optimistically set the flag to True. 543 filtered = True 544 module = __import__(base) 545 parts = filterspec.split('.') 546 obj = module 547 for part in parts: 548 try: 549 parent, obj = obj, getattr(obj, part) 550 except AttributeError: 551 # The filterspec has failed. 552 filtered = False 553 break 554 # Forgo this module if the (base, filterspec) combo is invalid 555 # and no '-g' option is specified 556 if fs4all and not filtered: 557 continue 558 559 # Add either the filtered test case or the entire test class. 560 if filterspec and filtered: 561 suite.addTests( 562 unittest2.defaultTestLoader.loadTestsFromName(filterspec, module)) 563 else: 564 # A simple case of just the module name. Also the failover case 565 # from the filterspec branch when the (base, filterspec) combo 566 # doesn't make sense. 567 suite.addTests(unittest2.defaultTestLoader.loadTestsFromName(base)) 568 569 570def lldbLoggings(): 571 """Check and do lldb loggings if necessary.""" 572 573 # Turn on logging for debugging purposes if ${LLDB_LOG} environment variable is 574 # defined. Use ${LLDB_LOG} to specify the log file. 575 ci = lldb.DBG.GetCommandInterpreter() 576 res = lldb.SBCommandReturnObject() 577 if ("LLDB_LOG" in os.environ): 578 if ("LLDB_LOG_OPTION" in os.environ): 579 lldb_log_option = os.environ["LLDB_LOG_OPTION"] 580 else: 581 lldb_log_option = "event process expr state api" 582 ci.HandleCommand( 583 "log enable -T -n -f " + os.environ["LLDB_LOG"] + " lldb " + lldb_log_option, 584 res) 585 if not res.Succeeded(): 586 raise Exception('log enable failed (check LLDB_LOG env variable.') 587 # Ditto for gdb-remote logging if ${GDB_REMOTE_LOG} environment variable is defined. 588 # Use ${GDB_REMOTE_LOG} to specify the log file. 589 if ("GDB_REMOTE_LOG" in os.environ): 590 if ("GDB_REMOTE_LOG_OPTION" in os.environ): 591 gdb_remote_log_option = os.environ["GDB_REMOTE_LOG_OPTION"] 592 else: 593 gdb_remote_log_option = "packets process" 594 ci.HandleCommand( 595 "log enable -T -n -f " + os.environ["GDB_REMOTE_LOG"] + " process.gdb-remote " 596 + gdb_remote_log_option, 597 res) 598 if not res.Succeeded(): 599 raise Exception('log enable failed (check GDB_REMOTE_LOG env variable.') 600 601 602# ======================================== # 603# # 604# Execution of the test driver starts here # 605# # 606# ======================================== # 607 608# 609# Start the actions by first parsing the options while setting up the test 610# directories, followed by setting up the search paths for lldb utilities; 611# then, we walk the directory trees and collect the tests into our test suite. 612# 613parseOptionsAndInitTestdirs() 614setupSysPath() 615 616# 617# If '-d' is specified, do a delay of 10 seconds for the debugger to attach. 618# 619if delay: 620 doDelay(10) 621 622# 623# If '-l' is specified, do not skip the long running tests. 624if not skipLongRunningTest: 625 os.environ["LLDB_SKIP_LONG_RUNNING_TEST"] = "NO" 626 627# 628# Walk through the testdirs while collecting tests. 629# 630for testdir in testdirs: 631 os.path.walk(testdir, visit, 'Test') 632 633# 634# Now that we have loaded all the test cases, run the whole test suite. 635# 636 637# For the time being, let's bracket the test runner within the 638# lldb.SBDebugger.Initialize()/Terminate() pair. 639import lldb, atexit 640# Update: the act of importing lldb now executes lldb.SBDebugger.Initialize(), 641# there's no need to call it a second time. 642#lldb.SBDebugger.Initialize() 643atexit.register(lambda: lldb.SBDebugger.Terminate()) 644 645# Create a singleton SBDebugger in the lldb namespace. 646lldb.DBG = lldb.SBDebugger.Create() 647 648# Put the blacklist in the lldb namespace, to be used by lldb.TestBase. 649lldb.blacklist = blacklist 650 651# Put dont/just_do_python_api_test in the lldb namespace, too. 652lldb.dont_do_python_api_test = dont_do_python_api_test 653lldb.just_do_python_api_test = just_do_python_api_test 654 655# Turn on lldb loggings if necessary. 656lldbLoggings() 657 658# Install the control-c handler. 659unittest2.signals.installHandler() 660 661# If sdir_name is not specified through the '-s sdir_name' option, get a 662# timestamp string and export it as LLDB_SESSION_DIR environment var. This will 663# be used when/if we want to dump the session info of individual test cases 664# later on. 665# 666# See also TestBase.dumpSessionInfo() in lldbtest.py. 667if not sdir_name: 668 import datetime 669 # The windows platforms don't like ':' in the pathname. 670 timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H_%M_%S") 671 sdir_name = timestamp 672os.environ["LLDB_SESSION_DIRNAME"] = sdir_name 673sys.stderr.write("\nSession logs for test failures/errors will go into directory '%s'\n" % sdir_name) 674 675# 676# Invoke the default TextTestRunner to run the test suite, possibly iterating 677# over different configurations. 678# 679 680iterArchs = False 681iterCompilers = False 682 683from types import * 684if "archs" in config: 685 archs = config["archs"] 686 if type(archs) is ListType and len(archs) >= 1: 687 iterArchs = True 688if "compilers" in config: 689 compilers = config["compilers"] 690 if type(compilers) is ListType and len(compilers) >= 1: 691 iterCompilers = True 692 693# Make a shallow copy of sys.path, we need to manipulate the search paths later. 694# This is only necessary if we are relocated and with different configurations. 695if rdir and (iterArchs or iterCompilers): 696 old_sys_path = sys.path[:] 697 old_stderr = sys.stderr 698 old_stdout = sys.stdout 699 new_stderr = None 700 new_stdout = None 701 702# Iterating over all possible architecture and compiler combinations. 703for ia in range(len(archs) if iterArchs else 1): 704 archConfig = "" 705 if iterArchs: 706 os.environ["ARCH"] = archs[ia] 707 archConfig = "arch=%s" % archs[ia] 708 for ic in range(len(compilers) if iterCompilers else 1): 709 if iterCompilers: 710 os.environ["CC"] = compilers[ic] 711 configString = "%s compiler=%s" % (archConfig, compilers[ic]) 712 else: 713 configString = archConfig 714 715 if iterArchs or iterCompilers: 716 # If we specified a relocated directory to run the test suite, do 717 # the extra housekeeping to copy the testdirs to a configStringified 718 # directory and to update sys.path before invoking the test runner. 719 # The purpose is to separate the configuration-specific directories 720 # from each other. 721 if rdir: 722 from string import maketrans 723 from shutil import copytree, ignore_patterns 724 725 # Translate ' ' to '-' for dir name. 726 tbl = maketrans(' ', '-') 727 configPostfix = configString.translate(tbl) 728 newrdir = "%s.%s" % (rdir, configPostfix) 729 730 # Copy the tree to a new directory with postfix name configPostfix. 731 copytree(rdir, newrdir, ignore=ignore_patterns('*.pyc', '*.o', '*.d')) 732 733 # Check whether we need to split stderr/stdout into configuration 734 # specific files. 735 if old_stderr.name != '<stderr>' and config.get('split_stderr'): 736 if new_stderr: 737 new_stderr.close() 738 new_stderr = open("%s.%s" % (old_stderr.name, configPostfix), "w") 739 sys.stderr = new_stderr 740 if old_stdout.name != '<stdout>' and config.get('split_stdout'): 741 if new_stdout: 742 new_stdout.close() 743 new_stdout = open("%s.%s" % (old_stdout.name, configPostfix), "w") 744 sys.stdout = new_stdout 745 746 # Update the LLDB_TEST environment variable to reflect new top 747 # level test directory. 748 # 749 # See also lldbtest.TestBase.setUpClass(cls). 750 if len(testdirs) == 1 and os.path.basename(testdirs[0]) == 'test': 751 os.environ["LLDB_TEST"] = os.path.join(newrdir, 'test') 752 else: 753 os.environ["LLDB_TEST"] = newrdir 754 755 # And update the Python search paths for modules. 756 sys.path = [x.replace(rdir, newrdir, 1) for x in old_sys_path] 757 758 # Output the configuration. 759 sys.stderr.write("\nConfiguration: " + configString + "\n") 760 761 #print "sys.stderr name is", sys.stderr.name 762 #print "sys.stdout name is", sys.stdout.name 763 764 # First, write out the number of collected test cases. 765 sys.stderr.write(separator + "\n") 766 sys.stderr.write("Collected %d test%s\n\n" 767 % (suite.countTestCases(), 768 suite.countTestCases() != 1 and "s" or "")) 769 770 class LLDBTestResult(unittest2.TextTestResult): 771 """ 772 Enforce a singleton pattern to allow introspection of test progress. 773 774 Overwrite addError(), addFailure(), and addExpectedFailure() methods 775 to enable each test instance to track its failure/error status. It 776 is used in the LLDB test framework to emit detailed trace messages 777 to a log file for easier human inspection of test failres/errors. 778 """ 779 __singleton__ = None 780 __ignore_singleton__ = False 781 782 def __init__(self, *args): 783 if not LLDBTestResult.__ignore_singleton__ and LLDBTestResult.__singleton__: 784 raise Exception("LLDBTestResult instantiated more than once") 785 super(LLDBTestResult, self).__init__(*args) 786 LLDBTestResult.__singleton__ = self 787 # Now put this singleton into the lldb module namespace. 788 lldb.test_result = self 789 790 def addError(self, test, err): 791 global sdir_has_content 792 sdir_has_content = True 793 super(LLDBTestResult, self).addError(test, err) 794 method = getattr(test, "markError", None) 795 if method: 796 method() 797 798 def addFailure(self, test, err): 799 global sdir_has_content 800 sdir_has_content = True 801 super(LLDBTestResult, self).addFailure(test, err) 802 method = getattr(test, "markFailure", None) 803 if method: 804 method() 805 806 def addExpectedFailure(self, test, err): 807 global sdir_has_content 808 sdir_has_content = True 809 super(LLDBTestResult, self).addExpectedFailure(test, err) 810 method = getattr(test, "markExpectedFailure", None) 811 if method: 812 method() 813 814 # Invoke the test runner. 815 if count == 1: 816 result = unittest2.TextTestRunner(stream=sys.stderr, 817 verbosity=verbose, 818 failfast=failfast, 819 resultclass=LLDBTestResult).run(suite) 820 else: 821 # We are invoking the same test suite more than once. In this case, 822 # mark __ignore_singleton__ flag as True so the signleton pattern is 823 # not enforced. 824 LLDBTestResult.__ignore_singleton__ = True 825 for i in range(count): 826 result = unittest2.TextTestRunner(stream=sys.stderr, 827 verbosity=verbose, 828 failfast=failfast, 829 resultclass=LLDBTestResult).run(suite) 830 831 832if sdir_has_content: 833 sys.stderr.write("Session logs for test failures/errors can be found in directory '%s'\n" % sdir_name) 834 835# Terminate the test suite if ${LLDB_TESTSUITE_FORCE_FINISH} is defined. 836# This should not be necessary now. 837if ("LLDB_TESTSUITE_FORCE_FINISH" in os.environ): 838 import subprocess 839 print "Terminating Test suite..." 840 subprocess.Popen(["/bin/sh", "-c", "kill %s; exit 0" % (os.getpid())]) 841 842# Exiting. 843sys.exit(not result.wasSuccessful) 844