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