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