731f36f27b32564a1909ada69801b69a53d8b544
[scons.git] / runtest.py
1 #!/usr/bin/env python
2 #
3 # __COPYRIGHT__
4 #
5 # runtest.py - wrapper script for running SCons tests
6 #
7 # This script mainly exists to set PYTHONPATH to the right list of
8 # directories to test the SCons modules.
9 #
10 # By default, it directly uses the modules in the local tree:
11 # ./src/ (source files we ship) and ./QMTest/ (other modules we don't).
12 #
13 # HOWEVER, now that SCons has Repository support, we don't have
14 # Aegis copy all of the files into the local tree.  So if you're
15 # using Aegis and want to run tests by hand using this script, you
16 # must "aecp ." the entire source tree into your local directory
17 # structure.  When you're done with your change, you can then
18 # "aecpu -unch ." to un-copy any files that you haven't changed.
19 #
20 # When any -p option is specified, this script assumes it's in a
21 # directory in which a build has been performed, and sets PYTHONPATH
22 # so that it *only* references the modules that have unpacked from
23 # the specified built package, to test whether the packages are good.
24 #
25 # Options:
26 #
27 #       -a              Run all tests; does a virtual 'find' for
28 #                       all SCons tests under the current directory.
29 #
30 #       --aegis         Print test results to an output file (specified
31 #                       by the -o option) in the format expected by
32 #                       aetest(5).  This is intended for use in the
33 #                       batch_test_command field in the Aegis project
34 #                       config file.
35 #
36 #       -d              Debug.  Runs the script under the Python
37 #                       debugger (pdb.py) so you don't have to
38 #                       muck with PYTHONPATH yourself.
39 #
40 #       -f file         Only execute the tests listed in the specified
41 #                       file.
42 #
43 #       -h              Print the help and exit.
44 #
45 #       -l              List available tests and exit.
46 #
47 #       -n              No execute, just print command lines.
48 #
49 #       -o file         Print test results to the specified file.
50 #                       The --aegis and --xml options specify the
51 #                       output format.
52 #
53 #       -P Python       Use the specified Python interpreter.
54 #
55 #       -p package      Test against the specified package.
56 #
57 #       --passed        In the final summary, also report which tests
58 #                       passed.  The default is to only report tests
59 #                       which failed or returned NO RESULT.
60 #
61 #       -q              Quiet.  By default, runtest.py prints the
62 #                       command line it will execute before
63 #                       executing it.  This suppresses that print.
64 #
65 #       --sp            The Aegis search path.
66 #
67 #       --spe           The Aegis executable search path.
68 #
69 #       -t              Print the execution time of each test.
70 #
71 #       -X              The scons "script" is an executable; don't
72 #                       feed it to Python.
73 #
74 #       -x scons        The scons script to use for tests.
75 #
76 #       --xml           Print test results to an output file (specified
77 #                       by the -o option) in an SCons-specific XML format.
78 #                       This is (will be) used for reporting results back
79 #                       to a central SCons test monitoring infrastructure.
80 #
81 # (Note:  There used to be a -v option that specified the SCons
82 # version to be tested, when we were installing in a version-specific
83 # library directory.  If we ever resurrect that as the default, then
84 # you can find the appropriate code in the 0.04 version of this script,
85 # rather than reinventing that wheel.)
86 #
87
88 import getopt
89 import glob
90 import os
91 import os.path
92 import re
93 import stat
94 import string
95 import sys
96 import time
97
98 if not hasattr(os, 'WEXITSTATUS'):
99     os.WEXITSTATUS = lambda x: x
100
101 cwd = os.getcwd()
102
103 all = 0
104 baseline = 0
105 builddir = os.path.join(cwd, 'build')
106 debug = ''
107 execute_tests = 1
108 format = None
109 list_only = None
110 printcommand = 1
111 package = None
112 print_passed_summary = None
113 scons = None
114 scons_exec = None
115 outputfile = None
116 testlistfile = None
117 version = ''
118 print_times = None
119 python = None
120 sp = None
121 spe = None
122
123 helpstr = """\
124 Usage: runtest.py [OPTIONS] [TEST ...]
125 Options:
126   -a, --all                   Run all tests.
127   --aegis                     Print results in Aegis format.
128   -b BASE, --baseline BASE    Run test scripts against baseline BASE.
129   --builddir DIR              Directory in which packages were built.
130   -d, --debug                 Run test scripts under the Python debugger.
131   -f FILE, --file FILE        Run tests in specified FILE.
132   -h, --help                  Print this message and exit.
133   -l, --list                  List available tests and exit.
134   -n, --no-exec               No execute, just print command lines.
135   --noqmtest                  Execute tests directly, not using QMTest.
136   -o FILE, --output FILE      Print test results to FILE.
137   -P Python                   Use the specified Python interpreter.
138   -p PACKAGE, --package PACKAGE
139                               Test against the specified PACKAGE:
140                                 deb           Debian
141                                 local-tar-gz  .tar.gz standalone package
142                                 local-zip     .zip standalone package
143                                 rpm           Red Hat
144                                 src-tar-gz    .tar.gz source package
145                                 src-zip       .zip source package
146                                 tar-gz        .tar.gz distribution
147                                 zip           .zip distribution
148   --passed                    Summarize which tests passed.
149   --qmtest                    Run using the QMTest harness.
150   -q, --quiet                 Don't print the test being executed.
151   --sp PATH                   The Aegis search path.
152   --spe PATH                  The Aegis executable search path.
153   -t, --time                  Print test execution time.
154   -v version                  Specify the SCons version.
155   --verbose=LEVEL             Set verbose level: 1 = print executed commands,
156                                 2 = print commands and non-zero output,
157                                 3 = print commands and all output.
158   -X                          Test script is executable, don't feed to Python.
159   -x SCRIPT, --exec SCRIPT    Test SCRIPT.
160   --xml                       Print results in SCons XML format.
161 """
162
163 opts, args = getopt.getopt(sys.argv[1:], "ab:df:hlno:P:p:qv:Xx:t",
164                             ['all', 'aegis', 'baseline=', 'builddir=',
165                              'debug', 'file=', 'help',
166                              'list', 'no-exec', 'noqmtest', 'output=',
167                              'package=', 'passed', 'python=', 'qmtest',
168                              'quiet', 'sp=', 'spe=', 'time',
169                              'version=', 'exec=',
170                              'verbose=', 'xml'])
171
172 for o, a in opts:
173     if o in ['-a', '--all']:
174         all = 1
175     elif o in ['-b', '--baseline']:
176         baseline = a
177     elif o in ['--builddir']:
178         builddir = a
179         if not os.path.isabs(builddir):
180             builddir = os.path.normpath(os.path.join(cwd, builddir))
181     elif o in ['-d', '--debug']:
182         for dir in sys.path:
183             pdb = os.path.join(dir, 'pdb.py')
184             if os.path.exists(pdb):
185                 debug = pdb
186                 break
187     elif o in ['-f', '--file']:
188         if not os.path.isabs(a):
189             a = os.path.join(cwd, a)
190         testlistfile = a
191     elif o in ['-h', '--help']:
192         print helpstr
193         sys.exit(0)
194     elif o in ['-l', '--list']:
195         list_only = 1
196     elif o in ['-n', '--no-exec']:
197         execute_tests = None
198     elif o in ['--noqmtest']:
199         qmtest = None
200     elif o in ['-o', '--output']:
201         if a != '-' and not os.path.isabs(a):
202             a = os.path.join(cwd, a)
203         outputfile = a
204     elif o in ['-p', '--package']:
205         package = a
206     elif o in ['--passed']:
207         print_passed_summary = 1
208     elif o in ['-P', '--python']:
209         python = a
210     elif o in ['--qmtest']:
211         if sys.platform == 'win32':
212             # typically in c:/PythonXX/Scripts
213             qmtest = 'qmtest.py'
214         else:
215             qmtest = 'qmtest'
216     elif o in ['-q', '--quiet']:
217         printcommand = 0
218     elif o in ['--sp']:
219         sp = string.split(a, os.pathsep)
220     elif o in ['--spe']:
221         spe = string.split(a, os.pathsep)
222     elif o in ['-t', '--time']:
223         print_times = 1
224     elif o in ['--verbose']:
225         os.environ['TESTCMD_VERBOSE'] = a
226     elif o in ['-v', '--version']:
227         version = a
228     elif o in ['-X']:
229         scons_exec = 1
230     elif o in ['-x', '--exec']:
231         scons = a
232     elif o in ['--aegis', '--xml']:
233         format = o
234
235 if not args and not all and not testlistfile:
236     sys.stderr.write("""\
237 runtest.py:  No tests were specified.
238              List one or more tests on the command line, use the
239              -f option to specify a file containing a list of tests,
240              or use the -a option to find and run all tests.
241
242 """)
243     sys.exit(1)
244
245 if sys.platform in ('win32', 'cygwin'):
246
247     def whereis(file):
248         pathext = [''] + string.split(os.environ['PATHEXT'], os.pathsep)
249         for dir in string.split(os.environ['PATH'], os.pathsep):
250             f = os.path.join(dir, file)
251             for ext in pathext:
252                 fext = f + ext
253                 if os.path.isfile(fext):
254                     return fext
255         return None
256
257 else:
258
259     def whereis(file):
260         for dir in string.split(os.environ['PATH'], os.pathsep):
261             f = os.path.join(dir, file)
262             if os.path.isfile(f):
263                 try:
264                     st = os.stat(f)
265                 except OSError:
266                     continue
267                 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
268                     return f
269         return None
270
271 # See if --qmtest or --noqmtest specified
272 try:
273     qmtest
274 except NameError:
275     qmtest = None
276     # Old code for using QMTest by default if it's installed.
277     # We now default to not using QMTest unless explicitly asked for.
278     #for q in ['qmtest', 'qmtest.py']:
279     #    path = whereis(q)
280     #    if path:
281     #        # The name was found on $PATH; just execute the found name so
282     #        # we don't have to worry about paths containing white space.
283     #        qmtest = q
284     #        break
285     #if not qmtest:
286     #    msg = ('Warning:  found neither qmtest nor qmtest.py on $PATH;\n' +
287     #           '\tassuming --noqmtest option.\n')
288     #    sys.stderr.write(msg)
289     #    sys.stderr.flush()
290
291 aegis = whereis('aegis')
292
293 if format == '--aegis' and aegis:
294     change = os.popen("aesub '$c' 2>/dev/null", "r").read()
295     if change:
296         if sp is None:
297             paths = os.popen("aesub '$sp' 2>/dev/null", "r").read()[:-1]
298             sp = string.split(paths, os.pathsep)
299         if spe is None:
300             spe = os.popen("aesub '$spe' 2>/dev/null", "r").read()[:-1]
301             spe = string.split(spe, os.pathsep)
302     else:
303         aegis = None
304
305 if sp is None:
306     sp = []
307 if spe is None:
308     spe = []
309
310 sp.append(builddir)
311 sp.append(cwd)
312
313 #
314 _ws = re.compile('\s')
315
316 def escape(s):
317     if _ws.search(s):
318         s = '"' + s + '"'
319     s = string.replace(s, '\\', '\\\\')
320     return s
321
322 # Set up lowest-common-denominator spawning of a process on both Windows
323 # and non-Windows systems that works all the way back to Python 1.5.2.
324 try:
325     os.spawnv
326 except AttributeError:
327     def spawn_it(command_args):
328         pid = os.fork()
329         if pid == 0:
330             os.execv(command_args[0], command_args)
331         else:
332             pid, status = os.waitpid(pid, 0)
333             return status >> 8
334 else:
335     def spawn_it(command_args):
336         command = command_args[0]
337         command_args = map(escape, command_args)
338         return os.spawnv(os.P_WAIT, command, command_args)
339
340 class Base:
341     def __init__(self, path, spe=None):
342         self.path = path
343         self.abspath = os.path.abspath(path)
344         if spe:
345             for dir in spe:
346                 f = os.path.join(dir, path)
347                 if os.path.isfile(f):
348                     self.abspath = f
349                     break
350         self.status = None
351
352 class SystemExecutor(Base):
353     def execute(self):
354         s = spawn_it(self.command_args)
355         self.status = s
356         if s < 0 or s > 2:
357             sys.stdout.write("Unexpected exit status %d\n" % s)
358
359 try:
360     import subprocess
361 except ImportError:
362     import popen2
363     try:
364         popen2.Popen3
365     except AttributeError:
366         class PopenExecutor(Base):
367             def execute(self):
368                 (tochild, fromchild, childerr) = os.popen3(self.command_str)
369                 tochild.close()
370                 self.stderr = childerr.read()
371                 self.stdout = fromchild.read()
372                 fromchild.close()
373                 self.status = childerr.close()
374                 if not self.status:
375                     self.status = 0
376     else:
377         class PopenExecutor(Base):
378             def execute(self):
379                 p = popen2.Popen3(self.command_str, 1)
380                 p.tochild.close()
381                 self.stdout = p.fromchild.read()
382                 self.stderr = p.childerr.read()
383                 self.status = p.wait()
384 else:
385     class PopenExecutor(Base):
386         def execute(self):
387             p = subprocess.Popen(self.command_str, shell=True)
388             p.stdin.close()
389             self.stdout = p.stdout.read()
390             self.stdout = p.stderr.read()
391             self.status = p.wait()
392
393 class Aegis(SystemExecutor):
394     def header(self, f):
395         f.write('test_result = [\n')
396     def write(self, f):
397         f.write('    { file_name = "%s";\n' % self.path)
398         f.write('      exit_status = %d; },\n' % self.status)
399     def footer(self, f):
400         f.write('];\n')
401
402 class XML(PopenExecutor):
403     def header(self, f):
404         f.write('  <results>\n')
405     def write(self, f):
406         f.write('    <test>\n')
407         f.write('      <file_name>%s</file_name>\n' % self.path)
408         f.write('      <command_line>%s</command_line>\n' % self.command_str)
409         f.write('      <exit_status>%s</exit_status>\n' % self.status)
410         f.write('      <stdout>%s</stdout>\n' % self.stdout)
411         f.write('      <stderr>%s</stderr>\n' % self.stderr)
412         f.write('      <time>%.1f</time>\n' % self.test_time)
413         f.write('    </test>\n')
414     def footer(self, f):
415         f.write('  <time>%.1f</time>\n' % self.total_time)
416         f.write('  </results>\n')
417
418 format_class = {
419     None        : SystemExecutor,
420     '--aegis'   : Aegis,
421     '--xml'     : XML,
422 }
423
424 Test = format_class[format]
425
426 if package:
427
428     dir = {
429         'deb'          : 'usr',
430         'local-tar-gz' : None,
431         'local-zip'    : None,
432         'rpm'          : 'usr',
433         'src-tar-gz'   : '',
434         'src-zip'      : '',
435         'tar-gz'       : '',
436         'zip'          : '',
437     }
438
439     # The hard-coded "python2.1" here is the library directory
440     # name on Debian systems, not an executable, so it's all right.
441     lib = {
442         'deb'        : os.path.join('python2.1', 'site-packages')
443     }
444
445     if not dir.has_key(package):
446         sys.stderr.write("Unknown package '%s'\n" % package)
447         sys.exit(2)
448
449     test_dir = os.path.join(builddir, 'test-%s' % package)
450
451     if dir[package] is None:
452         scons_script_dir = test_dir
453         globs = glob.glob(os.path.join(test_dir, 'scons-local-*'))
454         if not globs:
455             sys.stderr.write("No `scons-local-*' dir in `%s'\n" % test_dir)
456             sys.exit(2)
457         scons_lib_dir = None
458         pythonpath_dir = globs[len(globs)-1]
459     elif sys.platform == 'win32':
460         scons_script_dir = os.path.join(test_dir, dir[package], 'Scripts')
461         scons_lib_dir = os.path.join(test_dir, dir[package])
462         pythonpath_dir = scons_lib_dir
463     else:
464         scons_script_dir = os.path.join(test_dir, dir[package], 'bin')
465         l = lib.get(package, 'scons')
466         scons_lib_dir = os.path.join(test_dir, dir[package], 'lib', l)
467         pythonpath_dir = scons_lib_dir
468
469     scons_runtest_dir = builddir
470
471 else:
472     sd = None
473     ld = None
474
475     # XXX:  Logic like the following will be necessary once
476     # we fix runtest.py to run tests within an Aegis change
477     # without symlinks back to the baseline(s).
478     #
479     #if spe:
480     #    if not scons:
481     #        for dir in spe:
482     #            d = os.path.join(dir, 'src', 'script')
483     #            f = os.path.join(d, 'scons.py')
484     #            if os.path.isfile(f):
485     #                sd = d
486     #                scons = f
487     #    spe = map(lambda x: os.path.join(x, 'src', 'engine'), spe)
488     #    ld = string.join(spe, os.pathsep)
489
490     if not baseline or baseline == '.':
491         base = cwd
492     elif baseline == '-':
493         # Tentative code for fetching information directly from the
494         # QMTest context file.
495         #
496         #import qm.common
497         #import qm.test.context
498         #qm.rc.Load("test")
499         #context = qm.test.context.Context()
500         #context.Read('context')
501
502         url = None
503         svn_info =  os.popen("svn info 2>&1", "r").read()
504         match = re.search('URL: (.*)', svn_info)
505         if match:
506             url = match.group(1)
507         if not url:
508             sys.stderr.write('runtest.py: could not find a URL:\n')
509             sys.stderr.write(svn_info)
510             sys.exit(1)
511         import tempfile
512         base = tempfile.mkdtemp(prefix='runtest-tmp-')
513
514         command = 'cd %s && svn co -q %s' % (base, url)
515
516         base = os.path.join(base, os.path.split(url)[1])
517         if printcommand:
518             print command
519         if execute_tests:
520             os.system(command)
521     else:
522         base = baseline
523
524     scons_runtest_dir = base
525
526     scons_script_dir = sd or os.path.join(base, 'src', 'script')
527
528     scons_lib_dir = ld or os.path.join(base, 'src', 'engine')
529
530     pythonpath_dir = scons_lib_dir
531
532 if scons:
533     # Let the version of SCons that the -x option pointed to find
534     # its own modules.
535     os.environ['SCONS'] = scons
536 elif scons_lib_dir:
537     # Because SCons is really aggressive about finding its modules,
538     # it sometimes finds SCons modules elsewhere on the system.
539     # This forces SCons to use the modules that are being tested.
540     os.environ['SCONS_LIB_DIR'] = scons_lib_dir
541
542 if scons_exec:
543     os.environ['SCONS_EXEC'] = '1'
544
545 os.environ['SCONS_RUNTEST_DIR'] = scons_runtest_dir
546 os.environ['SCONS_SCRIPT_DIR'] = scons_script_dir
547 os.environ['SCONS_CWD'] = cwd
548
549 os.environ['SCONS_VERSION'] = version
550
551 old_pythonpath = os.environ.get('PYTHONPATH')
552
553 # FIXME: the following is necessary to pull in half of the testing
554 #        harness from $srcdir/etc. Those modules should be transfered
555 #        to QMTest/ once we completely cut over to using that as
556 #        the harness, in which case this manipulation of PYTHONPATH
557 #        should be able to go away.
558 pythonpaths = [ pythonpath_dir ]
559
560 for dir in sp:
561     if format == '--aegis':
562         q = os.path.join(dir, 'build', 'QMTest')
563     else:
564         q = os.path.join(dir, 'QMTest')
565     pythonpaths.append(q)
566
567 os.environ['SCONS_SOURCE_PATH_EXECUTABLE'] = string.join(spe, os.pathsep)
568
569 os.environ['PYTHONPATH'] = string.join(pythonpaths, os.pathsep)
570
571 if old_pythonpath:
572     os.environ['PYTHONPATH'] = os.environ['PYTHONPATH'] + \
573                                os.pathsep + \
574                                old_pythonpath
575
576 tests = []
577
578 if args:
579     if spe:
580         for a in args:
581             if os.path.isabs(a):
582                 tests.extend(glob.glob(a))
583             else:
584                 for dir in spe:
585                     x = os.path.join(dir, a)
586                     globs = glob.glob(x)
587                     if globs:
588                         tests.extend(globs)
589                         break
590     else:
591         for a in args:
592             tests.extend(glob.glob(a))
593 elif testlistfile:
594     tests = open(testlistfile, 'r').readlines()
595     tests = filter(lambda x: x[0] != '#', tests)
596     tests = map(lambda x: x[:-1], tests)
597 elif all and not qmtest:
598     # Find all of the SCons functional tests in the local directory
599     # tree.  This is anything under the 'src' subdirectory that ends
600     # with 'Tests.py', or any Python script (*.py) under the 'test'
601     # subdirectory.
602     #
603     # Note that there are some tests under 'src' that *begin* with
604     # 'test_', but they're packaging and installation tests, not
605     # functional tests, so we don't execute them by default.  (They can
606     # still be executed by hand, though, and are routinely executed
607     # by the Aegis packaging build to make sure that we're building
608     # things correctly.)
609     tdict = {}
610
611     def find_Tests_py(tdict, dirname, names):
612         for n in filter(lambda n: n[-8:] == "Tests.py", names):
613             tdict[os.path.join(dirname, n)] = 1
614     os.path.walk('src', find_Tests_py, tdict)
615
616     def find_py(tdict, dirname, names):
617         tests = filter(lambda n: n[-3:] == ".py", names)
618         try:
619             excludes = open(os.path.join(dirname,".exclude_tests")).readlines()
620         except (OSError, IOError):
621             pass
622         else:
623             for exclude in excludes:
624                 exclude = string.split(exclude, '#' , 1)[0]
625                 exclude = string.strip(exclude)
626                 if not exclude: continue
627                 tests = filter(lambda n, ex = exclude: n != ex, tests)
628         for n in tests:
629             tdict[os.path.join(dirname, n)] = 1
630     os.path.walk('test', find_py, tdict)
631
632     if format == '--aegis' and aegis:
633         cmd = "aegis -list -unf pf 2>/dev/null"
634         for line in os.popen(cmd, "r").readlines():
635             a = string.split(line)
636             if a[0] == "test" and not tdict.has_key(a[-1]):
637                 tdict[a[-1]] = Test(a[-1], spe)
638         cmd = "aegis -list -unf cf 2>/dev/null"
639         for line in os.popen(cmd, "r").readlines():
640             a = string.split(line)
641             if a[0] == "test":
642                 if a[1] == "remove":
643                     del tdict[a[-1]]
644                 elif not tdict.has_key(a[-1]):
645                     tdict[a[-1]] = Test(a[-1], spe)
646
647     tests = tdict.keys()
648     tests.sort()
649
650 if qmtest:
651     if baseline:
652         aegis_result_stream = 'scons_tdb.AegisBaselineStream'
653         qmr_file = 'baseline.qmr'
654     else:
655         aegis_result_stream = 'scons_tdb.AegisChangeStream'
656         qmr_file = 'results.qmr'
657
658     if print_times:
659         aegis_result_stream = aegis_result_stream + "(print_time='1')"
660
661     qmtest_args = [ qmtest, ]
662
663     if format == '--aegis':
664         dir = builddir
665         if not os.path.isdir(dir):
666             dir = cwd
667         qmtest_args.extend(['-D', dir])
668
669     qmtest_args.extend([
670                 'run',
671                 '--output %s' % qmr_file,
672                 '--format none',
673                 '--result-stream="%s"' % aegis_result_stream,
674               ])
675
676     if python:
677         qmtest_args.append('--context python="%s"' % python)
678
679     if outputfile:
680         if format == '--xml':
681             rsclass = 'scons_tdb.SConsXMLResultStream'
682         else:
683             rsclass = 'scons_tdb.AegisBatchStream'
684         qof = "r'" + outputfile + "'"
685         rs = '--result-stream="%s(filename=%s)"' % (rsclass, qof)
686         qmtest_args.append(rs)
687
688     if format == '--aegis':
689         tests = map(lambda x: string.replace(x, cwd+os.sep, ''), tests)
690     else:
691         os.environ['SCONS'] = os.path.join(cwd, 'src', 'script', 'scons.py')
692
693     cmd = string.join(qmtest_args + tests, ' ')
694     if printcommand:
695         sys.stdout.write(cmd + '\n')
696         sys.stdout.flush()
697     status = 0
698     if execute_tests:
699         status = os.WEXITSTATUS(os.system(cmd))
700     sys.exit(status)
701
702 #try:
703 #    os.chdir(scons_script_dir)
704 #except OSError:
705 #    pass
706
707 tests = map(Test, tests)
708
709 class Unbuffered:
710     def __init__(self, file):
711         self.file = file
712     def write(self, arg):
713         self.file.write(arg)
714         self.file.flush()
715     def __getattr__(self, attr):
716         return getattr(self.file, attr)
717
718 sys.stdout = Unbuffered(sys.stdout)
719
720 if list_only:
721     for t in tests:
722         sys.stdout.write(t.path + "\n")
723     sys.exit(0)
724
725 #
726 if not python:
727     if os.name == 'java':
728         python = os.path.join(sys.prefix, 'jython')
729     else:
730         python = sys.executable
731
732 # time.clock() is the suggested interface for doing benchmarking timings,
733 # but time.time() does a better job on Linux systems, so let that be
734 # the non-Windows default.
735
736 if sys.platform == 'win32':
737     time_func = time.clock
738 else:
739     time_func = time.time
740
741 if print_times:
742     print_time_func = lambda fmt, time: sys.stdout.write(fmt % time)
743 else:
744     print_time_func = lambda fmt, time: None
745
746 total_start_time = time_func()
747 for t in tests:
748     t.command_args = [python, '-tt']
749     if debug:
750         t.command_args.append(debug)
751     t.command_args.append(t.path)
752     t.command_str = string.join(map(escape, t.command_args), " ")
753     if printcommand:
754         sys.stdout.write(t.command_str + "\n")
755     test_start_time = time_func()
756     if execute_tests:
757         t.execute()
758     t.test_time = time_func() - test_start_time
759     print_time_func("Test execution time: %.1f seconds\n", t.test_time)
760 if len(tests) > 0:
761     tests[0].total_time = time_func() - total_start_time
762     print_time_func("Total execution time for all tests: %.1f seconds\n", tests[0].total_time)
763
764 passed = filter(lambda t: t.status == 0, tests)
765 fail = filter(lambda t: t.status == 1, tests)
766 no_result = filter(lambda t: t.status == 2, tests)
767
768 if len(tests) != 1 and execute_tests:
769     if passed and print_passed_summary:
770         if len(passed) == 1:
771             sys.stdout.write("\nPassed the following test:\n")
772         else:
773             sys.stdout.write("\nPassed the following %d tests:\n" % len(passed))
774         paths = map(lambda x: x.path, passed)
775         sys.stdout.write("\t" + string.join(paths, "\n\t") + "\n")
776     if fail:
777         if len(fail) == 1:
778             sys.stdout.write("\nFailed the following test:\n")
779         else:
780             sys.stdout.write("\nFailed the following %d tests:\n" % len(fail))
781         paths = map(lambda x: x.path, fail)
782         sys.stdout.write("\t" + string.join(paths, "\n\t") + "\n")
783     if no_result:
784         if len(no_result) == 1:
785             sys.stdout.write("\nNO RESULT from the following test:\n")
786         else:
787             sys.stdout.write("\nNO RESULT from the following %d tests:\n" % len(no_result))
788         paths = map(lambda x: x.path, no_result)
789         sys.stdout.write("\t" + string.join(paths, "\n\t") + "\n")
790
791 if outputfile:
792     if outputfile == '-':
793         f = sys.stdout
794     else:
795         f = open(outputfile, 'w')
796     tests[0].header(f)
797     #f.write("test_result = [\n")
798     for t in tests:
799         t.write(f)
800     tests[0].footer(f)
801     #f.write("];\n")
802     if outputfile != '-':
803         f.close()
804
805 if format == '--aegis':
806     sys.exit(0)
807 elif len(fail):
808     sys.exit(1)
809 elif len(no_result):
810     sys.exit(2)
811 else:
812     sys.exit(0)
813
814 # Local Variables:
815 # tab-width:4
816 # indent-tabs-mode:nil
817 # End:
818 # vim: set expandtab tabstop=4 shiftwidth=4: