summaryrefslogtreecommitdiffstats
path: root/runtest.py
diff options
context:
space:
mode:
authorWilliam Deegan <bill@baddogconsulting.com>2015-09-21 17:03:12 (GMT)
committerWilliam Deegan <bill@baddogconsulting.com>2015-09-21 17:03:12 (GMT)
commit0941093e0e5a030faa49968457638a3a6aee7ad8 (patch)
tree6d33513c14eb6eac0531dd050de0ecca4c39bd79 /runtest.py
downloadSCons-2.4.0.zip
SCons-2.4.0.tar.gz
SCons-2.4.0.tar.bz2
release 2.4.02.4.0
Diffstat (limited to 'runtest.py')
-rwxr-xr-xruntest.py909
1 files changed, 909 insertions, 0 deletions
diff --git a/runtest.py b/runtest.py
new file mode 100755
index 0000000..b7cbdc6
--- /dev/null
+++ b/runtest.py
@@ -0,0 +1,909 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# runtest.py - wrapper script for running SCons tests
+#
+# SCons test suite consists of:
+#
+# - unit tests - included in *Tests.py files from src/ dir
+# - end-to-end tests - these are *.py files in test/ directory that
+# require custom SCons framework from QMTest/
+#
+# This script adds src/ and QMTest/ directories to PYTHONPATH,
+# performs test discovery and processes them according to options.
+#
+# With -p (--package) option, script tests specified package from
+# build directory and sets PYTHONPATH to reference modules unpacked
+# during build process for testing purposes (build/test-*).
+#
+# -3 Run with the python -3 option,
+#
+# -a Run all tests found under the current directory.
+# It is also possible to specify a list of
+# subdirectories to search.
+#
+# -d Debug. Runs the script under the Python
+# debugger (pdb.py) so you don't have to
+# muck with PYTHONPATH yourself.
+#
+# -e Starts the script in external mode, for
+# testing separate Tools and packages.
+#
+# -f file Only execute the tests listed in the specified
+# file.
+#
+# -k Suppress printing of count and percent progress for
+# the single tests.
+#
+# -l List available tests and exit.
+#
+# -n No execute, just print command lines.
+#
+# -o file Save screen output to the specified log file.
+#
+# -P Python Use the specified Python interpreter.
+#
+# -p package Test against the specified package.
+#
+# --passed In the final summary, also report which tests
+# passed. The default is to only report tests
+# which failed or returned NO RESULT.
+#
+# -q Quiet. By default, runtest.py prints the
+# command line it will execute before
+# executing it. This suppresses that print.
+#
+# --quit-on-failure Quit on any test failure
+#
+# -s Short progress. Prints only the command line
+# and a percentage value, based on the total and
+# current number of tests.
+# All stdout and stderr messages get suppressed (this
+# does only work with subprocess though)!
+#
+# -t Print the execution time of each test.
+#
+# -X The scons "script" is an executable; don't
+# feed it to Python.
+#
+# -x scons The scons script to use for tests.
+#
+# --xml file Save test results to the specified file in an
+# SCons-specific XML format.
+# This is (will be) used for reporting results back
+# to a central SCons test monitoring infrastructure.
+#
+# (Note: There used to be a -v option that specified the SCons
+# version to be tested, when we were installing in a version-specific
+# library directory. If we ever resurrect that as the default, then
+# you can find the appropriate code in the 0.04 version of this script,
+# rather than reinventing that wheel.)
+
+import getopt
+import glob
+import os
+import re
+import stat
+import sys
+import time
+
+try:
+ import threading
+ import Queue # 2to3: rename to queue
+ threading_ok = True
+except ImportError:
+ print "Can't import threading or queue"
+ threading_ok = False
+
+cwd = os.getcwd()
+
+baseline = 0
+builddir = os.path.join(cwd, 'build')
+external = 0
+debug = ''
+execute_tests = 1
+jobs = 1
+list_only = None
+printcommand = 1
+package = None
+print_passed_summary = None
+python3incompatibilities = None
+scons = None
+scons_exec = None
+testlistfile = None
+version = ''
+print_times = None
+python = None
+sp = []
+print_progress = 1
+suppress_stdout = False
+suppress_stderr = False
+allow_pipe_files = True
+quit_on_failure = False
+
+usagestr = """\
+Usage: runtest.py [OPTIONS] [TEST ...]
+ runtest.py -h|--help
+"""
+helpstr = usagestr + """\
+Options:
+ -3 Warn about Python 3.x incompatibilities.
+ -a --all Run all tests.
+ -b --baseline BASE Run test scripts against baseline BASE.
+ --builddir DIR Directory in which packages were built.
+ -d --debug Run test scripts under the Python debugger.
+ -e --external Run the script in external mode (for testing separate Tools)
+ -f --file FILE Run tests in specified FILE.
+ -k --no-progress Suppress count and percent progress messages.
+ -l --list List available tests and exit.
+ -n --no-exec No execute, just print command lines.
+ --nopipefiles Doesn't use the "file pipe" workaround for subprocess.Popen()
+ for starting tests. WARNING: Only use this when too much file
+ traffic is giving you trouble AND you can be sure that none of
+ your tests create output that exceed 65K chars! You might
+ run into some deadlocks else.
+ -o --output FILE Save the output from a test run to the log file.
+ -P PYTHON Use the specified Python interpreter.
+ -p --package PACKAGE Test against the specified PACKAGE:
+ deb Debian
+ local-tar-gz .tar.gz standalone package
+ local-zip .zip standalone package
+ rpm Red Hat
+ src-tar-gz .tar.gz source package
+ src-zip .zip source package
+ tar-gz .tar.gz distribution
+ zip .zip distribution
+ --passed Summarize which tests passed.
+ -q --quiet Don't print the test being executed.
+ --quit-on-failure Quit on any test failure
+ --runner CLASS Alternative test runner class for unit tests
+ -s --short-progress Short progress, prints only the command line
+ and a percentage value, based on the total and
+ current number of tests.
+ -t --time Print test execution time.
+ -v VERSION Specify the SCons version.
+ --verbose=LEVEL Set verbose level: 1 = print executed commands,
+ 2 = print commands and non-zero output,
+ 3 = print commands and all output.
+ -X Test script is executable, don't feed to Python.
+ -x --exec SCRIPT Test SCRIPT.
+ --xml file Save results to file in SCons XML format.
+
+Environment Variables:
+
+ PRESERVE, PRESERVE_{PASS,FAIL,NO_RESULT}: preserve test subdirs
+ TESTCMD_VERBOSE: turn on verbosity in TestCommand\
+"""
+
+
+# "Pass-through" option parsing -- an OptionParser that ignores
+# unknown options and lets them pile up in the leftover argument
+# list. Useful to gradually port getopt to optparse.
+
+from optparse import OptionParser, BadOptionError
+
+class PassThroughOptionParser(OptionParser):
+ def _process_long_opt(self, rargs, values):
+ try:
+ OptionParser._process_long_opt(self, rargs, values)
+ except BadOptionError, err:
+ self.largs.append(err.opt_str)
+ def _process_short_opts(self, rargs, values):
+ try:
+ OptionParser._process_short_opts(self, rargs, values)
+ except BadOptionError, err:
+ self.largs.append(err.opt_str)
+
+parser = PassThroughOptionParser(add_help_option=False)
+parser.add_option('-a', '--all', action='store_true',
+ help="Run all tests.")
+parser.add_option('-o', '--output',
+ help="Save the output from a test run to the log file.")
+parser.add_option('--runner', metavar='class',
+ help="Test runner class for unit tests.")
+parser.add_option('--xml',
+ help="Save results to file in SCons XML format.")
+(options, args) = parser.parse_args()
+
+#print "options:", options
+#print "args:", args
+
+
+opts, args = getopt.getopt(args, "3b:def:hj:klnP:p:qsv:Xx:t",
+ ['baseline=', 'builddir=',
+ 'debug', 'external', 'file=', 'help', 'no-progress',
+ 'jobs=',
+ 'list', 'no-exec', 'nopipefiles',
+ 'package=', 'passed', 'python=',
+ 'quiet',
+ 'quit-on-failure',
+ 'short-progress', 'time',
+ 'version=', 'exec=',
+ 'verbose='])
+
+for o, a in opts:
+ if o in ['-3']:
+ python3incompatibilities = 1
+ elif o in ['-b', '--baseline']:
+ baseline = a
+ elif o in ['--builddir']:
+ builddir = a
+ if not os.path.isabs(builddir):
+ builddir = os.path.normpath(os.path.join(cwd, builddir))
+ elif o in ['-d', '--debug']:
+ for dir in sys.path:
+ pdb = os.path.join(dir, 'pdb.py')
+ if os.path.exists(pdb):
+ debug = pdb
+ break
+ elif o in ['-e', '--external']:
+ external = 1
+ elif o in ['-f', '--file']:
+ if not os.path.isabs(a):
+ a = os.path.join(cwd, a)
+ testlistfile = a
+ elif o in ['-h', '--help']:
+ print helpstr
+ sys.exit(0)
+ elif o in ['-j', '--jobs']:
+ jobs = int(a)
+ elif o in ['-k', '--no-progress']:
+ print_progress = 0
+ elif o in ['-l', '--list']:
+ list_only = 1
+ elif o in ['-n', '--no-exec']:
+ execute_tests = None
+ elif o in ['--nopipefiles']:
+ allow_pipe_files = False
+ elif o in ['-p', '--package']:
+ package = a
+ elif o in ['--passed']:
+ print_passed_summary = 1
+ elif o in ['-P', '--python']:
+ python = a
+ elif o in ['-q', '--quiet']:
+ printcommand = 0
+ suppress_stdout = True
+ suppress_stderr = True
+ elif o in ['--quit-on-failure']:
+ quit_on_failure = True
+ elif o in ['-s', '--short-progress']:
+ print_progress = 1
+ suppress_stdout = True
+ suppress_stderr = True
+ elif o in ['-t', '--time']:
+ print_times = 1
+ elif o in ['--verbose']:
+ os.environ['TESTCMD_VERBOSE'] = a
+ elif o in ['-v', '--version']:
+ version = a
+ elif o in ['-X']:
+ scons_exec = 1
+ elif o in ['-x', '--exec']:
+ scons = a
+
+
+
+# --- setup stdout/stderr ---
+class Unbuffered(object):
+ def __init__(self, file):
+ self.file = file
+ self.softspace = 0 ## backward compatibility; not supported in Py3k
+ def write(self, arg):
+ self.file.write(arg)
+ self.file.flush()
+ def __getattr__(self, attr):
+ return getattr(self.file, attr)
+
+sys.stdout = Unbuffered(sys.stdout)
+sys.stderr = Unbuffered(sys.stderr)
+
+if options.output:
+ logfile = open(options.output, 'w')
+ class Tee(object):
+ def __init__(self, openfile, stream):
+ self.file = openfile
+ self.stream = stream
+ def write(self, data):
+ self.file.write(data)
+ self.stream.write(data)
+ sys.stdout = Tee(logfile, sys.stdout)
+ sys.stderr = Tee(logfile, sys.stderr)
+
+# --- define helpers ----
+if sys.platform in ('win32', 'cygwin'):
+
+ def whereis(file):
+ pathext = [''] + os.environ['PATHEXT'].split(os.pathsep)
+ for dir in os.environ['PATH'].split(os.pathsep):
+ f = os.path.join(dir, file)
+ for ext in pathext:
+ fext = f + ext
+ if os.path.isfile(fext):
+ return fext
+ return None
+
+else:
+
+ def whereis(file):
+ for dir in os.environ['PATH'].split(os.pathsep):
+ f = os.path.join(dir, file)
+ if os.path.isfile(f):
+ try:
+ st = os.stat(f)
+ except OSError:
+ continue
+ if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
+ return f
+ return None
+
+sp.append(builddir)
+sp.append(cwd)
+
+#
+_ws = re.compile('\s')
+
+def escape(s):
+ if _ws.search(s):
+ s = '"' + s + '"'
+ s = s.replace('\\', '\\\\')
+ return s
+
+
+import subprocess
+
+if not suppress_stdout and not suppress_stderr:
+ # Without any output suppressed, we let the subprocess
+ # write its stuff freely to stdout/stderr.
+ def spawn_it(command_args):
+ p = subprocess.Popen(' '.join(command_args),
+ shell=True)
+ return (None, None, p.wait())
+else:
+ # Else, we catch the output of both pipes...
+ if allow_pipe_files:
+ # The subprocess.Popen() suffers from a well-known
+ # problem. Data for stdout/stderr is read into a
+ # memory buffer of fixed size, 65K which is not very much.
+ # When it fills up, it simply stops letting the child process
+ # write to it. The child will then sit and patiently wait to
+ # be able to write the rest of its output. Hang!
+ # In order to work around this, we follow a suggestion
+ # by Anders Pearson in
+ # http://http://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/
+ # and pass temp file objects to Popen() instead of the ubiquitous
+ # subprocess.PIPE.
+ def spawn_it(command_args):
+ # Create temporary files
+ import tempfile
+ tmp_stdout = tempfile.TemporaryFile(mode='w+t')
+ tmp_stderr = tempfile.TemporaryFile(mode='w+t')
+ # Start subprocess...
+ p = subprocess.Popen(' '.join(command_args),
+ stdout=tmp_stdout,
+ stderr=tmp_stderr,
+ shell=True)
+ # ... and wait for it to finish.
+ ret = p.wait()
+
+ try:
+ # Rewind to start of files
+ tmp_stdout.seek(0)
+ tmp_stderr.seek(0)
+ # Read output
+ spawned_stdout = tmp_stdout.read()
+ spawned_stderr = tmp_stderr.read()
+ finally:
+ # Remove temp files by closing them
+ tmp_stdout.close()
+ tmp_stderr.close()
+
+ # Return values
+ return (spawned_stderr, spawned_stdout, ret)
+
+ else:
+ # We get here only if the user gave the '--nopipefiles'
+ # option, meaning the "temp file" approach for
+ # subprocess.communicate() above shouldn't be used.
+ # He hopefully knows what he's doing, but again we have a
+ # potential deadlock situation in the following code:
+ # If the subprocess writes a lot of data to its stderr,
+ # the pipe will fill up (nobody's reading it yet) and the
+ # subprocess will wait for someone to read it.
+ # But the parent process is trying to read from stdin
+ # (but the subprocess isn't writing anything there).
+ # Hence a deadlock.
+ # Be dragons here! Better don't use this!
+ def spawn_it(command_args):
+ p = subprocess.Popen(' '.join(command_args),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=True)
+ spawned_stdout = p.stdout.read()
+ spawned_stderr = p.stderr.read()
+ return (spawned_stderr, spawned_stdout, p.wait())
+
+class Base(object):
+ def __init__(self, path, spe=None):
+ self.path = path
+ self.abspath = os.path.abspath(path)
+ if spe:
+ for dir in spe:
+ f = os.path.join(dir, path)
+ if os.path.isfile(f):
+ self.abspath = f
+ break
+ self.status = None
+
+class SystemExecutor(Base):
+ def execute(self):
+ self.stderr, self.stdout, s = spawn_it(self.command_args)
+ self.status = s
+ if s < 0 or s > 2:
+ sys.stdout.write("Unexpected exit status %d\n" % s)
+
+class PopenExecutor(Base):
+ # For an explanation of the following 'if ... else'
+ # and the 'allow_pipe_files' option, please check out the
+ # definition of spawn_it() above.
+ if allow_pipe_files:
+ def execute(self):
+ # Create temporary files
+ import tempfile
+ tmp_stdout = tempfile.TemporaryFile(mode='w+t')
+ tmp_stderr = tempfile.TemporaryFile(mode='w+t')
+ # Start subprocess...
+ p = subprocess.Popen(self.command_str,
+ stdout=tmp_stdout,
+ stderr=tmp_stderr,
+ shell=True)
+ # ... and wait for it to finish.
+ self.status = p.wait()
+
+ try:
+ # Rewind to start of files
+ tmp_stdout.seek(0)
+ tmp_stderr.seek(0)
+ # Read output
+ self.stdout = tmp_stdout.read()
+ self.stderr = tmp_stderr.read()
+ finally:
+ # Remove temp files by closing them
+ tmp_stdout.close()
+ tmp_stderr.close()
+ else:
+ def execute(self):
+ p = subprocess.Popen(self.command_str,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=True)
+ self.stdout = p.stdout.read()
+ self.stderr = p.stderr.read()
+ self.status = p.wait()
+
+class XML(PopenExecutor):
+ def header(self, f):
+ f.write(' <results>\n')
+ def write(self, f):
+ f.write(' <test>\n')
+ f.write(' <file_name>%s</file_name>\n' % self.path)
+ f.write(' <command_line>%s</command_line>\n' % self.command_str)
+ f.write(' <exit_status>%s</exit_status>\n' % self.status)
+ f.write(' <stdout>%s</stdout>\n' % self.stdout)
+ f.write(' <stderr>%s</stderr>\n' % self.stderr)
+ f.write(' <time>%.1f</time>\n' % self.test_time)
+ f.write(' </test>\n')
+ def footer(self, f):
+ f.write(' <time>%.1f</time>\n' % self.total_time)
+ f.write(' </results>\n')
+
+if options.xml:
+ Test = XML
+else:
+ Test = SystemExecutor
+
+# --- start processing ---
+if package:
+
+ dir = {
+ 'deb' : 'usr',
+ 'local-tar-gz' : None,
+ 'local-zip' : None,
+ 'rpm' : 'usr',
+ 'src-tar-gz' : '',
+ 'src-zip' : '',
+ 'tar-gz' : '',
+ 'zip' : '',
+ }
+
+ # The hard-coded "python2.1" here is the library directory
+ # name on Debian systems, not an executable, so it's all right.
+ lib = {
+ 'deb' : os.path.join('python2.1', 'site-packages')
+ }
+
+ if package not in dir:
+ sys.stderr.write("Unknown package '%s'\n" % package)
+ sys.exit(2)
+
+ test_dir = os.path.join(builddir, 'test-%s' % package)
+
+ if dir[package] is None:
+ scons_script_dir = test_dir
+ globs = glob.glob(os.path.join(test_dir, 'scons-local-*'))
+ if not globs:
+ sys.stderr.write("No `scons-local-*' dir in `%s'\n" % test_dir)
+ sys.exit(2)
+ scons_lib_dir = None
+ pythonpath_dir = globs[len(globs)-1]
+ elif sys.platform == 'win32':
+ scons_script_dir = os.path.join(test_dir, dir[package], 'Scripts')
+ scons_lib_dir = os.path.join(test_dir, dir[package])
+ pythonpath_dir = scons_lib_dir
+ else:
+ scons_script_dir = os.path.join(test_dir, dir[package], 'bin')
+ l = lib.get(package, 'scons')
+ scons_lib_dir = os.path.join(test_dir, dir[package], 'lib', l)
+ pythonpath_dir = scons_lib_dir
+
+ scons_runtest_dir = builddir
+
+else:
+ sd = None
+ ld = None
+
+ if not baseline or baseline == '.':
+ base = cwd
+ elif baseline == '-':
+ url = None
+ svn_info = os.popen("svn info 2>&1", "r").read()
+ match = re.search('URL: (.*)', svn_info)
+ if match:
+ url = match.group(1)
+ if not url:
+ sys.stderr.write('runtest.py: could not find a URL:\n')
+ sys.stderr.write(svn_info)
+ sys.exit(1)
+ import tempfile
+ base = tempfile.mkdtemp(prefix='runtest-tmp-')
+
+ command = 'cd %s && svn co -q %s' % (base, url)
+
+ base = os.path.join(base, os.path.split(url)[1])
+ if printcommand:
+ print command
+ if execute_tests:
+ os.system(command)
+ else:
+ base = baseline
+
+ scons_runtest_dir = base
+
+ if not external:
+ scons_script_dir = sd or os.path.join(base, 'src', 'script')
+ scons_lib_dir = ld or os.path.join(base, 'src', 'engine')
+ else:
+ scons_script_dir = sd or ''
+ scons_lib_dir = ld or ''
+
+ pythonpath_dir = scons_lib_dir
+
+if scons:
+ # Let the version of SCons that the -x option pointed to find
+ # its own modules.
+ os.environ['SCONS'] = scons
+elif scons_lib_dir:
+ # Because SCons is really aggressive about finding its modules,
+ # it sometimes finds SCons modules elsewhere on the system.
+ # This forces SCons to use the modules that are being tested.
+ os.environ['SCONS_LIB_DIR'] = scons_lib_dir
+
+if scons_exec:
+ os.environ['SCONS_EXEC'] = '1'
+
+if external:
+ os.environ['SCONS_EXTERNAL_TEST'] = '1'
+
+os.environ['SCONS_RUNTEST_DIR'] = scons_runtest_dir
+os.environ['SCONS_SCRIPT_DIR'] = scons_script_dir
+os.environ['SCONS_CWD'] = cwd
+
+os.environ['SCONS_VERSION'] = version
+
+old_pythonpath = os.environ.get('PYTHONPATH')
+
+# FIXME: the following is necessary to pull in half of the testing
+# harness from $srcdir/etc. Those modules should be transfered
+# to testing/, in which case this manipulation of PYTHONPATH
+# should be able to go away.
+pythonpaths = [ pythonpath_dir ]
+
+# Add path of the QMTest folder to PYTHONPATH
+# [ ] move used parts from QMTest to testing/framework/
+scriptpath = os.path.dirname(os.path.realpath(__file__))
+pythonpaths.append(os.path.join(scriptpath, 'QMTest'))
+# Add path for testing framework to PYTHONPATH
+pythonpaths.append(os.path.join(scriptpath, 'testing', 'framework'))
+
+
+os.environ['PYTHONPATH'] = os.pathsep.join(pythonpaths)
+
+if old_pythonpath:
+ os.environ['PYTHONPATH'] = os.environ['PYTHONPATH'] + \
+ os.pathsep + \
+ old_pythonpath
+
+if python3incompatibilities:
+ os.environ['SCONS_HORRIBLE_REGRESSION_TEST_HACK'] = '1'
+
+
+# ---[ test discovery ]------------------------------------
+
+tests = []
+
+unittests = []
+endtests = []
+
+def find_Tests_py(directory):
+ """ Look for unit tests """
+ result = []
+ for dirpath, dirnames, filenames in os.walk(directory):
+ # Skip folders containing a sconstest.skip file
+ if 'sconstest.skip' in filenames:
+ continue
+ for fname in filenames:
+ if fname.endswith("Tests.py"):
+ result.append(os.path.join(dirpath, fname))
+ return sorted(result)
+
+def find_py(directory):
+ """ Look for end-to-end tests """
+ result = []
+ for dirpath, dirnames, filenames in os.walk(directory):
+ # Skip folders containing a sconstest.skip file
+ if 'sconstest.skip' in filenames:
+ continue
+ try:
+ exclude_fp = open(os.path.join(dirpath, ".exclude_tests"))
+ except EnvironmentError:
+ excludes = []
+ else:
+ excludes = [ e.split('#', 1)[0].strip()
+ for e in exclude_fp.readlines() ]
+ for fname in filenames:
+ if fname.endswith(".py") and fname not in excludes:
+ result.append(os.path.join(dirpath, fname))
+ return sorted(result)
+
+
+if testlistfile:
+ tests = open(testlistfile, 'r').readlines()
+ tests = [x for x in tests if x[0] != '#']
+ tests = [x[:-1] for x in tests]
+ tests = [x.strip() for x in tests]
+
+else:
+ testpaths = []
+
+ # Each test path specifies a test file, or a directory to search for
+ # SCons tests. SCons code layout assumes that any file under the 'src'
+ # subdirectory that ends with 'Tests.py' is a unit test, and Python
+ # script (*.py) under the 'test' subdirectory an end-to-end test.
+ #
+ # Note that there are some tests under 'src' that *begin* with
+ # 'test_', but they're packaging and installation tests, not
+ # functional tests, so we don't execute them by default. (They can
+ # still be executed by hand, though, and are routinely executed
+ # by the Aegis packaging build to make sure that we're building
+ # things correctly.)
+
+ if options.all:
+ testpaths = ['src', 'test']
+ elif args:
+ testpaths = args
+
+ for tp in testpaths:
+ for path in glob.glob(tp):
+ if os.path.isdir(path):
+ if path.startswith('src'):
+ for p in find_Tests_py(path):
+ unittests.append(p)
+ elif path.startswith('test'):
+ for p in find_py(path):
+ endtests.append(p)
+ else:
+ if path.endswith("Tests.py"):
+ unittests.append(path)
+ else:
+ endtests.append(path)
+
+ tests.extend(unittests)
+ tests.extend(endtests)
+ tests.sort()
+
+if not tests:
+ sys.stderr.write(usagestr + """
+runtest.py: No tests were found.
+ Tests can be specified on the command line, read from file
+ with -f option, or discovered with -a to run all tests.
+""")
+ sys.exit(1)
+
+
+# ---[ test processing ]-----------------------------------
+
+tests = [Test(t) for t in tests]
+
+if list_only:
+ for t in tests:
+ sys.stdout.write(t.path + "\n")
+ sys.exit(0)
+
+if not python:
+ if os.name == 'java':
+ python = os.path.join(sys.prefix, 'jython')
+ else:
+ python = sys.executable
+os.environ["python_executable"] = python
+
+# time.clock() is the suggested interface for doing benchmarking timings,
+# but time.time() does a better job on Linux systems, so let that be
+# the non-Windows default.
+
+if sys.platform == 'win32':
+ time_func = time.clock
+else:
+ time_func = time.time
+
+if print_times:
+ print_time_func = lambda fmt, time: sys.stdout.write(fmt % time)
+else:
+ print_time_func = lambda fmt, time: None
+
+total_start_time = time_func()
+total_num_tests = len(tests)
+tests_completed = 0
+
+def run_test(t, io_lock, async=True):
+ global tests_completed
+ header = ""
+ command_args = ['-tt']
+ if python3incompatibilities:
+ command_args.append('-3')
+ if debug:
+ command_args.append(debug)
+ command_args.append(t.path)
+ if options.runner and t.path in unittests:
+ # For example --runner TestUnit.TAPTestRunner
+ command_args.append('--runner ' + options.runner)
+ t.command_args = [python] + command_args
+ t.command_str = " ".join([escape(python)] + command_args)
+ if printcommand:
+ if print_progress:
+ tests_completed += 1
+ n = tests_completed # approx indicator of where we are
+ header += ("%d/%d (%.2f%s) %s\n" % (n, total_num_tests,
+ float(n)*100.0/float(total_num_tests),
+ '%',
+ t.command_str))
+ else:
+ header += t.command_str + "\n"
+ if not suppress_stdout and not suppress_stderr:
+ sys.stdout.write(header)
+ head, tail = os.path.split(t.abspath)
+ if head:
+ os.environ['PYTHON_SCRIPT_DIR'] = head
+ else:
+ os.environ['PYTHON_SCRIPT_DIR'] = ''
+ test_start_time = time_func()
+ if execute_tests:
+ t.execute()
+
+ t.test_time = time_func() - test_start_time
+ if io_lock:
+ io_lock.acquire()
+ if suppress_stdout or suppress_stderr:
+ sys.stdout.write(header)
+ if not suppress_stdout and t.stdout:
+ print t.stdout
+ if not suppress_stderr and t.stderr:
+ print t.stderr
+ print_time_func("Test execution time: %.1f seconds\n", t.test_time)
+ if io_lock:
+ io_lock.release()
+ if quit_on_failure and t.status == 1:
+ print "Exiting due to error"
+ print t.status
+ sys.exit(1)
+
+class RunTest(threading.Thread):
+ def __init__(self, queue, io_lock):
+ threading.Thread.__init__(self)
+ self.queue = queue
+ self.io_lock = io_lock
+
+ def run(self):
+ while True:
+ t = self.queue.get()
+ run_test(t, io_lock, True)
+ self.queue.task_done()
+
+if jobs > 1 and threading_ok:
+ print "Running tests using %d jobs"%jobs
+ # Start worker threads
+ queue = Queue.Queue()
+ io_lock = threading.Lock()
+ for i in range(1, jobs):
+ t = RunTest(queue, io_lock)
+ t.daemon = True
+ t.start()
+ # Give tasks to workers
+ for t in tests:
+ queue.put(t)
+ queue.join()
+else:
+ # Run tests serially
+ if jobs > 1:
+ print "Ignoring -j%d option; no python threading module available."%jobs
+ for t in tests:
+ run_test(t, None, False)
+
+# --- all tests are complete by the time we get here ---
+if len(tests) > 0:
+ tests[0].total_time = time_func() - total_start_time
+ print_time_func("Total execution time for all tests: %.1f seconds\n", tests[0].total_time)
+
+passed = [t for t in tests if t.status == 0]
+fail = [t for t in tests if t.status == 1]
+no_result = [t for t in tests if t.status == 2]
+
+if len(tests) != 1 and execute_tests:
+ if passed and print_passed_summary:
+ if len(passed) == 1:
+ sys.stdout.write("\nPassed the following test:\n")
+ else:
+ sys.stdout.write("\nPassed the following %d tests:\n" % len(passed))
+ paths = [x.path for x in passed]
+ sys.stdout.write("\t" + "\n\t".join(paths) + "\n")
+ if fail:
+ if len(fail) == 1:
+ sys.stdout.write("\nFailed the following test:\n")
+ else:
+ sys.stdout.write("\nFailed the following %d tests:\n" % len(fail))
+ paths = [x.path for x in fail]
+ sys.stdout.write("\t" + "\n\t".join(paths) + "\n")
+ if no_result:
+ if len(no_result) == 1:
+ sys.stdout.write("\nNO RESULT from the following test:\n")
+ else:
+ sys.stdout.write("\nNO RESULT from the following %d tests:\n" % len(no_result))
+ paths = [x.path for x in no_result]
+ sys.stdout.write("\t" + "\n\t".join(paths) + "\n")
+
+if options.xml:
+ if options.xml == '-':
+ f = sys.stdout
+ else:
+ f = open(options.xml, 'w')
+ tests[0].header(f)
+ #f.write("test_result = [\n")
+ for t in tests:
+ t.write(f)
+ tests[0].footer(f)
+ #f.write("];\n")
+ if options.xml != '-':
+ f.close()
+
+if len(fail):
+ sys.exit(1)
+elif len(no_result):
+ sys.exit(2)
+else:
+ sys.exit(0)
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: