Move 2.0 changes collected in branches/pending back to trunk for further
[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 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
88
89 import getopt
90 import glob
91 import os
92 import re
93 import stat
94 import sys
95 import time
96
97 if not hasattr(os, 'WEXITSTATUS'):
98     os.WEXITSTATUS = lambda x: x
99
100 cwd = os.getcwd()
101
102 all = 0
103 baseline = 0
104 builddir = os.path.join(cwd, 'build')
105 debug = ''
106 execute_tests = 1
107 format = None
108 list_only = None
109 printcommand = 1
110 package = None
111 print_passed_summary = None
112 scons = None
113 scons_exec = None
114 outputfile = None
115 testlistfile = None
116 version = ''
117 print_times = None
118 python = None
119 sp = None
120 spe = None
121
122 helpstr = """\
123 Usage: runtest.py [OPTIONS] [TEST ...]
124 Options:
125   -a, --all                   Run all tests.
126   --aegis                     Print results in Aegis format.
127   -b BASE, --baseline BASE    Run test scripts against baseline BASE.
128   --builddir DIR              Directory in which packages were built.
129   -d, --debug                 Run test scripts under the Python debugger.
130   -f FILE, --file FILE        Run tests in specified FILE.
131   -h, --help                  Print this message and exit.
132   -l, --list                  List available tests and exit.
133   -n, --no-exec               No execute, just print command lines.
134   --noqmtest                  Execute tests directly, not using QMTest.
135   -o FILE, --output FILE      Print test results to FILE.
136   -P Python                   Use the specified Python interpreter.
137   -p PACKAGE, --package PACKAGE
138                               Test against the specified PACKAGE:
139                                 deb           Debian
140                                 local-tar-gz  .tar.gz standalone package
141                                 local-zip     .zip standalone package
142                                 rpm           Red Hat
143                                 src-tar-gz    .tar.gz source package
144                                 src-zip       .zip source package
145                                 tar-gz        .tar.gz distribution
146                                 zip           .zip distribution
147   --passed                    Summarize which tests passed.
148   --qmtest                    Run using the QMTest harness.
149   -q, --quiet                 Don't print the test being executed.
150   --sp PATH                   The Aegis search path.
151   --spe PATH                  The Aegis executable search path.
152   -t, --time                  Print test execution time.
153   -v version                  Specify the SCons version.
154   --verbose=LEVEL             Set verbose level: 1 = print executed commands,
155                                 2 = print commands and non-zero output,
156                                 3 = print commands and all output.
157   -X                          Test script is executable, don't feed to Python.
158   -x SCRIPT, --exec SCRIPT    Test SCRIPT.
159   --xml                       Print results in SCons XML format.
160 """
161
162 opts, args = getopt.getopt(sys.argv[1:], "ab:df:hlno:P:p:qv:Xx:t",
163                             ['all', 'aegis', 'baseline=', 'builddir=',
164                              'debug', 'file=', 'help',
165                              'list', 'no-exec', 'noqmtest', 'output=',
166                              'package=', 'passed', 'python=', 'qmtest',
167                              'quiet', 'sp=', 'spe=', 'time',
168                              'version=', 'exec=',
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         if sys.platform == 'win32':
211             # typically in c:/PythonXX/Scripts
212             qmtest = 'qmtest.py'
213         else:
214             qmtest = 'qmtest'
215     elif o in ['-q', '--quiet']:
216         printcommand = 0
217     elif o in ['--sp']:
218         sp = a.split(os.pathsep)
219     elif o in ['--spe']:
220         spe = a.split(os.pathsep)
221     elif o in ['-t', '--time']:
222         print_times = 1
223     elif o in ['--verbose']:
224         os.environ['TESTCMD_VERBOSE'] = a
225     elif o in ['-v', '--version']:
226         version = a
227     elif o in ['-X']:
228         scons_exec = 1
229     elif o in ['-x', '--exec']:
230         scons = a
231     elif o in ['--aegis', '--xml']:
232         format = o
233
234 if not args and not all and not testlistfile:
235     sys.stderr.write("""\
236 runtest.py:  No tests were specified.
237              List one or more tests on the command line, use the
238              -f option to specify a file containing a list of tests,
239              or use the -a option to find and run all tests.
240
241 """)
242     sys.exit(1)
243
244 if sys.platform in ('win32', 'cygwin'):
245
246     def whereis(file):
247         pathext = [''] + os.environ['PATHEXT'].split(os.pathsep)
248         for dir in os.environ['PATH'].split(os.pathsep):
249             f = os.path.join(dir, file)
250             for ext in pathext:
251                 fext = f + ext
252                 if os.path.isfile(fext):
253                     return fext
254         return None
255
256 else:
257
258     def whereis(file):
259         for dir in os.environ['PATH'].split(os.pathsep):
260             f = os.path.join(dir, file)
261             if os.path.isfile(f):
262                 try:
263                     st = os.stat(f)
264                 except OSError:
265                     continue
266                 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
267                     return f
268         return None
269
270 # See if --qmtest or --noqmtest specified
271 try:
272     qmtest
273 except NameError:
274     qmtest = None
275     # Old code for using QMTest by default if it's installed.
276     # We now default to not using QMTest unless explicitly asked for.
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 = paths.split(os.pathsep)
298         if spe is None:
299             spe = os.popen("aesub '$spe' 2>/dev/null", "r").read()[:-1]
300             spe = spe.split(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 = s.replace('\\', '\\\\')
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 = list(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                     self.status = self.status >> 8
377     else:
378         class PopenExecutor(Base):
379             def execute(self):
380                 p = popen2.Popen3(self.command_str, 1)
381                 p.tochild.close()
382                 self.stdout = p.fromchild.read()
383                 self.stderr = p.childerr.read()
384                 self.status = p.wait()
385                 self.status = self.status >> 8
386 else:
387     class PopenExecutor(Base):
388         def execute(self):
389             p = subprocess.Popen(self.command_str,
390                                  stdout=subprocess.PIPE,
391                                  stderr=subprocess.PIPE,
392                                  shell=True)
393             self.stdout = p.stdout.read()
394             self.stderr = p.stderr.read()
395             self.status = p.wait()
396
397 class Aegis(SystemExecutor):
398     def header(self, f):
399         f.write('test_result = [\n')
400     def write(self, f):
401         f.write('    { file_name = "%s";\n' % self.path)
402         f.write('      exit_status = %d; },\n' % self.status)
403     def footer(self, f):
404         f.write('];\n')
405
406 class XML(PopenExecutor):
407     def header(self, f):
408         f.write('  <results>\n')
409     def write(self, f):
410         f.write('    <test>\n')
411         f.write('      <file_name>%s</file_name>\n' % self.path)
412         f.write('      <command_line>%s</command_line>\n' % self.command_str)
413         f.write('      <exit_status>%s</exit_status>\n' % self.status)
414         f.write('      <stdout>%s</stdout>\n' % self.stdout)
415         f.write('      <stderr>%s</stderr>\n' % self.stderr)
416         f.write('      <time>%.1f</time>\n' % self.test_time)
417         f.write('    </test>\n')
418     def footer(self, f):
419         f.write('  <time>%.1f</time>\n' % self.total_time)
420         f.write('  </results>\n')
421
422 format_class = {
423     None        : SystemExecutor,
424     '--aegis'   : Aegis,
425     '--xml'     : XML,
426 }
427
428 Test = format_class[format]
429
430 if package:
431
432     dir = {
433         'deb'          : 'usr',
434         'local-tar-gz' : None,
435         'local-zip'    : None,
436         'rpm'          : 'usr',
437         'src-tar-gz'   : '',
438         'src-zip'      : '',
439         'tar-gz'       : '',
440         'zip'          : '',
441     }
442
443     # The hard-coded "python2.1" here is the library directory
444     # name on Debian systems, not an executable, so it's all right.
445     lib = {
446         'deb'        : os.path.join('python2.1', 'site-packages')
447     }
448
449     if package not in dir:
450         sys.stderr.write("Unknown package '%s'\n" % package)
451         sys.exit(2)
452
453     test_dir = os.path.join(builddir, 'test-%s' % package)
454
455     if dir[package] is None:
456         scons_script_dir = test_dir
457         globs = glob.glob(os.path.join(test_dir, 'scons-local-*'))
458         if not globs:
459             sys.stderr.write("No `scons-local-*' dir in `%s'\n" % test_dir)
460             sys.exit(2)
461         scons_lib_dir = None
462         pythonpath_dir = globs[len(globs)-1]
463     elif sys.platform == 'win32':
464         scons_script_dir = os.path.join(test_dir, dir[package], 'Scripts')
465         scons_lib_dir = os.path.join(test_dir, dir[package])
466         pythonpath_dir = scons_lib_dir
467     else:
468         scons_script_dir = os.path.join(test_dir, dir[package], 'bin')
469         l = lib.get(package, 'scons')
470         scons_lib_dir = os.path.join(test_dir, dir[package], 'lib', l)
471         pythonpath_dir = scons_lib_dir
472
473     scons_runtest_dir = builddir
474
475 else:
476     sd = None
477     ld = None
478
479     # XXX:  Logic like the following will be necessary once
480     # we fix runtest.py to run tests within an Aegis change
481     # without symlinks back to the baseline(s).
482     #
483     #if spe:
484     #    if not scons:
485     #        for dir in spe:
486     #            d = os.path.join(dir, 'src', 'script')
487     #            f = os.path.join(d, 'scons.py')
488     #            if os.path.isfile(f):
489     #                sd = d
490     #                scons = f
491     #    spe = map(lambda x: os.path.join(x, 'src', 'engine'), spe)
492     #    ld = os.pathsep.join(spe)
493
494     if not baseline or baseline == '.':
495         base = cwd
496     elif baseline == '-':
497         # Tentative code for fetching information directly from the
498         # QMTest context file.
499         #
500         #import qm.common
501         #import qm.test.context
502         #qm.rc.Load("test")
503         #context = qm.test.context.Context()
504         #context.Read('context')
505
506         url = None
507         svn_info =  os.popen("svn info 2>&1", "r").read()
508         match = re.search('URL: (.*)', svn_info)
509         if match:
510             url = match.group(1)
511         if not url:
512             sys.stderr.write('runtest.py: could not find a URL:\n')
513             sys.stderr.write(svn_info)
514             sys.exit(1)
515         import tempfile
516         base = tempfile.mkdtemp(prefix='runtest-tmp-')
517
518         command = 'cd %s && svn co -q %s' % (base, url)
519
520         base = os.path.join(base, os.path.split(url)[1])
521         if printcommand:
522             print command
523         if execute_tests:
524             os.system(command)
525     else:
526         base = baseline
527
528     scons_runtest_dir = base
529
530     scons_script_dir = sd or os.path.join(base, 'src', 'script')
531
532     scons_lib_dir = ld or os.path.join(base, 'src', 'engine')
533
534     pythonpath_dir = scons_lib_dir
535
536 if scons:
537     # Let the version of SCons that the -x option pointed to find
538     # its own modules.
539     os.environ['SCONS'] = scons
540 elif scons_lib_dir:
541     # Because SCons is really aggressive about finding its modules,
542     # it sometimes finds SCons modules elsewhere on the system.
543     # This forces SCons to use the modules that are being tested.
544     os.environ['SCONS_LIB_DIR'] = scons_lib_dir
545
546 if scons_exec:
547     os.environ['SCONS_EXEC'] = '1'
548
549 os.environ['SCONS_RUNTEST_DIR'] = scons_runtest_dir
550 os.environ['SCONS_SCRIPT_DIR'] = scons_script_dir
551 os.environ['SCONS_CWD'] = cwd
552
553 os.environ['SCONS_VERSION'] = version
554
555 old_pythonpath = os.environ.get('PYTHONPATH')
556
557 # FIXME: the following is necessary to pull in half of the testing
558 #        harness from $srcdir/etc. Those modules should be transfered
559 #        to QMTest/ once we completely cut over to using that as
560 #        the harness, in which case this manipulation of PYTHONPATH
561 #        should be able to go away.
562 pythonpaths = [ pythonpath_dir ]
563
564 for dir in sp:
565     if format == '--aegis':
566         q = os.path.join(dir, 'build', 'QMTest')
567     else:
568         q = os.path.join(dir, 'QMTest')
569     pythonpaths.append(q)
570
571 os.environ['SCONS_SOURCE_PATH_EXECUTABLE'] = os.pathsep.join(spe)
572
573 os.environ['PYTHONPATH'] = os.pathsep.join(pythonpaths)
574
575 if old_pythonpath:
576     os.environ['PYTHONPATH'] = os.environ['PYTHONPATH'] + \
577                                os.pathsep + \
578                                old_pythonpath
579
580 tests = []
581
582 def find_Tests_py(tdict, dirname, names):
583     for n in [n for n in names if n[-8:] == "Tests.py"]:
584         tdict[os.path.join(dirname, n)] = 1
585
586 def find_py(tdict, dirname, names):
587     tests = [n for n in names if n[-3:] == ".py"]
588     try:
589         excludes = open(os.path.join(dirname,".exclude_tests")).readlines()
590     except (OSError, IOError):
591         pass
592     else:
593         for exclude in excludes:
594             exclude = exclude.split('#' , 1)[0]
595             exclude = exclude.strip()
596             if not exclude: continue
597             tests = [n for n in tests if n != exclude]
598     for n in tests:
599         tdict[os.path.join(dirname, n)] = 1
600
601 if args:
602     if spe:
603         for a in args:
604             if os.path.isabs(a):
605                 tests.extend(glob.glob(a))
606             else:
607                 for dir in spe:
608                     x = os.path.join(dir, a)
609                     globs = glob.glob(x)
610                     if globs:
611                         tests.extend(globs)
612                         break
613     else:
614         for a in args:
615             for path in glob.glob(a):
616                 if os.path.isdir(path):
617                     tdict = {}
618                     if path[:3] == 'src':
619                         os.path.walk(path, find_Tests_py, tdict)
620                     elif path[:4] == 'test':
621                         os.path.walk(path, find_py, tdict)
622                     t = tdict.keys()
623                     t.sort()
624                     tests.extend(t)
625                 else:
626                     tests.append(path)
627 elif testlistfile:
628     tests = open(testlistfile, 'r').readlines()
629     tests = [x for x in tests if x[0] != '#']
630     tests = [x[:-1] for x in tests]
631 elif all and not qmtest:
632     # Find all of the SCons functional tests in the local directory
633     # tree.  This is anything under the 'src' subdirectory that ends
634     # with 'Tests.py', or any Python script (*.py) under the 'test'
635     # subdirectory.
636     #
637     # Note that there are some tests under 'src' that *begin* with
638     # 'test_', but they're packaging and installation tests, not
639     # functional tests, so we don't execute them by default.  (They can
640     # still be executed by hand, though, and are routinely executed
641     # by the Aegis packaging build to make sure that we're building
642     # things correctly.)
643     tdict = {}
644     os.path.walk('src', find_Tests_py, tdict)
645     os.path.walk('test', find_py, tdict)
646     if format == '--aegis' and aegis:
647         cmd = "aegis -list -unf pf 2>/dev/null"
648         for line in os.popen(cmd, "r").readlines():
649             a = line.split()
650             if a[0] == "test" and a[-1] not in tdict:
651                 tdict[a[-1]] = Test(a[-1], spe)
652         cmd = "aegis -list -unf cf 2>/dev/null"
653         for line in os.popen(cmd, "r").readlines():
654             a = line.split()
655             if a[0] == "test":
656                 if a[1] == "remove":
657                     del tdict[a[-1]]
658                 elif a[-1] not in tdict:
659                     tdict[a[-1]] = Test(a[-1], spe)
660
661     tests = tdict.keys()
662     tests.sort()
663
664 if qmtest:
665     if baseline:
666         aegis_result_stream = 'scons_tdb.AegisBaselineStream'
667         qmr_file = 'baseline.qmr'
668     else:
669         aegis_result_stream = 'scons_tdb.AegisChangeStream'
670         qmr_file = 'results.qmr'
671
672     if print_times:
673         aegis_result_stream = aegis_result_stream + "(print_time='1')"
674
675     qmtest_args = [ qmtest, ]
676
677     if format == '--aegis':
678         dir = builddir
679         if not os.path.isdir(dir):
680             dir = cwd
681         qmtest_args.extend(['-D', dir])
682
683     qmtest_args.extend([
684                 'run',
685                 '--output %s' % qmr_file,
686                 '--format none',
687                 '--result-stream="%s"' % aegis_result_stream,
688               ])
689
690     if python:
691         qmtest_args.append('--context python="%s"' % python)
692
693     if outputfile:
694         if format == '--xml':
695             rsclass = 'scons_tdb.SConsXMLResultStream'
696         else:
697             rsclass = 'scons_tdb.AegisBatchStream'
698         qof = "r'" + outputfile + "'"
699         rs = '--result-stream="%s(filename=%s)"' % (rsclass, qof)
700         qmtest_args.append(rs)
701
702     if format == '--aegis':
703         tests = [x.replace(cwd+os.sep, '') for x in tests]
704     else:
705         os.environ['SCONS'] = os.path.join(cwd, 'src', 'script', 'scons.py')
706
707     cmd = ' '.join(qmtest_args + tests)
708     if printcommand:
709         sys.stdout.write(cmd + '\n')
710         sys.stdout.flush()
711     status = 0
712     if execute_tests:
713         status = os.WEXITSTATUS(os.system(cmd))
714     sys.exit(status)
715
716 #try:
717 #    os.chdir(scons_script_dir)
718 #except OSError:
719 #    pass
720
721 tests = list(map(Test, tests))
722
723 class Unbuffered:
724     def __init__(self, file):
725         self.file = file
726     def write(self, arg):
727         self.file.write(arg)
728         self.file.flush()
729     def __getattr__(self, attr):
730         return getattr(self.file, attr)
731
732 sys.stdout = Unbuffered(sys.stdout)
733 sys.stderr = Unbuffered(sys.stderr)
734
735 if list_only:
736     for t in tests:
737         sys.stdout.write(t.path + "\n")
738     sys.exit(0)
739
740 #
741 if not python:
742     if os.name == 'java':
743         python = os.path.join(sys.prefix, 'jython')
744     else:
745         python = sys.executable
746
747 # time.clock() is the suggested interface for doing benchmarking timings,
748 # but time.time() does a better job on Linux systems, so let that be
749 # the non-Windows default.
750
751 if sys.platform == 'win32':
752     time_func = time.clock
753 else:
754     time_func = time.time
755
756 if print_times:
757     print_time_func = lambda fmt, time: sys.stdout.write(fmt % time)
758 else:
759     print_time_func = lambda fmt, time: None
760
761 total_start_time = time_func()
762 for t in tests:
763     command_args = ['-tt']
764     if debug:
765         command_args.append(debug)
766     command_args.append(t.path)
767     t.command_args = [python] + command_args
768     t.command_str = " ".join([escape(python)] + command_args)
769     if printcommand:
770         sys.stdout.write(t.command_str + "\n")
771     test_start_time = time_func()
772     if execute_tests:
773         t.execute()
774     t.test_time = time_func() - test_start_time
775     print_time_func("Test execution time: %.1f seconds\n", t.test_time)
776 if len(tests) > 0:
777     tests[0].total_time = time_func() - total_start_time
778     print_time_func("Total execution time for all tests: %.1f seconds\n", tests[0].total_time)
779
780 passed = [t for t in tests if t.status == 0]
781 fail = [t for t in tests if t.status == 1]
782 no_result = [t for t in tests if t.status == 2]
783
784 if len(tests) != 1 and execute_tests:
785     if passed and print_passed_summary:
786         if len(passed) == 1:
787             sys.stdout.write("\nPassed the following test:\n")
788         else:
789             sys.stdout.write("\nPassed the following %d tests:\n" % len(passed))
790         paths = [x.path for x in passed]
791         sys.stdout.write("\t" + "\n\t".join(paths) + "\n")
792     if fail:
793         if len(fail) == 1:
794             sys.stdout.write("\nFailed the following test:\n")
795         else:
796             sys.stdout.write("\nFailed the following %d tests:\n" % len(fail))
797         paths = [x.path for x in fail]
798         sys.stdout.write("\t" + "\n\t".join(paths) + "\n")
799     if no_result:
800         if len(no_result) == 1:
801             sys.stdout.write("\nNO RESULT from the following test:\n")
802         else:
803             sys.stdout.write("\nNO RESULT from the following %d tests:\n" % len(no_result))
804         paths = [x.path for x in no_result]
805         sys.stdout.write("\t" + "\n\t".join(paths) + "\n")
806
807 if outputfile:
808     if outputfile == '-':
809         f = sys.stdout
810     else:
811         f = open(outputfile, 'w')
812     tests[0].header(f)
813     #f.write("test_result = [\n")
814     for t in tests:
815         t.write(f)
816     tests[0].footer(f)
817     #f.write("];\n")
818     if outputfile != '-':
819         f.close()
820
821 if format == '--aegis':
822     sys.exit(0)
823 elif len(fail):
824     sys.exit(1)
825 elif len(no_result):
826     sys.exit(2)
827 else:
828     sys.exit(0)
829
830 # Local Variables:
831 # tab-width:4
832 # indent-tabs-mode:nil
833 # End:
834 # vim: set expandtab tabstop=4 shiftwidth=4: