win-tests.py revision 322442
1#
2#
3# Licensed to the Apache Software Foundation (ASF) under one
4# or more contributor license agreements.  See the NOTICE file
5# distributed with this work for additional information
6# regarding copyright ownership.  The ASF licenses this file
7# to you under the Apache License, Version 2.0 (the
8# "License"); you may not use this file except in compliance
9# with the License.  You may obtain a copy of the License at
10#
11#   http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing,
14# software distributed under the License is distributed on an
15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16# KIND, either express or implied.  See the License for the
17# specific language governing permissions and limitations
18# under the License.
19#
20#
21"""
22Driver for running the tests on Windows.
23
24For a list of options, run this script with the --help option.
25"""
26
27# $HeadURL: https://svn.apache.org/repos/asf/subversion/branches/1.9.x/win-tests.py $
28# $LastChangedRevision: 1718291 $
29
30import os, sys, subprocess
31import filecmp
32import shutil
33import traceback
34import logging
35try:
36  # Python >=3.0
37  import configparser
38except ImportError:
39  # Python <3.0
40  import ConfigParser as configparser
41import string
42import random
43
44import getopt
45try:
46    my_getopt = getopt.gnu_getopt
47except AttributeError:
48    my_getopt = getopt.getopt
49
50def _usage_exit():
51  "print usage, exit the script"
52
53  print("Driver for running the tests on Windows.")
54  print("Usage: python win-tests.py [option] [test-path]")
55  print("")
56  print("Valid options:")
57  print("  -r, --release          : test the Release configuration")
58  print("  -d, --debug            : test the Debug configuration (default)")
59  print("  --bin=PATH             : use the svn binaries installed in PATH")
60  print("  -u URL, --url=URL      : run ra_dav or ra_svn tests against URL;")
61  print("                           will start svnserve for ra_svn tests")
62  print("  -v, --verbose          : talk more")
63  print("  -f, --fs-type=type     : filesystem type to use (fsfs is default)")
64  print("  -c, --cleanup          : cleanup after running a test")
65  print("  -t, --test=TEST        : Run the TEST test (all is default); use")
66  print("                           TEST#n to run a particular test number,")
67  print("                           multiples also accepted e.g. '2,4-7'")
68  print("  --log-level=LEVEL      : Set log level to LEVEL (E.g. DEBUG)")
69  print("  --log-to-stdout        : Write log results to stdout")
70
71  print("  --svnserve-args=list   : comma-separated list of arguments for")
72  print("                           svnserve")
73  print("                           default is '-d,-r,<test-path-root>'")
74  print("  --asp.net-hack         : use '_svn' instead of '.svn' for the admin")
75  print("                           dir name")
76  print("  --httpd-dir            : location where Apache HTTPD is installed")
77  print("  --httpd-port           : port for Apache HTTPD; random port number")
78  print("                           will be used, if not specified")
79  print("  --httpd-daemon         : Run Apache httpd as daemon")
80  print("  --httpd-service        : Run Apache httpd as Windows service (default)")
81  print("  --httpd-no-log         : Disable httpd logging")
82  print("  --http-short-circuit   : Use SVNPathAuthz short_circuit on HTTP server")
83  print("  --disable-http-v2      : Do not advertise support for HTTPv2 on server")
84  print("  --disable-bulk-updates : Disable bulk updates on HTTP server")
85  print("  --ssl-cert             : Path to SSL server certificate to trust.")
86  print("  --javahl               : Run the javahl tests instead of the normal tests")
87  print("  --swig=language        : Run the swig perl/python/ruby tests instead of")
88  print("                           the normal tests")
89  print("  --list                 : print test doc strings only")
90  print("  --milestone-filter=RE  : RE is a regular expression pattern that (when")
91  print("                           used with --list) limits the tests listed to")
92  print("                           those with an associated issue in the tracker")
93  print("                           which has a target milestone that matches RE.")
94  print("  --mode-filter=TYPE     : limit tests to expected TYPE = XFAIL, SKIP, PASS,")
95  print("                           or 'ALL' (default)")
96  print("  --enable-sasl          : enable Cyrus SASL authentication for")
97  print("                           svnserve")
98  print("  -p, --parallel         : run multiple tests in parallel")
99  print("  --server-minor-version : the minor version of the server being")
100  print("                           tested")
101  print("  --config-file          : Configuration file for tests")
102  print("  --fsfs-sharding        : Specify shard size (for fsfs)")
103  print("  --fsfs-packing         : Run 'svnadmin pack' automatically")
104  print("  -q, --quiet            : Deprecated; this is the default.")
105  print("                           Use --set-log-level instead.")
106
107  sys.exit(0)
108
109CMDLINE_TEST_SCRIPT_PATH = 'subversion/tests/cmdline/'
110CMDLINE_TEST_SCRIPT_NATIVE_PATH = CMDLINE_TEST_SCRIPT_PATH.replace('/', os.sep)
111
112sys.path.insert(0, os.path.join('build', 'generator'))
113sys.path.insert(1, 'build')
114
115import gen_win_dependencies
116import gen_base
117version_header = os.path.join('subversion', 'include', 'svn_version.h')
118cp = configparser.ConfigParser()
119cp.read('gen-make.opts')
120gen_obj = gen_win_dependencies.GenDependenciesBase('build.conf', version_header,
121                                                   cp.items('options'))
122opts, args = my_getopt(sys.argv[1:], 'hrdvqct:pu:f:',
123                       ['release', 'debug', 'verbose', 'quiet', 'cleanup',
124                        'test=', 'url=', 'svnserve-args=', 'fs-type=', 'asp.net-hack',
125                        'httpd-dir=', 'httpd-port=', 'httpd-daemon',
126                        'httpd-server', 'http-short-circuit', 'httpd-no-log',
127                        'disable-http-v2', 'disable-bulk-updates', 'help',
128                        'fsfs-packing', 'fsfs-sharding=', 'javahl', 'swig=',
129                        'list', 'enable-sasl', 'bin=', 'parallel',
130                        'config-file=', 'server-minor-version=', 'log-level=',
131                        'log-to-stdout', 'mode-filter=', 'milestone-filter=',
132                        'ssl-cert='])
133if len(args) > 1:
134  print('Warning: non-option arguments after the first one will be ignored')
135
136# Interpret the options and set parameters
137base_url, fs_type, verbose, cleanup = None, None, None, None
138repo_loc = 'local repository.'
139objdir = 'Debug'
140log = 'tests.log'
141faillog = 'fails.log'
142run_svnserve = None
143svnserve_args = None
144run_httpd = None
145httpd_port = None
146httpd_service = None
147httpd_no_log = None
148http_short_circuit = False
149advertise_httpv2 = True
150http_bulk_updates = True
151list_tests = None
152milestone_filter = None
153test_javahl = None
154test_swig = None
155enable_sasl = None
156svn_bin = None
157parallel = None
158fsfs_sharding = None
159fsfs_packing = None
160server_minor_version = None
161config_file = None
162log_to_stdout = None
163mode_filter=None
164tests_to_run = []
165log_level = None
166ssl_cert = None
167
168for opt, val in opts:
169  if opt in ('-h', '--help'):
170    _usage_exit()
171  elif opt in ('-u', '--url'):
172    base_url = val
173  elif opt in ('-f', '--fs-type'):
174    fs_type = val
175  elif opt in ('-v', '--verbose'):
176    verbose = 1
177    log_level = logging.DEBUG
178  elif opt in ('-c', '--cleanup'):
179    cleanup = 1
180  elif opt in ('-t', '--test'):
181    tests_to_run.append(val)
182  elif opt in ['-r', '--release']:
183    objdir = 'Release'
184  elif opt in ['-d', '--debug']:
185    objdir = 'Debug'
186  elif opt == '--svnserve-args':
187    svnserve_args = val.split(',')
188    run_svnserve = 1
189  elif opt == '--asp.net-hack':
190    os.environ['SVN_ASP_DOT_NET_HACK'] = opt
191  elif opt == '--httpd-dir':
192    abs_httpd_dir = os.path.abspath(val)
193    run_httpd = 1
194  elif opt == '--httpd-port':
195    httpd_port = int(val)
196  elif opt == '--httpd-daemon':
197    httpd_service = 0
198  elif opt == '--httpd-service':
199    httpd_service = 1
200  elif opt == '--httpd-no-log':
201    httpd_no_log = 1
202  elif opt == '--http-short-circuit':
203    http_short_circuit = True
204  elif opt == '--disable-http-v2':
205    advertise_httpv2 = False
206  elif opt == '--disable-bulk-updates':
207    http_bulk_updates = False
208  elif opt == '--fsfs-sharding':
209    fsfs_sharding = int(val)
210  elif opt == '--fsfs-packing':
211    fsfs_packing = 1
212  elif opt == '--javahl':
213    test_javahl = 1
214  elif opt == '--swig':
215    if val not in ['perl', 'python', 'ruby']:
216      sys.stderr.write('Running \'%s\' swig tests not supported (yet).\n'
217                        % (val,))
218    test_swig = val
219  elif opt == '--list':
220    list_tests = 1
221  elif opt == '--milestone-filter':
222    milestone_filter = val
223  elif opt == '--mode-filter':
224    mode_filter = val
225  elif opt == '--enable-sasl':
226    enable_sasl = 1
227    base_url = "svn://localhost/"
228  elif opt == '--server-minor-version':
229    server_minor_version = int(val)
230  elif opt == '--bin':
231    svn_bin = val
232  elif opt in ('-p', '--parallel'):
233    parallel = 1
234  elif opt in ('--config-file'):
235    config_file = val
236  elif opt == '--log-to-stdout':
237    log_to_stdout = 1
238  elif opt == '--log-level':
239    log_level = getattr(logging, val, None) or int(val)
240  elif opt == '--ssl-cert':
241    ssl_cert = val
242
243# Calculate the source and test directory names
244abs_srcdir = os.path.abspath("")
245abs_objdir = os.path.join(abs_srcdir, objdir)
246if len(args) == 0:
247  abs_builddir = abs_objdir
248  create_dirs = 0
249else:
250  abs_builddir = os.path.abspath(args[0])
251  create_dirs = 1
252
253# Default to fsfs explicitly
254if not fs_type:
255  fs_type = 'fsfs'
256
257if fs_type == 'bdb':
258  all_tests = gen_obj.test_progs + gen_obj.bdb_test_progs \
259            + gen_obj.scripts + gen_obj.bdb_scripts
260else:
261  all_tests = gen_obj.test_progs + gen_obj.scripts
262
263client_tests = [x for x in all_tests if x.startswith(CMDLINE_TEST_SCRIPT_PATH)]
264
265if run_httpd:
266  if not httpd_port:
267    httpd_port = random.randrange(1024, 30000)
268  if not base_url:
269    base_url = 'http://localhost:' + str(httpd_port)
270
271if base_url:
272  repo_loc = 'remote repository ' + base_url + '.'
273  if base_url[:4] == 'http':
274    log = 'dav-tests.log'
275    faillog = 'dav-fails.log'
276  elif base_url[:3] == 'svn':
277    log = 'svn-tests.log'
278    faillog = 'svn-fails.log'
279    run_svnserve = 1
280  else:
281    # Don't know this scheme, but who're we to judge whether it's
282    # correct or not?
283    log = 'url-tests.log'
284    faillog = 'url-fails.log'
285
286# Have to move the executables where the tests expect them to be
287copied_execs = []   # Store copied exec files to avoid the final dir scan
288
289def create_target_dir(dirname):
290  tgt_dir = os.path.join(abs_builddir, dirname)
291  if not os.path.exists(tgt_dir):
292    if verbose:
293      print("mkdir: %s" % tgt_dir)
294    os.makedirs(tgt_dir)
295
296def copy_changed_file(src, tgt=None, to_dir=None, cleanup=True):
297  if not os.path.isfile(src):
298    print('Could not find ' + src)
299    sys.exit(1)
300
301  if to_dir and not tgt:
302    tgt = os.path.join(to_dir, os.path.basename(src))
303  elif not tgt or (tgt and to_dir):
304    raise RuntimeError("Using 'tgt' *or* 'to_dir' is required" % (tgt,))
305  elif tgt and os.path.isdir(tgt):
306    raise RuntimeError("'%s' is a directory. Use to_dir=" % (tgt,))
307
308  if os.path.exists(tgt):
309    assert os.path.isfile(tgt)
310    if filecmp.cmp(src, tgt):
311      if verbose:
312        print("same: %s" % src)
313        print(" and: %s" % tgt)
314      return 0
315  if verbose:
316    print("copy: %s" % src)
317    print("  to: %s" % tgt)
318  shutil.copy(src, tgt)
319
320  if cleanup:
321    copied_execs.append(tgt)
322
323def locate_libs():
324  "Move DLLs to a known location and set env vars"
325
326  debug = (objdir == 'Debug')
327
328  for lib in gen_obj._libraries.values():
329
330    if debug:
331      name, dir = lib.debug_dll_name, lib.debug_dll_dir
332    else:
333      name, dir = lib.dll_name, lib.dll_dir
334
335    if name and dir:
336      src = os.path.join(dir, name)
337      if os.path.exists(src):
338        copy_changed_file(src, to_dir=abs_builddir, cleanup=False)
339
340    for name in lib.extra_bin:
341      src = os.path.join(dir, name)
342      copy_changed_file(src, to_dir=abs_builddir)
343
344
345  # Copy the Subversion library DLLs
346  for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
347    if isinstance(i, gen_base.TargetLib) and i.msvc_export:
348      src = os.path.join(abs_objdir, i.filename)
349      if os.path.isfile(src):
350        copy_changed_file(src, to_dir=abs_builddir,
351                          cleanup=False)
352
353  # Copy the Apache modules
354  if run_httpd and cp.has_option('options', '--with-httpd'):
355    mod_dav_svn_path = os.path.join(abs_objdir, 'subversion',
356                                    'mod_dav_svn', 'mod_dav_svn.so')
357    mod_authz_svn_path = os.path.join(abs_objdir, 'subversion',
358                                      'mod_authz_svn', 'mod_authz_svn.so')
359    mod_dontdothat_path = os.path.join(abs_objdir, 'tools', 'server-side',
360                                        'mod_dontdothat', 'mod_dontdothat.so')
361
362    copy_changed_file(mod_dav_svn_path, to_dir=abs_builddir, cleanup=False)
363    copy_changed_file(mod_authz_svn_path, to_dir=abs_builddir, cleanup=False)
364    copy_changed_file(mod_dontdothat_path, to_dir=abs_builddir, cleanup=False)
365
366  os.environ['PATH'] = abs_builddir + os.pathsep + os.environ['PATH']
367
368def fix_case(path):
369    path = os.path.normpath(path)
370    parts = path.split(os.path.sep)
371    drive = parts[0].upper()
372    parts = parts[1:]
373    path = drive + os.path.sep
374    for part in parts:
375        dirs = os.listdir(path)
376        for dir in dirs:
377            if dir.lower() == part.lower():
378                path = os.path.join(path, dir)
379                break
380    return path
381
382class Svnserve:
383  "Run svnserve for ra_svn tests"
384  def __init__(self, svnserve_args, objdir, abs_objdir, abs_builddir):
385    self.args = svnserve_args
386    self.name = 'svnserve.exe'
387    self.kind = objdir
388    self.path = os.path.join(abs_objdir,
389                             'subversion', 'svnserve', self.name)
390    self.root = os.path.join(abs_builddir, CMDLINE_TEST_SCRIPT_NATIVE_PATH)
391    self.proc = None
392
393  def __del__(self):
394    "Stop svnserve when the object is deleted"
395    self.stop()
396
397  def _quote(self, arg):
398    if ' ' in arg:
399      return '"' + arg + '"'
400    else:
401      return arg
402
403  def start(self):
404    if not self.args:
405      args = [self.name, '-d', '-r', self.root]
406    else:
407      args = [self.name] + self.args
408    print('Starting %s %s' % (self.kind, self.name))
409
410    self.proc = subprocess.Popen([self.path] + args[1:])
411
412  def stop(self):
413    if self.proc is not None:
414      try:
415        print('Stopping %s' % self.name)
416        self.proc.poll();
417        if self.proc.returncode is None:
418          self.proc.kill();
419        return
420      except AttributeError:
421        pass
422    print('Svnserve.stop not implemented')
423
424class Httpd:
425  "Run httpd for DAV tests"
426  def __init__(self, abs_httpd_dir, abs_objdir, abs_builddir, httpd_port,
427               service, no_log, httpv2, short_circuit, bulk_updates):
428    self.name = 'apache.exe'
429    self.httpd_port = httpd_port
430    self.httpd_dir = abs_httpd_dir
431
432    if httpv2:
433      self.httpv2_option = 'on'
434    else:
435      self.httpv2_option = 'off'
436
437    if bulk_updates:
438      self.bulkupdates_option = 'on'
439    else:
440      self.bulkupdates_option = 'off'
441
442    self.service = service
443    self.proc = None
444    self.path = os.path.join(self.httpd_dir, 'bin', self.name)
445
446    if short_circuit:
447      self.path_authz_option = 'short_circuit'
448    else:
449      self.path_authz_option = 'on'
450
451    if not os.path.exists(self.path):
452      self.name = 'httpd.exe'
453      self.path = os.path.join(self.httpd_dir, 'bin', self.name)
454      if not os.path.exists(self.path):
455        raise RuntimeError("Could not find a valid httpd binary!")
456
457    self.root_dir = os.path.join(CMDLINE_TEST_SCRIPT_NATIVE_PATH, 'httpd')
458    self.root = os.path.join(abs_builddir, self.root_dir)
459    self.authz_file = os.path.join(abs_builddir,
460                                   CMDLINE_TEST_SCRIPT_NATIVE_PATH,
461                                   'svn-test-work', 'authz')
462    self.dontdothat_file = os.path.join(abs_builddir,
463                                         CMDLINE_TEST_SCRIPT_NATIVE_PATH,
464                                         'svn-test-work', 'dontdothat')
465    self.httpd_config = os.path.join(self.root, 'httpd.conf')
466    self.httpd_users = os.path.join(self.root, 'users')
467    self.httpd_mime_types = os.path.join(self.root, 'mime.types')
468    self.httpd_groups = os.path.join(self.root, 'groups')
469    self.abs_builddir = abs_builddir
470    self.abs_objdir = abs_objdir
471    self.service_name = 'svn-test-httpd-' + str(httpd_port)
472
473    if self.service:
474      self.httpd_args = [self.name, '-n', self._quote(self.service_name),
475                         '-f', self._quote(self.httpd_config)]
476    else:
477      self.httpd_args = [self.name, '-f', self._quote(self.httpd_config)]
478
479    create_target_dir(self.root_dir)
480
481    self._create_users_file()
482    self._create_groups_file()
483    self._create_mime_types_file()
484    self._create_dontdothat_file()
485
486    # Obtain version.
487    version_vals = gen_obj._libraries['httpd'].version.split('.')
488    self.httpd_ver = float('%s.%s' % (version_vals[0], version_vals[1]))
489
490    # Create httpd config file
491    fp = open(self.httpd_config, 'w')
492
493    # Limit the number of threads (default = 64)
494    fp.write('<IfModule mpm_winnt.c>\n')
495    fp.write('ThreadsPerChild 16\n')
496    fp.write('</IfModule>\n')
497
498    # Global Environment
499    fp.write('ServerRoot   ' + self._quote(self.root) + '\n')
500    fp.write('DocumentRoot ' + self._quote(self.root) + '\n')
501    fp.write('ServerName   localhost\n')
502    fp.write('PidFile      pid\n')
503    fp.write('ErrorLog     log\n')
504    fp.write('Listen       ' + str(self.httpd_port) + '\n')
505
506    if not no_log:
507      fp.write('LogFormat    "%h %l %u %t \\"%r\\" %>s %b" common\n')
508      fp.write('Customlog    log common\n')
509      fp.write('LogLevel     Debug\n')
510    else:
511      fp.write('LogLevel     Crit\n')
512
513    # Write LoadModule for minimal system module
514    fp.write(self._sys_module('dav_module', 'mod_dav.so'))
515    if self.httpd_ver >= 2.3:
516      fp.write(self._sys_module('access_compat_module', 'mod_access_compat.so'))
517      fp.write(self._sys_module('authz_core_module', 'mod_authz_core.so'))
518      fp.write(self._sys_module('authz_user_module', 'mod_authz_user.so'))
519      fp.write(self._sys_module('authn_core_module', 'mod_authn_core.so'))
520    if self.httpd_ver >= 2.2:
521      fp.write(self._sys_module('auth_basic_module', 'mod_auth_basic.so'))
522      fp.write(self._sys_module('authn_file_module', 'mod_authn_file.so'))
523      fp.write(self._sys_module('authz_groupfile_module', 'mod_authz_groupfile.so'))
524      fp.write(self._sys_module('authz_host_module', 'mod_authz_host.so'))
525    else:
526      fp.write(self._sys_module('auth_module', 'mod_auth.so'))
527    fp.write(self._sys_module('alias_module', 'mod_alias.so'))
528    fp.write(self._sys_module('mime_module', 'mod_mime.so'))
529    fp.write(self._sys_module('log_config_module', 'mod_log_config.so'))
530
531    # Write LoadModule for Subversion modules
532    fp.write(self._svn_module('dav_svn_module', 'mod_dav_svn.so'))
533    fp.write(self._svn_module('authz_svn_module', 'mod_authz_svn.so'))
534
535    # And for mod_dontdothat
536    fp.write(self._svn_module('dontdothat_module', 'mod_dontdothat.so'))
537
538    # Don't handle .htaccess, symlinks, etc.
539    fp.write('<Directory />\n')
540    fp.write('AllowOverride None\n')
541    fp.write('Options None\n')
542    fp.write('</Directory>\n\n')
543
544    # Define two locations for repositories
545    fp.write(self._svn_repo('repositories'))
546    fp.write(self._svn_repo('local_tmp'))
547    fp.write(self._svn_authz_repo())
548
549    # And two redirects for the redirect tests
550    fp.write('RedirectMatch permanent ^/svn-test-work/repositories/'
551             'REDIRECT-PERM-(.*)$ /svn-test-work/repositories/$1\n')
552    fp.write('RedirectMatch           ^/svn-test-work/repositories/'
553             'REDIRECT-TEMP-(.*)$ /svn-test-work/repositories/$1\n')
554
555    fp.write('TypesConfig     ' + self._quote(self.httpd_mime_types) + '\n')
556    fp.write('HostNameLookups Off\n')
557
558    fp.close()
559
560  def __del__(self):
561    "Stop httpd when the object is deleted"
562    self.stop()
563
564  def _quote(self, arg):
565    if ' ' in arg:
566      return '"' + arg + '"'
567    else:
568      return arg
569
570  def _create_users_file(self):
571    "Create users file"
572    htpasswd = os.path.join(self.httpd_dir, 'bin', 'htpasswd.exe')
573    # Create the cheapest to compare password form for our testsuite
574    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bcp', self.httpd_users,
575                                    'jrandom', 'rayjandom'])
576    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp',  self.httpd_users,
577                                    'jconstant', 'rayjandom'])
578    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp',  self.httpd_users,
579                                    'JRANDOM', 'rayjandom'])
580    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp',  self.httpd_users,
581                                    'JCONSTANT', 'rayjandom'])
582
583  def _create_groups_file(self):
584    "Create groups for mod_authz_svn tests"
585    fp = open(self.httpd_groups, 'w')
586    fp.write('random: jrandom\n')
587    fp.write('constant: jconstant\n')
588    fp.close()
589
590  def _create_mime_types_file(self):
591    "Create empty mime.types file"
592    fp = open(self.httpd_mime_types, 'w')
593    fp.close()
594
595  def _create_dontdothat_file(self):
596    "Create empty mime.types file"
597    # If the tests have not previously been run or were cleaned
598    # up, then 'svn-test-work' does not exist yet.
599    parent_dir = os.path.dirname(self.dontdothat_file)
600    if not os.path.exists(parent_dir):
601      os.makedirs(parent_dir)
602
603    fp = open(self.dontdothat_file, 'w')
604    fp.write('[recursive-actions]\n')
605    fp.write('/ = deny\n')
606    fp.close()
607
608  def _sys_module(self, name, path):
609    full_path = os.path.join(self.httpd_dir, 'modules', path)
610    return 'LoadModule ' + name + " " + self._quote(full_path) + '\n'
611
612  def _svn_module(self, name, path):
613    full_path = os.path.join(self.abs_builddir, path)
614    return 'LoadModule ' + name + ' ' + self._quote(full_path) + '\n'
615
616  def _svn_repo(self, name):
617    path = os.path.join(self.abs_builddir,
618                        CMDLINE_TEST_SCRIPT_NATIVE_PATH,
619                        'svn-test-work', name)
620    location = '/svn-test-work/' + name
621    ddt_location = '/ddt-test-work/' + name
622    return \
623      '<Location ' + location + '>\n' \
624      '  DAV             svn\n' \
625      '  SVNParentPath   ' + self._quote(path) + '\n' \
626      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
627      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
628      '  SVNAllowBulkUpdates ' + self.bulkupdates_option + '\n' \
629      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
630      '  AuthType        Basic\n' \
631      '  AuthName        "Subversion Repository"\n' \
632      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
633      '  Require         valid-user\n' \
634      '</Location>\n' \
635      '<Location ' + ddt_location + '>\n' \
636      '  DAV             svn\n' \
637      '  SVNParentPath   ' + self._quote(path) + '\n' \
638      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
639      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
640      '  SVNAllowBulkUpdates ' + self.bulkupdates_option + '\n' \
641      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
642      '  AuthType        Basic\n' \
643      '  AuthName        "Subversion Repository"\n' \
644      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
645      '  Require         valid-user\n' \
646      '  DontDoThatConfigFile ' + self._quote(self.dontdothat_file) + '\n' \
647      '</Location>\n'
648
649  def _svn_authz_repo(self):
650    local_tmp = os.path.join(self.abs_builddir,
651                             CMDLINE_TEST_SCRIPT_NATIVE_PATH,
652                             'svn-test-work', 'local_tmp')
653    return \
654      '<Location /authz-test-work/anon>' + '\n' \
655      '  DAV               svn' + '\n' \
656      '  SVNParentPath     ' + local_tmp + '\n' \
657      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
658      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
659      '  SVNListParentPath On' + '\n' \
660      '  <IfModule mod_authz_core.c>' + '\n' \
661      '    Require all granted' + '\n' \
662      '  </IfModule>' + '\n' \
663      '  <IfModule !mod_authz_core.c>' + '\n' \
664      '    Allow from all' + '\n' \
665      '  </IfModule>' + '\n' \
666      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
667      '</Location>' + '\n' \
668      '<Location /authz-test-work/mixed>' + '\n' \
669      '  DAV               svn' + '\n' \
670      '  SVNParentPath     ' + local_tmp + '\n' \
671      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
672      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
673      '  SVNListParentPath On' + '\n' \
674      '  AuthType          Basic' + '\n' \
675      '  AuthName          "Subversion Repository"' + '\n' \
676      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
677      '  Require           valid-user' + '\n' \
678      '  Satisfy Any' + '\n' \
679      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
680      '</Location>' + '\n' \
681      '<Location /authz-test-work/mixed-noauthwhenanon>' + '\n' \
682      '  DAV               svn' + '\n' \
683      '  SVNParentPath     ' + local_tmp + '\n' \
684      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
685      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
686      '  SVNListParentPath On' + '\n' \
687      '  AuthType          Basic' + '\n' \
688      '  AuthName          "Subversion Repository"' + '\n' \
689      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
690      '  Require           valid-user' + '\n' \
691      '  AuthzSVNNoAuthWhenAnonymousAllowed On' + '\n' \
692      '  SVNPathAuthz On' + '\n' \
693      '</Location>' + '\n' \
694      '<Location /authz-test-work/authn>' + '\n' \
695      '  DAV               svn' + '\n' \
696      '  SVNParentPath     ' + local_tmp + '\n' \
697      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
698      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
699      '  SVNListParentPath On' + '\n' \
700      '  AuthType          Basic' + '\n' \
701      '  AuthName          "Subversion Repository"' + '\n' \
702      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
703      '  Require           valid-user' + '\n' \
704      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
705      '</Location>' + '\n' \
706      '<Location /authz-test-work/authn-anonoff>' + '\n' \
707      '  DAV               svn' + '\n' \
708      '  SVNParentPath     ' + local_tmp + '\n' \
709      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
710      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
711      '  SVNListParentPath On' + '\n' \
712      '  AuthType          Basic' + '\n' \
713      '  AuthName          "Subversion Repository"' + '\n' \
714      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
715      '  Require           valid-user' + '\n' \
716      '  AuthzSVNAnonymous Off' + '\n' \
717      '  SVNPathAuthz On' + '\n' \
718      '</Location>' + '\n' \
719      '<Location /authz-test-work/authn-lcuser>' + '\n' \
720      '  DAV               svn' + '\n' \
721      '  SVNParentPath     ' + local_tmp + '\n' \
722      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
723      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
724      '  SVNListParentPath On' + '\n' \
725      '  AuthType          Basic' + '\n' \
726      '  AuthName          "Subversion Repository"' + '\n' \
727      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
728      '  Require           valid-user' + '\n' \
729      '  AuthzForceUsernameCase Lower' + '\n' \
730      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
731      '</Location>' + '\n' \
732      '<Location /authz-test-work/authn-lcuser>' + '\n' \
733      '  DAV               svn' + '\n' \
734      '  SVNParentPath     ' + local_tmp + '\n' \
735      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
736      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
737      '  SVNListParentPath On' + '\n' \
738      '  AuthType          Basic' + '\n' \
739      '  AuthName          "Subversion Repository"' + '\n' \
740      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
741      '  Require           valid-user' + '\n' \
742      '  AuthzForceUsernameCase Lower' + '\n' \
743      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
744      '</Location>' + '\n' \
745      '<Location /authz-test-work/authn-group>' + '\n' \
746      '  DAV               svn' + '\n' \
747      '  SVNParentPath     ' + local_tmp + '\n' \
748      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
749      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
750      '  SVNListParentPath On' + '\n' \
751      '  AuthType          Basic' + '\n' \
752      '  AuthName          "Subversion Repository"' + '\n' \
753      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
754      '  AuthGroupFile    ' + self._quote(self.httpd_groups) + '\n' \
755      '  Require           group random' + '\n' \
756      '  AuthzSVNAuthoritative Off' + '\n' \
757      '  SVNPathAuthz On' + '\n' \
758      '</Location>' + '\n' \
759      '<IfModule mod_authz_core.c>' + '\n' \
760      '<Location /authz-test-work/sallrany>' + '\n' \
761      '  DAV               svn' + '\n' \
762      '  SVNParentPath     ' + local_tmp + '\n' \
763      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
764      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
765      '  SVNListParentPath On' + '\n' \
766      '  AuthType          Basic' + '\n' \
767      '  AuthName          "Subversion Repository"' + '\n' \
768      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
769      '  AuthzSendForbiddenOnFailure On' + '\n' \
770      '  Satisfy All' + '\n' \
771      '  <RequireAny>' + '\n' \
772      '    Require valid-user' + '\n' \
773      '    Require expr req(\'ALLOW\') == \'1\'' + '\n' \
774      '  </RequireAny>' + '\n' \
775      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
776      '</Location>' + '\n' \
777      '<Location /authz-test-work/sallrall>'+ '\n' \
778      '  DAV               svn' + '\n' \
779      '  SVNParentPath     ' + local_tmp + '\n' \
780      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
781      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
782      '  SVNListParentPath On' + '\n' \
783      '  AuthType          Basic' + '\n' \
784      '  AuthName          "Subversion Repository"' + '\n' \
785      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
786      '  AuthzSendForbiddenOnFailure On' + '\n' \
787      '  Satisfy All' + '\n' \
788      '  <RequireAll>' + '\n' \
789      '  Require valid-user' + '\n' \
790      '  Require expr req(\'ALLOW\') == \'1\'' + '\n' \
791      '</RequireAll>' + '\n' \
792      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
793      '</Location>' + '\n' \
794      '</IfModule>' + '\n' \
795
796  def start(self):
797    if self.service:
798      self._start_service()
799    else:
800      self._start_daemon()
801
802  def stop(self):
803    if self.service:
804      self._stop_service()
805    else:
806      self._stop_daemon()
807
808  def _start_service(self):
809    "Install and start HTTPD service"
810    print('Installing service %s' % self.service_name)
811    os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'install'])
812    print('Starting service %s' % self.service_name)
813    os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'start'])
814
815  def _stop_service(self):
816    "Stop and uninstall HTTPD service"
817    os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'stop'])
818    os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'uninstall'])
819
820  def _start_daemon(self):
821    "Start HTTPD as daemon"
822    print('Starting httpd as daemon')
823    print(self.httpd_args)
824    self.proc = subprocess.Popen([self.path] + self.httpd_args[1:])
825
826  def _stop_daemon(self):
827    "Stop the HTTPD daemon"
828    if self.proc is not None:
829      try:
830        print('Stopping %s' % self.name)
831        self.proc.poll();
832        if self.proc.returncode is None:
833          self.proc.kill();
834        return
835      except AttributeError:
836        pass
837    print('Httpd.stop_daemon not implemented')
838
839# Move the binaries to the test directory
840create_target_dir(abs_builddir)
841locate_libs()
842if create_dirs:
843  for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
844    if isinstance(i, gen_base.TargetExe):
845      src = os.path.join(abs_objdir, i.filename)
846
847      if os.path.isfile(src):
848        dst = os.path.join(abs_builddir, i.filename)
849        create_target_dir(os.path.dirname(dst))
850        copy_changed_file(src, dst)
851
852# Create the base directory for Python tests
853create_target_dir(CMDLINE_TEST_SCRIPT_NATIVE_PATH)
854
855# Ensure the tests directory is correctly cased
856abs_builddir = fix_case(abs_builddir)
857
858daemon = None
859# Run the tests
860
861# No need to start any servers if we are only listing the tests.
862if not list_tests:
863  if run_svnserve:
864    daemon = Svnserve(svnserve_args, objdir, abs_objdir, abs_builddir)
865
866  if run_httpd:
867    daemon = Httpd(abs_httpd_dir, abs_objdir, abs_builddir, httpd_port,
868                   httpd_service, httpd_no_log,
869                   advertise_httpv2, http_short_circuit,
870                   http_bulk_updates)
871
872  # Start service daemon, if any
873  if daemon:
874    daemon.start()
875
876# Find the full path and filename of any test that is specified just by
877# its base name.
878if len(tests_to_run) != 0:
879  tests = []
880  for t in tests_to_run:
881    tns = None
882    if '#' in t:
883      t, tns = t.split('#')
884
885    test = [x for x in all_tests if x.split('/')[-1] == t]
886    if not test and not (t.endswith('-test.exe') or t.endswith('_tests.py')):
887      # The lengths of '-test.exe' and of '_tests.py' are both 9.
888      test = [x for x in all_tests if x.split('/')[-1][:-9] == t]
889
890    if not test:
891      print("Skipping test '%s', test not found." % t)
892    elif tns:
893      tests.append('%s#%s' % (test[0], tns))
894    else:
895      tests.extend(test)
896
897  tests_to_run = tests
898else:
899  tests_to_run = all_tests
900
901
902if list_tests:
903  print('Listing %s configuration on %s' % (objdir, repo_loc))
904else:
905  print('Testing %s configuration on %s' % (objdir, repo_loc))
906sys.path.insert(0, os.path.join(abs_srcdir, 'build'))
907
908if not test_javahl and not test_swig:
909  import run_tests
910  if log_to_stdout:
911    log_file = None
912    fail_log_file = None
913  else:
914    log_file = os.path.join(abs_builddir, log)
915    fail_log_file = os.path.join(abs_builddir, faillog)
916
917  if run_httpd:
918    httpd_version = gen_obj._libraries['httpd'].version
919  else:
920    httpd_version = None
921
922  opts, args = run_tests.create_parser().parse_args([])
923  opts.url = base_url
924  opts.fs_type = fs_type
925  opts.http_library = 'serf'
926  opts.server_minor_version = server_minor_version
927  opts.cleanup = cleanup
928  opts.enable_sasl = enable_sasl
929  opts.parallel = parallel
930  opts.config_file = config_file
931  opts.fsfs_sharding = fsfs_sharding
932  opts.fsfs_packing = fsfs_packing
933  opts.list_tests = list_tests
934  opts.svn_bin = svn_bin
935  opts.mode_filter = mode_filter
936  opts.milestone_filter = milestone_filter
937  opts.httpd_version = httpd_version
938  opts.set_log_level = log_level
939  opts.ssl_cert = ssl_cert
940  th = run_tests.TestHarness(abs_srcdir, abs_builddir,
941                             log_file, fail_log_file, opts)
942  old_cwd = os.getcwd()
943  try:
944    os.chdir(abs_builddir)
945    failed = th.run(tests_to_run)
946  except:
947    os.chdir(old_cwd)
948    raise
949  else:
950    os.chdir(old_cwd)
951elif test_javahl:
952  failed = False
953
954  java_exe = None
955
956  for path in os.environ["PATH"].split(os.pathsep):
957    if os.path.isfile(os.path.join(path, 'java.exe')):
958      java_exe = os.path.join(path, 'java.exe')
959      break
960
961  if not java_exe and 'java_sdk' in gen_obj._libraries:
962    jdk = gen_obj._libraries['java_sdk']
963
964    if os.path.isfile(os.path.join(jdk.lib_dir, '../bin/java.exe')):
965      java_exe = os.path.join(jdk.lib_dir, '../bin/java.exe')
966
967  if not java_exe:
968    print('Java not found. Skipping Java tests')
969  else:
970    args = (os.path.abspath(java_exe),)
971    if (objdir == 'Debug'):
972      args = args + ('-Xcheck:jni',)
973
974    args = args + (
975            '-Dtest.rootdir=' + os.path.join(abs_builddir, 'javahl'),
976            '-Dtest.srcdir=' + os.path.join(abs_srcdir,
977                                            'subversion/bindings/javahl'),
978            '-Dtest.rooturl=',
979            '-Dtest.fstype=' + fs_type ,
980            '-Dtest.tests=',
981
982            '-Djava.library.path='
983                      + os.path.join(abs_objdir,
984                                     'subversion/bindings/javahl/native'),
985            '-classpath',
986            os.path.join(abs_srcdir, 'subversion/bindings/javahl/classes') +';' +
987              gen_obj.junit_path
988           )
989
990    sys.stderr.flush()
991    print('Running org.apache.subversion tests:')
992    sys.stdout.flush()
993
994    r = subprocess.call(args + tuple(['org.apache.subversion.javahl.RunTests']))
995    sys.stdout.flush()
996    sys.stderr.flush()
997    if (r != 0):
998      print('[Test runner reported failure]')
999      failed = True
1000
1001    print('Running org.tigris.subversion tests:')
1002    sys.stdout.flush()
1003    r = subprocess.call(args + tuple(['org.tigris.subversion.javahl.RunTests']))
1004    sys.stdout.flush()
1005    sys.stderr.flush()
1006    if (r != 0):
1007      print('[Test runner reported failure]')
1008      failed = True
1009elif test_swig == 'perl':
1010  failed = False
1011  swig_dir = os.path.join(abs_builddir, 'swig')
1012  swig_pl_dir = os.path.join(swig_dir, 'p5lib')
1013  swig_pl_svn = os.path.join(swig_pl_dir, 'SVN')
1014  swig_pl_auto_svn = os.path.join(swig_pl_dir, 'auto', 'SVN')
1015
1016  create_target_dir(swig_pl_svn)
1017
1018  for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
1019    if isinstance(i, gen_base.TargetSWIG) and i.lang == 'perl':
1020      mod_dir = os.path.join(swig_pl_auto_svn, '_' + i.name[5:].capitalize())
1021      create_target_dir(mod_dir)
1022      copy_changed_file(os.path.join(abs_objdir, i.filename), to_dir=mod_dir)
1023
1024    elif isinstance(i, gen_base.TargetSWIGLib) and i.lang == 'perl':
1025      copy_changed_file(os.path.join(abs_objdir, i.filename),
1026                        to_dir=abs_builddir)
1027
1028  pm_src = os.path.join(abs_srcdir, 'subversion', 'bindings', 'swig', 'perl',
1029                        'native')
1030
1031  tests = []
1032
1033  for root, dirs, files in os.walk(pm_src):
1034    for name in files:
1035      if name.endswith('.pm'):
1036        fn = os.path.join(root, name)
1037        copy_changed_file(fn, to_dir=swig_pl_svn)
1038      elif name.endswith('.t'):
1039        tests.append(os.path.relpath(os.path.join(root, name), pm_src))
1040
1041  perl5lib = swig_pl_dir
1042  if 'PERL5LIB' in os.environ:
1043    perl5lib += os.pathsep + os.environ['PERL5LIB']
1044
1045  perl_exe = 'perl.exe'
1046
1047  print('-- Running Swig Perl tests --')
1048  sys.stdout.flush()
1049  old_cwd = os.getcwd()
1050  try:
1051    os.chdir(pm_src)
1052
1053    os.environ['PERL5LIB'] = perl5lib
1054    os.environ["SVN_DBG_NO_ABORT_ON_ERROR_LEAK"] = 'YES'
1055
1056    r = subprocess.call([
1057              perl_exe,
1058              '-MExtUtils::Command::MM',
1059              '-e', 'test_harness()'
1060              ] + tests)
1061  finally:
1062    os.chdir(old_cwd)
1063
1064  if (r != 0):
1065    print('[Test runner reported failure]')
1066    failed = True
1067elif test_swig == 'python':
1068  failed = False
1069  swig_dir = os.path.join(abs_builddir, 'swig')
1070  swig_py_dir = os.path.join(swig_dir, 'pylib')
1071  swig_py_libsvn = os.path.join(swig_py_dir, 'libsvn')
1072  swig_py_svn = os.path.join(swig_py_dir, 'svn')
1073
1074  create_target_dir(swig_py_libsvn)
1075  create_target_dir(swig_py_svn)
1076
1077  for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
1078    if (isinstance(i, gen_base.TargetSWIG)
1079        or isinstance(i, gen_base.TargetSWIGLib)) and i.lang == 'python':
1080
1081      src = os.path.join(abs_objdir, i.filename)
1082      copy_changed_file(src, to_dir=swig_py_libsvn)
1083
1084  py_src = os.path.join(abs_srcdir, 'subversion', 'bindings', 'swig', 'python')
1085
1086  for py_file in os.listdir(py_src):
1087    if py_file.endswith('.py'):
1088      copy_changed_file(os.path.join(py_src, py_file),
1089                        to_dir=swig_py_libsvn)
1090
1091  py_src_svn = os.path.join(py_src, 'svn')
1092  for py_file in os.listdir(py_src_svn):
1093    if py_file.endswith('.py'):
1094      copy_changed_file(os.path.join(py_src_svn, py_file),
1095                        to_dir=swig_py_svn)
1096
1097  print('-- Running Swig Python tests --')
1098  sys.stdout.flush()
1099
1100  pythonpath = swig_py_dir
1101  if 'PYTHONPATH' in os.environ:
1102    pythonpath += os.pathsep + os.environ['PYTHONPATH']
1103
1104  python_exe = 'python.exe'
1105  old_cwd = os.getcwd()
1106  try:
1107    os.environ['PYTHONPATH'] = pythonpath
1108
1109    r = subprocess.call([
1110              python_exe,
1111              os.path.join(py_src, 'tests', 'run_all.py')
1112              ])
1113  finally:
1114    os.chdir(old_cwd)
1115
1116    if (r != 0):
1117      print('[Test runner reported failure]')
1118      failed = True
1119
1120elif test_swig == 'ruby':
1121  failed = False
1122
1123  if 'ruby' not in gen_obj._libraries:
1124    print('Ruby not found. Skipping Ruby tests')
1125  else:
1126    ruby_lib = gen_obj._libraries['ruby']
1127
1128    ruby_exe = 'ruby.exe'
1129    ruby_subdir = os.path.join('subversion', 'bindings', 'swig', 'ruby')
1130    ruby_args = [
1131        '-I', os.path.join(abs_srcdir, ruby_subdir),
1132        os.path.join(abs_srcdir, ruby_subdir, 'test', 'run-test.rb'),
1133        '--verbose'
1134      ]
1135
1136    print('-- Running Swig Ruby tests --')
1137    sys.stdout.flush()
1138    old_cwd = os.getcwd()
1139    try:
1140      os.chdir(ruby_subdir)
1141
1142      os.environ["BUILD_TYPE"] = objdir
1143      os.environ["SVN_DBG_NO_ABORT_ON_ERROR_LEAK"] = 'YES'
1144      r = subprocess.call([ruby_exe] + ruby_args)
1145    finally:
1146      os.chdir(old_cwd)
1147
1148    sys.stdout.flush()
1149    sys.stderr.flush()
1150    if (r != 0):
1151      print('[Test runner reported failure]')
1152      failed = True
1153
1154# Stop service daemon, if any
1155if daemon:
1156  del daemon
1157
1158# Remove the execs again
1159for tgt in copied_execs:
1160  try:
1161    if os.path.isfile(tgt):
1162      if verbose:
1163        print("kill: %s" % tgt)
1164      os.unlink(tgt)
1165  except:
1166    traceback.print_exc(file=sys.stdout)
1167    pass
1168
1169
1170if failed:
1171  sys.exit(1)
1172