dotest.py revision b5fe80c62f0de8b855e0ee4bf25d3325f52e1984
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    """Returns true if fpath is an executable."""
28    return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
29
30def which(program):
31    """Returns the full path to a program; None otherwise."""
32    fpath, fname = os.path.split(program)
33    if fpath:
34        if is_exe(program):
35            return program
36    else:
37        for path in os.environ["PATH"].split(os.pathsep):
38            exe_file = os.path.join(path, program)
39            if is_exe(exe_file):
40                return exe_file
41    return None
42
43class _WritelnDecorator(object):
44    """Used to decorate file-like objects with a handy 'writeln' method"""
45    def __init__(self,stream):
46        self.stream = stream
47
48    def __getattr__(self, attr):
49        if attr in ('stream', '__getstate__'):
50            raise AttributeError(attr)
51        return getattr(self.stream,attr)
52
53    def writeln(self, arg=None):
54        if arg:
55            self.write(arg)
56        self.write('\n') # text-mode streams translate to \r\n if needed
57
58#
59# Global variables:
60#
61
62# The test suite.
63suite = unittest2.TestSuite()
64
65# By default, both command line and Python API tests are performed.
66# Use @python_api_test decorator, defined in lldbtest.py, to mark a test as
67# a Python API test.
68dont_do_python_api_test = False
69
70# By default, both command line and Python API tests are performed.
71just_do_python_api_test = False
72
73# The blacklist is optional (-b blacklistFile) and allows a central place to skip
74# testclass's and/or testclass.testmethod's.
75blacklist = None
76
77# The dictionary as a result of sourcing blacklistFile.
78blacklistConfig = {}
79
80# The config file is optional.
81configFile = None
82
83# Test suite repeat count.  Can be overwritten with '-# count'.
84count = 1
85
86# The dictionary as a result of sourcing configFile.
87config = {}
88
89# The 'archs' and 'compilers' can be specified via either command line or configFile,
90# with the command line overriding the configFile.  When specified, they should be
91# of the list type.  For example, "-A x86_64^i386" => archs=['x86_64', 'i386'] and
92# "-C gcc^clang" => compilers=['gcc', 'clang'].
93archs = None
94compilers = None
95
96# Delay startup in order for the debugger to attach.
97delay = False
98
99# Dump the Python sys.path variable.  Use '-D' to dump sys.path.
100dumpSysPath = False
101
102# By default, failfast is False.  Use '-F' to overwrite it.
103failfast = False
104
105# The filter (testclass.testmethod) used to admit tests into our test suite.
106filterspec = None
107
108# If '-g' is specified, the filterspec is not exclusive.  If a test module does
109# not contain testclass.testmethod which matches the filterspec, the whole test
110# module is still admitted into our test suite.  fs4all flag defaults to True.
111fs4all = True
112
113# Ignore the build search path relative to this script to locate the lldb.py module.
114ignore = False
115
116# By default, we skip long running test case.  Use '-l' option to override.
117skipLongRunningTest = True
118
119# The regular expression pattern to match against eligible filenames as our test cases.
120regexp = None
121
122# By default, tests are executed in place and cleanups are performed afterwards.
123# Use '-r dir' option to relocate the tests and their intermediate files to a
124# different directory and to forgo any cleanups.  The directory specified must
125# not exist yet.
126rdir = None
127
128# By default, recorded session info for errored/failed test are dumped into its
129# own file under a session directory named after the timestamp of the test suite
130# run.  Use '-s session-dir-name' to specify a specific dir name.
131sdir_name = None
132
133# Set this flag if there is any session info dumped during the test run.
134sdir_has_content = False
135
136# svn_info stores the output from 'svn info lldb.base.dir'.
137svn_info = ''
138
139# Default verbosity is 0.
140verbose = 0
141
142# By default, search from the current working directory.
143testdirs = [ os.getcwd() ]
144
145# Separator string.
146separator = '-' * 70
147
148
149def usage():
150    print """
151Usage: dotest.py [option] [args]
152where options:
153-h   : print this help message and exit.  Add '-v' for more detailed help.
154-A   : specify the architecture(s) to launch for the inferior process
155       -A i386 => launch inferior with i386 architecture
156       -A x86_64^i386 => launch inferior with x86_64 and i386 architectures
157-C   : specify the compiler(s) used to build the inferior executable
158       -C clang => build debuggee using clang compiler
159       -C clang^gcc => build debuggee using clang and gcc compilers
160-D   : dump the Python sys.path variable
161-a   : don't do lldb Python API tests
162       use @python_api_test to decorate a test case as lldb Python API test
163+a   : just do lldb Python API tests
164       do not specify both '-a' and '+a' at the same time
165-b   : read a blacklist file specified after this option
166-c   : read a config file specified after this option
167       the architectures and compilers (note the plurals) specified via '-A' and '-C'
168       will override those specified via a config file
169       (see also lldb-trunk/example/test/usage-config)
170-d   : delay startup for 10 seconds (in order for the debugger to attach)
171-F   : failfast, stop the test suite on the first error/failure
172-f   : specify a filter, which consists of the test class name, a dot, followed by
173       the test method, to only admit such test into the test suite
174       e.g., -f 'ClassTypesTestCase.test_with_dwarf_and_python_api'
175-g   : if specified, the filterspec by -f is not exclusive, i.e., if a test module
176       does not match the filterspec (testclass.testmethod), the whole module is
177       still admitted to the test suite
178-i   : ignore (don't bailout) if 'lldb.py' module cannot be located in the build
179       tree relative to this script; use PYTHONPATH to locate the module
180-l   : don't skip long running test
181-p   : specify a regexp filename pattern for inclusion in the test suite
182-r   : specify a dir to relocate the tests and their intermediate files to;
183       the directory must not exist before running this test driver;
184       no cleanup of intermediate test files is performed in this case
185-s   : specify the name of the dir created to store the session files of tests
186       with errored or failed status; if not specified, the test driver uses the
187       timestamp as the session dir name
188-t   : turn on tracing of lldb command and other detailed test executions
189-v   : do verbose mode of unittest framework (print out each test case invocation)
190-w   : insert some wait time (currently 0.5 sec) between consecutive test cases
191-#   : Repeat the test suite for a specified number of times
192
193and:
194args : specify a list of directory names to search for test modules named after
195       Test*.py (test discovery)
196       if empty, search from the curret working directory, instead
197"""
198
199    if verbose > 0:
200        print """
201Examples:
202
203This is an example of using the -f option to pinpoint to a specfic test class
204and test method to be run:
205
206$ ./dotest.py -f ClassTypesTestCase.test_with_dsym_and_run_command
207----------------------------------------------------------------------
208Collected 1 test
209
210test_with_dsym_and_run_command (TestClassTypes.ClassTypesTestCase)
211Test 'frame variable this' when stopped on a class constructor. ... ok
212
213----------------------------------------------------------------------
214Ran 1 test in 1.396s
215
216OK
217
218And this is an example of using the -p option to run a single file (the filename
219matches the pattern 'ObjC' and it happens to be 'TestObjCMethods.py'):
220
221$ ./dotest.py -v -p ObjC
222----------------------------------------------------------------------
223Collected 4 tests
224
225test_break_with_dsym (TestObjCMethods.FoundationTestCase)
226Test setting objc breakpoints using '_regexp-break' and 'breakpoint set'. ... ok
227test_break_with_dwarf (TestObjCMethods.FoundationTestCase)
228Test setting objc breakpoints using '_regexp-break' and 'breakpoint set'. ... ok
229test_data_type_and_expr_with_dsym (TestObjCMethods.FoundationTestCase)
230Lookup objective-c data types and evaluate expressions. ... ok
231test_data_type_and_expr_with_dwarf (TestObjCMethods.FoundationTestCase)
232Lookup objective-c data types and evaluate expressions. ... ok
233
234----------------------------------------------------------------------
235Ran 4 tests in 16.661s
236
237OK
238
239Running of this script also sets up the LLDB_TEST environment variable so that
240individual test cases can locate their supporting files correctly.  The script
241tries to set up Python's search paths for modules by looking at the build tree
242relative to this script.  See also the '-i' option in the following example.
243
244Finally, this is an example of using the lldb.py module distributed/installed by
245Xcode4 to run against the tests under the 'forward' directory, and with the '-w'
246option to add some delay between two tests.  It uses ARCH=x86_64 to specify that
247as the architecture and CC=clang to specify the compiler used for the test run:
248
249$ PYTHONPATH=/Xcode4/Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python ARCH=x86_64 CC=clang ./dotest.py -v -w -i forward
250
251Session logs for test failures/errors will go into directory '2010-11-11-13_56_16'
252----------------------------------------------------------------------
253Collected 2 tests
254
255test_with_dsym_and_run_command (TestForwardDeclaration.ForwardDeclarationTestCase)
256Display *bar_ptr when stopped on a function with forward declaration of struct bar. ... ok
257test_with_dwarf_and_run_command (TestForwardDeclaration.ForwardDeclarationTestCase)
258Display *bar_ptr when stopped on a function with forward declaration of struct bar. ... ok
259
260----------------------------------------------------------------------
261Ran 2 tests in 5.659s
262
263OK
264
265The 'Session ...' verbiage is recently introduced (see also the '-s' option) to
266notify the directory containing the session logs for test failures or errors.
267In case there is any test failure/error, a similar message is appended at the
268end of the stderr output for your convenience.
269
270Environment variables related to loggings:
271
272o LLDB_LOG: if defined, specifies the log file pathname for the 'lldb' subsystem
273  with a default option of 'event process' if LLDB_LOG_OPTION is not defined.
274
275o GDB_REMOTE_LOG: if defined, specifies the log file pathname for the
276  'process.gdb-remote' subsystem with a default option of 'packets' if
277  GDB_REMOTE_LOG_OPTION is not defined.
278"""
279    sys.exit(0)
280
281
282def parseOptionsAndInitTestdirs():
283    """Initialize the list of directories containing our unittest scripts.
284
285    '-h/--help as the first option prints out usage info and exit the program.
286    """
287
288    global dont_do_python_api_test
289    global just_do_python_api_test
290    global blacklist
291    global blacklistConfig
292    global configFile
293    global archs
294    global compilers
295    global count
296    global delay
297    global dumpSysPath
298    global failfast
299    global filterspec
300    global fs4all
301    global ignore
302    global skipLongRunningTest
303    global regexp
304    global rdir
305    global sdir_name
306    global verbose
307    global testdirs
308
309    do_help = False
310
311    if len(sys.argv) == 1:
312        return
313
314    # Process possible trace and/or verbose flag, among other things.
315    index = 1
316    while index < len(sys.argv):
317        if sys.argv[index].startswith('-') or sys.argv[index].startswith('+'):
318            # We should continue processing...
319            pass
320        else:
321            # End of option processing.
322            break
323
324        if sys.argv[index].find('-h') != -1:
325            index += 1
326            do_help = True
327        elif sys.argv[index].startswith('-A'):
328            # Increment by 1 to fetch the ARCH spec.
329            index += 1
330            if index >= len(sys.argv) or sys.argv[index].startswith('-'):
331                usage()
332            archs = sys.argv[index].split('^')
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            compilers = sys.argv[index].split('^')
340            index += 1
341        elif sys.argv[index].startswith('-D'):
342            dumpSysPath = True
343            index += 1
344        elif sys.argv[index].startswith('-a'):
345            dont_do_python_api_test = True
346            index += 1
347        elif sys.argv[index].startswith('+a'):
348            just_do_python_api_test = True
349            index += 1
350        elif sys.argv[index].startswith('-b'):
351            # Increment by 1 to fetch the blacklist file name option argument.
352            index += 1
353            if index >= len(sys.argv) or sys.argv[index].startswith('-'):
354                usage()
355            blacklistFile = sys.argv[index]
356            if not os.path.isfile(blacklistFile):
357                print "Blacklist file:", blacklistFile, "does not exist!"
358                usage()
359            index += 1
360            # Now read the blacklist contents and assign it to blacklist.
361            execfile(blacklistFile, globals(), blacklistConfig)
362            blacklist = blacklistConfig.get('blacklist')
363        elif sys.argv[index].startswith('-c'):
364            # Increment by 1 to fetch the config file name option argument.
365            index += 1
366            if index >= len(sys.argv) or sys.argv[index].startswith('-'):
367                usage()
368            configFile = sys.argv[index]
369            if not os.path.isfile(configFile):
370                print "Config file:", configFile, "does not exist!"
371                usage()
372            index += 1
373        elif sys.argv[index].startswith('-d'):
374            delay = True
375            index += 1
376        elif sys.argv[index].startswith('-F'):
377            failfast = True
378            index += 1
379        elif sys.argv[index].startswith('-f'):
380            # Increment by 1 to fetch the filter spec.
381            index += 1
382            if index >= len(sys.argv) or sys.argv[index].startswith('-'):
383                usage()
384            filterspec = sys.argv[index]
385            index += 1
386        elif sys.argv[index].startswith('-g'):
387            fs4all = False
388            index += 1
389        elif sys.argv[index].startswith('-i'):
390            ignore = True
391            index += 1
392        elif sys.argv[index].startswith('-l'):
393            skipLongRunningTest = False
394            index += 1
395        elif sys.argv[index].startswith('-p'):
396            # Increment by 1 to fetch the reg exp pattern argument.
397            index += 1
398            if index >= len(sys.argv) or sys.argv[index].startswith('-'):
399                usage()
400            regexp = sys.argv[index]
401            index += 1
402        elif sys.argv[index].startswith('-r'):
403            # Increment by 1 to fetch the relocated directory argument.
404            index += 1
405            if index >= len(sys.argv) or sys.argv[index].startswith('-'):
406                usage()
407            rdir = os.path.abspath(sys.argv[index])
408            if os.path.exists(rdir):
409                print "Relocated directory:", rdir, "must not exist!"
410                usage()
411            index += 1
412        elif sys.argv[index].startswith('-s'):
413            # Increment by 1 to fetch the session dir name.
414            index += 1
415            if index >= len(sys.argv) or sys.argv[index].startswith('-'):
416                usage()
417            sdir_name = sys.argv[index]
418            index += 1
419        elif sys.argv[index].startswith('-t'):
420            os.environ["LLDB_COMMAND_TRACE"] = "YES"
421            index += 1
422        elif sys.argv[index].startswith('-v'):
423            verbose = 2
424            index += 1
425        elif sys.argv[index].startswith('-w'):
426            os.environ["LLDB_WAIT_BETWEEN_TEST_CASES"] = 'YES'
427            index += 1
428        elif sys.argv[index].startswith('-#'):
429            # Increment by 1 to fetch the repeat count argument.
430            index += 1
431            if index >= len(sys.argv) or sys.argv[index].startswith('-'):
432                usage()
433            count = int(sys.argv[index])
434            index += 1
435        else:
436            print "Unknown option: ", sys.argv[index]
437            usage()
438
439    if do_help == True:
440        usage()
441
442    # Do not specify both '-a' and '+a' at the same time.
443    if dont_do_python_api_test and just_do_python_api_test:
444        usage()
445
446    # Gather all the dirs passed on the command line.
447    if len(sys.argv) > index:
448        testdirs = map(os.path.abspath, sys.argv[index:])
449
450    # If '-r dir' is specified, the tests should be run under the relocated
451    # directory.  Let's copy the testdirs over.
452    if rdir:
453        from shutil import copytree, ignore_patterns
454
455        tmpdirs = []
456        for srcdir in testdirs:
457            dstdir = os.path.join(rdir, os.path.basename(srcdir))
458            # Don't copy the *.pyc and .svn stuffs.
459            copytree(srcdir, dstdir, ignore=ignore_patterns('*.pyc', '.svn'))
460            tmpdirs.append(dstdir)
461
462        # This will be our modified testdirs.
463        testdirs = tmpdirs
464
465        # With '-r dir' specified, there's no cleanup of intermediate test files.
466        os.environ["LLDB_DO_CLEANUP"] = 'NO'
467
468        # If testdirs is ['test'], the make directory has already been copied
469        # recursively and is contained within the rdir/test dir.  For anything
470        # else, we would need to copy over the make directory and its contents,
471        # so that, os.listdir(rdir) looks like, for example:
472        #
473        #     array_types conditional_break make
474        #
475        # where the make directory contains the Makefile.rules file.
476        if len(testdirs) != 1 or os.path.basename(testdirs[0]) != 'test':
477            # Don't copy the .svn stuffs.
478            copytree('make', os.path.join(rdir, 'make'),
479                     ignore=ignore_patterns('.svn'))
480
481    #print "testdirs:", testdirs
482
483    # Source the configFile if specified.
484    # The side effect, if any, will be felt from this point on.  An example
485    # config file may be these simple two lines:
486    #
487    # sys.stderr = open("/tmp/lldbtest-stderr", "w")
488    # sys.stdout = open("/tmp/lldbtest-stdout", "w")
489    #
490    # which will reassign the two file objects to sys.stderr and sys.stdout,
491    # respectively.
492    #
493    # See also lldb-trunk/example/test/usage-config.
494    global config
495    if configFile:
496        # Pass config (a dictionary) as the locals namespace for side-effect.
497        execfile(configFile, globals(), config)
498        #print "config:", config
499        #print "sys.stderr:", sys.stderr
500        #print "sys.stdout:", sys.stdout
501
502
503def setupSysPath():
504    """
505    Add LLDB.framework/Resources/Python to the search paths for modules.
506    As a side effect, we also discover the 'lldb' executable and export it here.
507    """
508
509    global rdir
510    global testdirs
511    global dumpSysPath
512    global svn_info
513
514    # Get the directory containing the current script.
515    if "DOTEST_PROFILE" in os.environ and "DOTEST_SCRIPT_DIR" in os.environ:
516        scriptPath = os.environ["DOTEST_SCRIPT_DIR"]
517    else:
518        scriptPath = sys.path[0]
519    if not scriptPath.endswith('test'):
520        print "This script expects to reside in lldb's test directory."
521        sys.exit(-1)
522
523    if rdir:
524        # Set up the LLDB_TEST environment variable appropriately, so that the
525        # individual tests can be located relatively.
526        #
527        # See also lldbtest.TestBase.setUpClass(cls).
528        if len(testdirs) == 1 and os.path.basename(testdirs[0]) == 'test':
529            os.environ["LLDB_TEST"] = os.path.join(rdir, 'test')
530        else:
531            os.environ["LLDB_TEST"] = rdir
532    else:
533        os.environ["LLDB_TEST"] = scriptPath
534    pluginPath = os.path.join(scriptPath, 'plugins')
535    pexpectPath = os.path.join(scriptPath, 'pexpect-2.4')
536
537    # Append script dir, plugin dir, and pexpect dir to the sys.path.
538    sys.path.append(scriptPath)
539    sys.path.append(pluginPath)
540    sys.path.append(pexpectPath)
541
542    # This is our base name component.
543    base = os.path.abspath(os.path.join(scriptPath, os.pardir))
544
545    # These are for xcode build directories.
546    xcode3_build_dir = ['build']
547    xcode4_build_dir = ['build', 'lldb', 'Build', 'Products']
548    dbg = ['Debug']
549    rel = ['Release']
550    bai = ['BuildAndIntegration']
551    python_resource_dir = ['LLDB.framework', 'Resources', 'Python']
552
553    # Some of the tests can invoke the 'lldb' command directly.
554    # We'll try to locate the appropriate executable right here.
555
556    executable = ['lldb']
557    dbgExec  = os.path.join(base, *(xcode3_build_dir + dbg + executable))
558    dbgExec2 = os.path.join(base, *(xcode4_build_dir + dbg + executable))
559    relExec  = os.path.join(base, *(xcode3_build_dir + rel + executable))
560    relExec2 = os.path.join(base, *(xcode4_build_dir + rel + executable))
561    baiExec  = os.path.join(base, *(xcode3_build_dir + bai + executable))
562    baiExec2 = os.path.join(base, *(xcode4_build_dir + bai + executable))
563
564    lldbExec = None
565    if is_exe(dbgExec):
566        lldbExec = dbgExec
567    elif is_exe(dbgExec2):
568        lldbExec = dbgExec2
569    elif is_exe(relExec):
570        lldbExec = relExec
571    elif is_exe(relExec2):
572        lldbExec = relExec2
573    elif is_exe(baiExec):
574        lldbExec = baiExec
575    elif is_exe(baiExec2):
576        lldbExec = baiExec2
577
578    if not lldbExec:
579        lldbExec = which('lldb')
580
581    if not lldbExec:
582        print "The 'lldb' executable cannot be located.  Some of the tests may not be run as a result."
583    else:
584        os.environ["LLDB_EXEC"] = lldbExec
585        #print "The 'lldb' executable path is", lldbExec
586        os.system('%s -v' % lldbExec)
587
588    import subprocess
589    pipe = subprocess.Popen(["svn", "info", base], stdout = subprocess.PIPE)
590    svn_info = pipe.stdout.read()
591    print svn_info
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/unexpected successes"
823                 " will go into directory '%s'\n" % sdir_name)
824sys.stderr.write("Command invoked: %s\n" % getMyCommandLine())
825
826if not os.path.isdir(sdir_name):
827    os.mkdir(sdir_name)
828fname = os.path.join(sdir_name, "svn-info")
829with open(fname, "w") as f:
830    print >> f, svn_info
831    print >> f, "Command invoked: %s\n" % getMyCommandLine()
832
833#
834# Invoke the default TextTestRunner to run the test suite, possibly iterating
835# over different configurations.
836#
837
838iterArchs = False
839iterCompilers = False
840
841if not archs and "archs" in config:
842    archs = config["archs"]
843
844if isinstance(archs, list) and len(archs) >= 1:
845    iterArchs = True
846
847if not compilers and "compilers" in config:
848    compilers = config["compilers"]
849
850if isinstance(compilers, list) and len(compilers) >= 1:
851    iterCompilers = True
852
853# Make a shallow copy of sys.path, we need to manipulate the search paths later.
854# This is only necessary if we are relocated and with different configurations.
855if rdir:
856    old_sys_path = sys.path[:]
857# If we iterate on archs or compilers, there is a chance we want to split stderr/stdout.
858if iterArchs or iterCompilers:
859    old_stderr = sys.stderr
860    old_stdout = sys.stdout
861    new_stderr = None
862    new_stdout = None
863
864# Iterating over all possible architecture and compiler combinations.
865for ia in range(len(archs) if iterArchs else 1):
866    archConfig = ""
867    if iterArchs:
868        os.environ["ARCH"] = archs[ia]
869        archConfig = "arch=%s" % archs[ia]
870    for ic in range(len(compilers) if iterCompilers else 1):
871        if iterCompilers:
872            os.environ["CC"] = compilers[ic]
873            configString = "%s compiler=%s" % (archConfig, compilers[ic])
874        else:
875            configString = archConfig
876
877        if iterArchs or iterCompilers:
878            # Translate ' ' to '-' for pathname component.
879            from string import maketrans
880            tbl = maketrans(' ', '-')
881            configPostfix = configString.translate(tbl)
882
883            # Check whether we need to split stderr/stdout into configuration
884            # specific files.
885            if old_stderr.name != '<stderr>' and config.get('split_stderr'):
886                if new_stderr:
887                    new_stderr.close()
888                new_stderr = open("%s.%s" % (old_stderr.name, configPostfix), "w")
889                sys.stderr = new_stderr
890            if old_stdout.name != '<stdout>' and config.get('split_stdout'):
891                if new_stdout:
892                    new_stdout.close()
893                new_stdout = open("%s.%s" % (old_stdout.name, configPostfix), "w")
894                sys.stdout = new_stdout
895
896            # If we specified a relocated directory to run the test suite, do
897            # the extra housekeeping to copy the testdirs to a configStringified
898            # directory and to update sys.path before invoking the test runner.
899            # The purpose is to separate the configuration-specific directories
900            # from each other.
901            if rdir:
902                from shutil import copytree, ignore_patterns
903
904                newrdir = "%s.%s" % (rdir, configPostfix)
905
906                # Copy the tree to a new directory with postfix name configPostfix.
907                copytree(rdir, newrdir, ignore=ignore_patterns('*.pyc', '*.o', '*.d'))
908
909               # Update the LLDB_TEST environment variable to reflect new top
910                # level test directory.
911                #
912                # See also lldbtest.TestBase.setUpClass(cls).
913                if len(testdirs) == 1 and os.path.basename(testdirs[0]) == 'test':
914                    os.environ["LLDB_TEST"] = os.path.join(newrdir, 'test')
915                else:
916                    os.environ["LLDB_TEST"] = newrdir
917
918                # And update the Python search paths for modules.
919                sys.path = [x.replace(rdir, newrdir, 1) for x in old_sys_path]
920
921            # Output the configuration.
922            sys.stderr.write("\nConfiguration: " + configString + "\n")
923
924        #print "sys.stderr name is", sys.stderr.name
925        #print "sys.stdout name is", sys.stdout.name
926
927        # First, write out the number of collected test cases.
928        sys.stderr.write(separator + "\n")
929        sys.stderr.write("Collected %d test%s\n\n"
930                         % (suite.countTestCases(),
931                            suite.countTestCases() != 1 and "s" or ""))
932
933        class LLDBTestResult(unittest2.TextTestResult):
934            """
935            Enforce a singleton pattern to allow introspection of test progress.
936
937            Overwrite addError(), addFailure(), and addExpectedFailure() methods
938            to enable each test instance to track its failure/error status.  It
939            is used in the LLDB test framework to emit detailed trace messages
940            to a log file for easier human inspection of test failres/errors.
941            """
942            __singleton__ = None
943            __ignore_singleton__ = False
944
945            def __init__(self, *args):
946                if not LLDBTestResult.__ignore_singleton__ and LLDBTestResult.__singleton__:
947                    raise Exception("LLDBTestResult instantiated more than once")
948                super(LLDBTestResult, self).__init__(*args)
949                LLDBTestResult.__singleton__ = self
950                # Now put this singleton into the lldb module namespace.
951                lldb.test_result = self
952                # Computes the format string for displaying the counter.
953                global suite
954                counterWidth = len(str(suite.countTestCases()))
955                self.fmt = "%" + str(counterWidth) + "d: "
956                self.indentation = ' ' * (counterWidth + 2)
957                # This counts from 1 .. suite.countTestCases().
958                self.counter = 0
959
960            def getDescription(self, test):
961                doc_first_line = test.shortDescription()
962                if self.descriptions and doc_first_line:
963                    return '\n'.join((str(test), self.indentation + doc_first_line))
964                else:
965                    return str(test)
966
967            def startTest(self, test):
968                self.counter += 1
969                if self.showAll:
970                    self.stream.write(self.fmt % self.counter)
971                super(LLDBTestResult, self).startTest(test)
972
973            def addError(self, test, err):
974                global sdir_has_content
975                sdir_has_content = True
976                super(LLDBTestResult, self).addError(test, err)
977                method = getattr(test, "markError", None)
978                if method:
979                    method()
980
981            def addFailure(self, test, err):
982                global sdir_has_content
983                sdir_has_content = True
984                super(LLDBTestResult, self).addFailure(test, err)
985                method = getattr(test, "markFailure", None)
986                if method:
987                    method()
988
989            def addExpectedFailure(self, test, err):
990                global sdir_has_content
991                sdir_has_content = True
992                super(LLDBTestResult, self).addExpectedFailure(test, err)
993                method = getattr(test, "markExpectedFailure", None)
994                if method:
995                    method()
996
997            def addUnexpectedSuccess(self, test):
998                global sdir_has_content
999                sdir_has_content = True
1000                super(LLDBTestResult, self).addUnexpectedSuccess(test)
1001                method = getattr(test, "markUnexpectedSuccess", None)
1002                if method:
1003                    method()
1004
1005        # Invoke the test runner.
1006        if count == 1:
1007            result = unittest2.TextTestRunner(stream=sys.stderr,
1008                                              verbosity=verbose,
1009                                              failfast=failfast,
1010                                              resultclass=LLDBTestResult).run(suite)
1011        else:
1012            # We are invoking the same test suite more than once.  In this case,
1013            # mark __ignore_singleton__ flag as True so the signleton pattern is
1014            # not enforced.
1015            LLDBTestResult.__ignore_singleton__ = True
1016            for i in range(count):
1017                result = unittest2.TextTestRunner(stream=sys.stderr,
1018                                                  verbosity=verbose,
1019                                                  failfast=failfast,
1020                                                  resultclass=LLDBTestResult).run(suite)
1021
1022
1023if sdir_has_content:
1024    sys.stderr.write("Session logs for test failures/errors/unexpected successes"
1025                     " can be found in directory '%s'\n" % sdir_name)
1026
1027# Terminate the test suite if ${LLDB_TESTSUITE_FORCE_FINISH} is defined.
1028# This should not be necessary now.
1029if ("LLDB_TESTSUITE_FORCE_FINISH" in os.environ):
1030    import subprocess
1031    print "Terminating Test suite..."
1032    subprocess.Popen(["/bin/sh", "-c", "kill %s; exit 0" % (os.getpid())])
1033
1034# Exiting.
1035sys.exit(not result.wasSuccessful)
1036