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