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