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