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