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