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