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