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