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