Have the {Append,Prepend}ENVPath() canonicalization make sure there's the
[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         qmtest = 'qmtest'
212     elif o in ['-q', '--quiet']:
213         printcommand = 0
214     elif o in ['--sp']:
215         sp = string.split(a, os.pathsep)
216     elif o in ['--spe']:
217         spe = string.split(a, os.pathsep)
218     elif o in ['-t', '--time']:
219         print_times = 1
220     elif o in ['--verbose']:
221         os.environ['TESTCMD_VERBOSE'] = a
222     elif o in ['-v', '--version']:
223         version = a
224     elif o in ['-X']:
225         scons_exec = 1
226     elif o in ['-x', '--exec']:
227         scons = a
228     elif o in ['--aegis', '--xml']:
229         format = o
230
231 if not args and not all and not testlistfile:
232     sys.stderr.write("""\
233 runtest.py:  No tests were specified.
234              List one or more tests on the command line, use the
235              -f option to specify a file containing a list of tests,
236              or use the -a option to find and run all tests.
237
238 """)
239     sys.exit(1)
240
241 if sys.platform in ('win32', 'cygwin'):
242
243     def whereis(file):
244         pathext = [''] + string.split(os.environ['PATHEXT'])
245         for dir in string.split(os.environ['PATH'], os.pathsep):
246             f = os.path.join(dir, file)
247             for ext in pathext:
248                 fext = f + ext
249                 if os.path.isfile(fext):
250                     return fext
251         return None
252
253 else:
254
255     def whereis(file):
256         for dir in string.split(os.environ['PATH'], os.pathsep):
257             f = os.path.join(dir, file)
258             if os.path.isfile(f):
259                 try:
260                     st = os.stat(f)
261                 except OSError:
262                     continue
263                 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
264                     return f
265         return None
266
267 try:
268     qmtest
269 except NameError:
270     q = 'qmtest'
271     qmtest = whereis(q)
272     if qmtest:
273         qmtest = q
274     else:
275         sys.stderr.write('Warning:  %s not found on $PATH, assuming --noqmtest option.\n' % q)
276         sys.stderr.flush()
277
278 aegis = whereis('aegis')
279
280 if format == '--aegis' and aegis:
281     change = os.popen("aesub '$c' 2>/dev/null", "r").read()
282     if change:
283         if sp is None:
284             paths = os.popen("aesub '$sp' 2>/dev/null", "r").read()[:-1]
285             sp = string.split(paths, os.pathsep)
286         if spe is None:
287             spe = os.popen("aesub '$spe' 2>/dev/null", "r").read()[:-1]
288             spe = string.split(spe, os.pathsep)
289     else:
290         aegis = None
291
292 if sp is None:
293     sp = []
294 if spe is None:
295     spe = []
296
297 sp.append(builddir)
298 sp.append(cwd)
299
300 #
301 _ws = re.compile('\s')
302
303 def escape(s):
304     if _ws.search(s):
305         s = '"' + s + '"'
306     s = string.replace(s, '\\', '\\\\')
307     return s
308
309 # Set up lowest-common-denominator spawning of a process on both Windows
310 # and non-Windows systems that works all the way back to Python 1.5.2.
311 try:
312     os.spawnv
313 except AttributeError:
314     def spawn_it(command_args):
315         pid = os.fork()
316         if pid == 0:
317             os.execv(command_args[0], command_args)
318         else:
319             pid, status = os.waitpid(pid, 0)
320             return status >> 8
321 else:
322     def spawn_it(command_args):
323         command = command_args[0]
324         command_args = map(escape, command_args)
325         return os.spawnv(os.P_WAIT, command, command_args)
326
327 class Base:
328     def __init__(self, path, spe=None):
329         self.path = path
330         self.abspath = os.path.abspath(path)
331         if spe:
332             for dir in spe:
333                 f = os.path.join(dir, path)
334                 if os.path.isfile(f):
335                     self.abspath = f
336                     break
337         self.status = None
338
339 class SystemExecutor(Base):
340     def execute(self):
341         s = spawn_it(self.command_args)
342         self.status = s
343         if s < 0 or s > 2:
344             sys.stdout.write("Unexpected exit status %d\n" % s)
345
346 try:
347     import subprocess
348 except ImportError:
349     import popen2
350     try:
351         popen2.Popen3
352     except AttributeError:
353         class PopenExecutor(Base):
354             def execute(self):
355                 (tochild, fromchild, childerr) = os.popen3(self.command_str)
356                 tochild.close()
357                 self.stderr = childerr.read()
358                 self.stdout = fromchild.read()
359                 fromchild.close()
360                 self.status = childerr.close()
361                 if not self.status:
362                     self.status = 0
363     else:
364         class PopenExecutor(Base):
365             def execute(self):
366                 p = popen2.Popen3(self.command_str, 1)
367                 p.tochild.close()
368                 self.stdout = p.fromchild.read()
369                 self.stderr = p.childerr.read()
370                 self.status = p.wait()
371 else:
372     class PopenExecutor(Base):
373         def execute(self):
374             p = subprocess.Popen(self.command_str, shell=True)
375             p.stdin.close()
376             self.stdout = p.stdout.read()
377             self.stdout = p.stderr.read()
378             self.status = p.wait()
379
380 class Aegis(SystemExecutor):
381     def header(self, f):
382         f.write('test_result = [\n')
383     def write(self, f):
384         f.write('    { file_name = "%s";\n' % self.path)
385         f.write('      exit_status = %d; },\n' % self.status)
386     def footer(self, f):
387         f.write('];\n')
388
389 class XML(PopenExecutor):
390     def header(self, f):
391         f.write('  <results>\n')
392     def write(self, f):
393         f.write('    <test>\n')
394         f.write('      <file_name>%s</file_name>\n' % self.path)
395         f.write('      <command_line>%s</command_line>\n' % self.command_str)
396         f.write('      <exit_status>%s</exit_status>\n' % self.status)
397         f.write('      <stdout>%s</stdout>\n' % self.stdout)
398         f.write('      <stderr>%s</stderr>\n' % self.stderr)
399         f.write('      <time>%.1f</time>\n' % self.test_time)
400         f.write('    </test>\n')
401     def footer(self, f):
402         f.write('  <time>%.1f</time>\n' % self.total_time)
403         f.write('  </results>\n')
404
405 format_class = {
406     None        : SystemExecutor,
407     '--aegis'   : Aegis,
408     '--xml'     : XML,
409 }
410
411 Test = format_class[format]
412
413 if package:
414
415     dir = {
416         'deb'          : 'usr',
417         'local-tar-gz' : None,
418         'local-zip'    : None,
419         'rpm'          : 'usr',
420         'src-tar-gz'   : '',
421         'src-zip'      : '',
422         'tar-gz'       : '',
423         'zip'          : '',
424     }
425
426     # The hard-coded "python2.1" here is the library directory
427     # name on Debian systems, not an executable, so it's all right.
428     lib = {
429         'deb'        : os.path.join('python2.1', 'site-packages')
430     }
431
432     if not dir.has_key(package):
433         sys.stderr.write("Unknown package '%s'\n" % package)
434         sys.exit(2)
435
436     test_dir = os.path.join(builddir, 'test-%s' % package)
437
438     if dir[package] is None:
439         scons_script_dir = test_dir
440         globs = glob.glob(os.path.join(test_dir, 'scons-local-*'))
441         if not globs:
442             sys.stderr.write("No `scons-local-*' dir in `%s'\n" % test_dir)
443             sys.exit(2)
444         scons_lib_dir = None
445         pythonpath_dir = globs[len(globs)-1]
446     elif sys.platform == 'win32':
447         scons_script_dir = os.path.join(test_dir, dir[package], 'Scripts')
448         scons_lib_dir = os.path.join(test_dir, dir[package])
449         pythonpath_dir = scons_lib_dir
450     else:
451         scons_script_dir = os.path.join(test_dir, dir[package], 'bin')
452         l = lib.get(package, 'scons')
453         scons_lib_dir = os.path.join(test_dir, dir[package], 'lib', l)
454         pythonpath_dir = scons_lib_dir
455
456     scons_runtest_dir = builddir
457
458 else:
459     sd = None
460     ld = None
461
462     # XXX:  Logic like the following will be necessary once
463     # we fix runtest.py to run tests within an Aegis change
464     # without symlinks back to the baseline(s).
465     #
466     #if spe:
467     #    if not scons:
468     #        for dir in spe:
469     #            d = os.path.join(dir, 'src', 'script')
470     #            f = os.path.join(d, 'scons.py')
471     #            if os.path.isfile(f):
472     #                sd = d
473     #                scons = f
474     #    spe = map(lambda x: os.path.join(x, 'src', 'engine'), spe)
475     #    ld = string.join(spe, os.pathsep)
476
477     if not baseline or baseline == '.':
478         base = cwd
479     elif baseline == '-':
480         # Tentative code for fetching information directly from the
481         # QMTest context file.
482         #
483         #import qm.common
484         #import qm.test.context
485         #qm.rc.Load("test")
486         #context = qm.test.context.Context()
487         #context.Read('context')
488
489         url = None
490         svn_info =  os.popen("svn info 2>&1", "r").read()
491         match = re.search('URL: (.*)', svn_info)
492         if match:
493             url = match.group(1)
494         if not url:
495             sys.stderr.write('runtest.py: could not find a URL:\n')
496             sys.stderr.write(svn_info)
497             sys.exit(1)
498         import tempfile
499         base = tempfile.mkdtemp(prefix='runtest-tmp-')
500
501         command = 'cd %s && svn co -q %s' % (base, url)
502
503         base = os.path.join(base, os.path.split(url)[1])
504         if printcommand:
505             print command
506         if execute_tests:
507             os.system(command)
508     else:
509         base = baseline
510
511     scons_runtest_dir = base
512
513     scons_script_dir = sd or os.path.join(base, 'src', 'script')
514
515     scons_lib_dir = ld or os.path.join(base, 'src', 'engine')
516
517     pythonpath_dir = scons_lib_dir
518
519 if scons:
520     # Let the version of SCons that the -x option pointed to find
521     # its own modules.
522     os.environ['SCONS'] = scons
523 elif scons_lib_dir:
524     # Because SCons is really aggressive about finding its modules,
525     # it sometimes finds SCons modules elsewhere on the system.
526     # This forces SCons to use the modules that are being tested.
527     os.environ['SCONS_LIB_DIR'] = scons_lib_dir
528
529 if scons_exec:
530     os.environ['SCONS_EXEC'] = '1'
531
532 os.environ['SCONS_RUNTEST_DIR'] = scons_runtest_dir
533 os.environ['SCONS_SCRIPT_DIR'] = scons_script_dir
534 os.environ['SCONS_CWD'] = cwd
535
536 os.environ['SCONS_VERSION'] = version
537
538 old_pythonpath = os.environ.get('PYTHONPATH')
539
540 # FIXME: the following is necessary to pull in half of the testing
541 #        harness from $srcdir/etc. Those modules should be transfered
542 #        to QMTest/ once we completely cut over to using that as
543 #        the harness, in which case this manipulation of PYTHONPATH
544 #        should be able to go away.
545 pythonpaths = [ pythonpath_dir ]
546
547 for dir in sp:
548     if format == '--aegis':
549         q = os.path.join(dir, 'build', 'QMTest')
550     else:
551         q = os.path.join(dir, 'QMTest')
552     pythonpaths.append(q)
553
554 os.environ['SCONS_SOURCE_PATH_EXECUTABLE'] = string.join(spe, os.pathsep)
555
556 os.environ['PYTHONPATH'] = string.join(pythonpaths, os.pathsep)
557
558 if old_pythonpath:
559     os.environ['PYTHONPATH'] = os.environ['PYTHONPATH'] + \
560                                os.pathsep + \
561                                old_pythonpath
562
563 tests = []
564
565 if args:
566     if spe:
567         for a in args:
568             if os.path.isabs(a):
569                 tests.extend(glob.glob(a))
570             else:
571                 for dir in spe:
572                     x = os.path.join(dir, a)
573                     globs = glob.glob(x)
574                     if globs:
575                         tests.extend(globs)
576                         break
577     else:
578         for a in args:
579             tests.extend(glob.glob(a))
580 elif testlistfile:
581     tests = open(testlistfile, 'r').readlines()
582     tests = filter(lambda x: x[0] != '#', tests)
583     tests = map(lambda x: x[:-1], tests)
584 elif all and not qmtest:
585     # Find all of the SCons functional tests in the local directory
586     # tree.  This is anything under the 'src' subdirectory that ends
587     # with 'Tests.py', or any Python script (*.py) under the 'test'
588     # subdirectory.
589     #
590     # Note that there are some tests under 'src' that *begin* with
591     # 'test_', but they're packaging and installation tests, not
592     # functional tests, so we don't execute them by default.  (They can
593     # still be executed by hand, though, and are routinely executed
594     # by the Aegis packaging build to make sure that we're building
595     # things correctly.)
596     tdict = {}
597
598     def find_Tests_py(tdict, dirname, names):
599         for n in filter(lambda n: n[-8:] == "Tests.py", names):
600             t = os.path.join(dirname, n)
601             if not tdict.has_key(t):
602                 tdict[t] = 1
603     os.path.walk('src', find_Tests_py, tdict)
604
605     def find_py(tdict, dirname, names):
606         for n in filter(lambda n: n[-3:] == ".py", names):
607             t = os.path.join(dirname, n)
608             if not tdict.has_key(t):
609                 tdict[t] = 1
610     os.path.walk('test', find_py, tdict)
611
612     if format == '--aegis' and aegis:
613         cmd = "aegis -list -unf pf 2>/dev/null"
614         for line in os.popen(cmd, "r").readlines():
615             a = string.split(line)
616             if a[0] == "test" and not tdict.has_key(a[-1]):
617                 tdict[a[-1]] = Test(a[-1], spe)
618         cmd = "aegis -list -unf cf 2>/dev/null"
619         for line in os.popen(cmd, "r").readlines():
620             a = string.split(line)
621             if a[0] == "test":
622                 if a[1] == "remove":
623                     del tdict[a[-1]]
624                 elif not tdict.has_key(a[-1]):
625                     tdict[a[-1]] = Test(a[-1], spe)
626
627     tests = tdict.keys()
628     tests.sort()
629
630 if qmtest:
631     if baseline:
632         aegis_result_stream = 'scons_tdb.AegisBaselineStream'
633         qmr_file = 'baseline.qmr'
634     else:
635         aegis_result_stream = 'scons_tdb.AegisChangeStream'
636         qmr_file = 'results.qmr'
637
638     if print_times:
639         aegis_result_stream = aegis_result_stream + "(print_time='1')"
640
641     qmtest_args = [ qmtest, ]
642
643     if format == '--aegis':
644         dir = builddir
645         if not os.path.isdir(dir):
646             dir = cwd
647         qmtest_args.extend(['-D', dir])
648
649     qmtest_args.extend([
650                 'run',
651                 '--output %s' % qmr_file,
652                 '--format none',
653                 '--result-stream="%s"' % aegis_result_stream,
654               ])
655
656     if python:
657         qmtest_args.append('--context python="%s"' % python)
658
659     if outputfile:
660         if format == '--xml':
661             rsclass = 'scons_tdb.SConsXMLResultStream'
662         else:
663             rsclass = 'scons_tdb.AegisBatchStream'
664         qof = "r'" + outputfile + "'"
665         rs = '--result-stream="%s(filename=%s)"' % (rsclass, qof)
666         qmtest_args.append(rs)
667
668     if format == '--aegis':
669         tests = map(lambda x: string.replace(x, cwd+os.sep, ''), tests)
670     else:
671         os.environ['SCONS'] = os.path.join(cwd, 'src', 'script', 'scons.py')
672
673     cmd = string.join(qmtest_args + tests, ' ')
674     if printcommand:
675         sys.stdout.write(cmd + '\n')
676         sys.stdout.flush()
677     status = 0
678     if execute_tests:
679         status = os.WEXITSTATUS(os.system(cmd))
680     sys.exit(status)
681
682 #try:
683 #    os.chdir(scons_script_dir)
684 #except OSError:
685 #    pass
686
687 tests = map(Test, tests)
688
689 class Unbuffered:
690     def __init__(self, file):
691         self.file = file
692     def write(self, arg):
693         self.file.write(arg)
694         self.file.flush()
695     def __getattr__(self, attr):
696         return getattr(self.file, attr)
697
698 sys.stdout = Unbuffered(sys.stdout)
699
700 if list_only:
701     for t in tests:
702         sys.stdout.write(t.abspath + "\n")
703     sys.exit(0)
704
705 #
706 if not python:
707     if os.name == 'java':
708         python = os.path.join(sys.prefix, 'jython')
709     else:
710         python = sys.executable
711
712 # time.clock() is the suggested interface for doing benchmarking timings,
713 # but time.time() does a better job on Linux systems, so let that be
714 # the non-Windows default.
715
716 if sys.platform == 'win32':
717     time_func = time.clock
718 else:
719     time_func = time.time
720
721 if print_times:
722     print_time_func = lambda fmt, time: sys.stdout.write(fmt % time)
723 else:
724     print_time_func = lambda fmt, time: None
725
726 total_start_time = time_func()
727 for t in tests:
728     t.command_args = [python, '-tt']
729     if debug:
730         t.command_args.append(debug)
731     t.command_args.append(t.abspath)
732     t.command_str = string.join(map(escape, t.command_args), " ")
733     if printcommand:
734         sys.stdout.write(t.command_str + "\n")
735     test_start_time = time_func()
736     if execute_tests:
737         t.execute()
738     t.test_time = time_func() - test_start_time
739     print_time_func("Test execution time: %.1f seconds\n", t.test_time)
740 if len(tests) > 0:
741     tests[0].total_time = time_func() - total_start_time
742     print_time_func("Total execution time for all tests: %.1f seconds\n", tests[0].total_time)
743
744 passed = filter(lambda t: t.status == 0, tests)
745 fail = filter(lambda t: t.status == 1, tests)
746 no_result = filter(lambda t: t.status == 2, tests)
747
748 if len(tests) != 1 and execute_tests:
749     if passed and print_passed_summary:
750         if len(passed) == 1:
751             sys.stdout.write("\nPassed the following test:\n")
752         else:
753             sys.stdout.write("\nPassed the following %d tests:\n" % len(passed))
754         paths = map(lambda x: x.path, passed)
755         sys.stdout.write("\t" + string.join(paths, "\n\t") + "\n")
756     if fail:
757         if len(fail) == 1:
758             sys.stdout.write("\nFailed the following test:\n")
759         else:
760             sys.stdout.write("\nFailed the following %d tests:\n" % len(fail))
761         paths = map(lambda x: x.path, fail)
762         sys.stdout.write("\t" + string.join(paths, "\n\t") + "\n")
763     if no_result:
764         if len(no_result) == 1:
765             sys.stdout.write("\nNO RESULT from the following test:\n")
766         else:
767             sys.stdout.write("\nNO RESULT from the following %d tests:\n" % len(no_result))
768         paths = map(lambda x: x.path, no_result)
769         sys.stdout.write("\t" + string.join(paths, "\n\t") + "\n")
770
771 if outputfile:
772     if outputfile == '-':
773         f = sys.stdout
774     else:
775         f = open(outputfile, 'w')
776     tests[0].header(f)
777     #f.write("test_result = [\n")
778     for t in tests:
779         t.write(f)
780     tests[0].footer(f)
781     #f.write("];\n")
782     if outputfile != '-':
783         f.close()
784
785 if format == '--aegis':
786     sys.exit(0)
787 elif len(fail):
788     sys.exit(1)
789 elif len(no_result):
790     sys.exit(2)
791 else:
792     sys.exit(0)