win-tests.py revision 286506
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: http://svn.apache.org/repos/asf/subversion/branches/1.8.x/win-tests.py $
28# $LastChangedRevision: 1692801 $
29
30import os, sys, subprocess
31import filecmp
32import shutil
33import traceback
34try:
35  # Python >=3.0
36  import configparser
37except ImportError:
38  # Python <3.0
39  import ConfigParser as configparser
40import string
41import random
42
43import getopt
44try:
45    my_getopt = getopt.gnu_getopt
46except AttributeError:
47    my_getopt = getopt.getopt
48
49def _usage_exit():
50  "print usage, exit the script"
51
52  print("Driver for running the tests on Windows.")
53  print("Usage: python win-tests.py [option] [test-path]")
54  print("")
55  print("Valid options:")
56  print("  -r, --release          : test the Release configuration")
57  print("  -d, --debug            : test the Debug configuration (default)")
58  print("  --bin=PATH             : use the svn binaries installed in PATH")
59  print("  -u URL, --url=URL      : run ra_dav or ra_svn tests against URL;")
60  print("                           will start svnserve for ra_svn tests")
61  print("  -v, --verbose          : talk more")
62  print("  -q, --quiet            : talk less")
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("  --list                 : print test doc strings only")
88  print("  --milestone-filter=RE  : RE is a regular expression pattern that (when")
89  print("                           used with --list) limits the tests listed to")
90  print("                           those with an associated issue in the tracker")
91  print("                           which has a target milestone that matches RE.")
92  print("  --mode-filter=TYPE     : limit tests to expected TYPE = XFAIL, SKIP, PASS,")
93  print("                           or 'ALL' (default)")
94  print("  --enable-sasl          : enable Cyrus SASL authentication for")
95  print("                           svnserve")
96  print("  -p, --parallel         : run multiple tests in parallel")
97  print("  --server-minor-version : the minor version of the server being")
98  print("                           tested")
99  print("  --config-file          : Configuration file for tests")
100  print("  --fsfs-sharding        : Specify shard size (for fsfs)")
101  print("  --fsfs-packing         : Run 'svnadmin pack' automatically")
102
103  sys.exit(0)
104
105CMDLINE_TEST_SCRIPT_PATH = 'subversion/tests/cmdline/'
106CMDLINE_TEST_SCRIPT_NATIVE_PATH = CMDLINE_TEST_SCRIPT_PATH.replace('/', os.sep)
107
108sys.path.insert(0, os.path.join('build', 'generator'))
109sys.path.insert(1, 'build')
110
111import gen_win
112version_header = os.path.join('subversion', 'include', 'svn_version.h')
113cp = configparser.ConfigParser()
114cp.read('gen-make.opts')
115gen_obj = gen_win.GeneratorBase('build.conf', version_header,
116                                cp.items('options'))
117all_tests = gen_obj.test_progs + gen_obj.bdb_test_progs \
118          + gen_obj.scripts + gen_obj.bdb_scripts
119client_tests = [x for x in all_tests if x.startswith(CMDLINE_TEST_SCRIPT_PATH)]
120
121svn_dlls = []
122for section in gen_obj.sections.values():
123  if section.options.get("msvc-export"):
124    dll_basename = section.name + "-" + str(gen_obj.version) + ".dll"
125    svn_dlls.append(os.path.join("subversion", section.name, dll_basename))
126
127opts, args = my_getopt(sys.argv[1:], 'hrdvqct:pu:f:',
128                       ['release', 'debug', 'verbose', 'quiet', 'cleanup',
129                        'test=', 'url=', 'svnserve-args=', 'fs-type=', 'asp.net-hack',
130                        'httpd-dir=', 'httpd-port=', 'httpd-daemon',
131                        'httpd-server', 'http-short-circuit', 'httpd-no-log',
132                        'disable-http-v2', 'disable-bulk-updates', 'help',
133                        'fsfs-packing', 'fsfs-sharding=', 'javahl',
134                        'list', 'enable-sasl', 'bin=', 'parallel',
135                        'config-file=', 'server-minor-version=', 'log-level=',
136                        'log-to-stdout', 'mode-filter=', 'milestone-filter=',
137                        'ssl-cert='])
138if len(args) > 1:
139  print('Warning: non-option arguments after the first one will be ignored')
140
141# Interpret the options and set parameters
142base_url, fs_type, verbose, quiet, cleanup = None, None, None, None, None
143repo_loc = 'local repository.'
144objdir = 'Debug'
145log = 'tests.log'
146faillog = 'fails.log'
147run_svnserve = None
148svnserve_args = None
149run_httpd = None
150httpd_port = None
151httpd_service = None
152httpd_no_log = None
153http_short_circuit = False
154advertise_httpv2 = True
155http_bulk_updates = True
156list_tests = None
157milestone_filter = None
158test_javahl = None
159enable_sasl = None
160svn_bin = None
161parallel = None
162fsfs_sharding = None
163fsfs_packing = None
164server_minor_version = None
165config_file = None
166log_to_stdout = None
167mode_filter=None
168tests_to_run = []
169log_level = None
170ssl_cert = None
171
172for opt, val in opts:
173  if opt in ('-h', '--help'):
174    _usage_exit()
175  elif opt in ('-u', '--url'):
176    base_url = val
177  elif opt in ('-f', '--fs-type'):
178    fs_type = val
179  elif opt in ('-v', '--verbose'):
180    verbose = 1
181  elif opt in ('-q', '--quiet'):
182    quiet = 1
183  elif opt in ('-c', '--cleanup'):
184    cleanup = 1
185  elif opt in ('-t', '--test'):
186    tests_to_run.append(val)
187  elif opt in ['-r', '--release']:
188    objdir = 'Release'
189  elif opt in ['-d', '--debug']:
190    objdir = 'Debug'
191  elif opt == '--svnserve-args':
192    svnserve_args = val.split(',')
193    run_svnserve = 1
194  elif opt == '--asp.net-hack':
195    os.environ['SVN_ASP_DOT_NET_HACK'] = opt
196  elif opt == '--httpd-dir':
197    abs_httpd_dir = os.path.abspath(val)
198    run_httpd = 1
199  elif opt == '--httpd-port':
200    httpd_port = int(val)
201  elif opt == '--httpd-daemon':
202    httpd_service = 0
203  elif opt == '--httpd-service':
204    httpd_service = 1
205  elif opt == '--httpd-no-log':
206    httpd_no_log = 1
207  elif opt == '--http-short-circuit':
208    http_short_circuit = True
209  elif opt == '--disable-http-v2':
210    advertise_httpv2 = False
211  elif opt == '--disable-bulk-updates':
212    http_bulk_updates = False
213  elif opt == '--fsfs-sharding':
214    fsfs_sharding = int(val)
215  elif opt == '--fsfs-packing':
216    fsfs_packing = 1
217  elif opt == '--javahl':
218    test_javahl = 1
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 = 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 = 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
257# Don't run bdb tests if they want to test fsfs
258if fs_type == 'fsfs':
259  all_tests = gen_obj.test_progs + gen_obj.scripts
260
261if run_httpd:
262  if not httpd_port:
263    httpd_port = random.randrange(1024, 30000)
264  if not base_url:
265    base_url = 'http://localhost:' + str(httpd_port)
266
267if base_url:
268  repo_loc = 'remote repository ' + base_url + '.'
269  if base_url[:4] == 'http':
270    log = 'dav-tests.log'
271    faillog = 'dav-fails.log'
272  elif base_url[:3] == 'svn':
273    log = 'svn-tests.log'
274    faillog = 'svn-fails.log'
275    run_svnserve = 1
276  else:
277    # Don't know this scheme, but who're we to judge whether it's
278    # correct or not?
279    log = 'url-tests.log'
280    faillog = 'url-fails.log'
281
282# Have to move the executables where the tests expect them to be
283copied_execs = []   # Store copied exec files to avoid the final dir scan
284
285def create_target_dir(dirname):
286  tgt_dir = os.path.join(abs_builddir, dirname)
287  if not os.path.exists(tgt_dir):
288    if verbose:
289      print("mkdir: %s" % tgt_dir)
290    os.makedirs(tgt_dir)
291
292def copy_changed_file(src, tgt):
293  if not os.path.isfile(src):
294    print('Could not find ' + src)
295    sys.exit(1)
296  if os.path.isdir(tgt):
297    tgt = os.path.join(tgt, os.path.basename(src))
298  if os.path.exists(tgt):
299    assert os.path.isfile(tgt)
300    if filecmp.cmp(src, tgt):
301      if verbose:
302        print("same: %s" % src)
303        print(" and: %s" % tgt)
304      return 0
305  if verbose:
306    print("copy: %s" % src)
307    print("  to: %s" % tgt)
308  shutil.copy(src, tgt)
309  return 1
310
311def copy_execs(baton, dirname, names):
312  copied_execs = baton
313  for name in names:
314    if not name.endswith('.exe'):
315      continue
316    src = os.path.join(dirname, name)
317    tgt = os.path.join(abs_builddir, dirname, name)
318    create_target_dir(dirname)
319    if copy_changed_file(src, tgt):
320      copied_execs.append(tgt)
321
322def locate_libs():
323  "Move DLLs to a known location and set env vars"
324
325  dlls = []
326
327  # look for APR 1.x dll's and use those if found
328  apr_test_path = os.path.join(gen_obj.apr_path, objdir, 'libapr-1.dll')
329  if os.path.exists(apr_test_path):
330    suffix = "-1"
331  else:
332    suffix = ""
333
334  if cp.has_option('options', '--with-static-apr'):
335    dlls.append(os.path.join(gen_obj.apr_path, objdir,
336                             'libapr%s.dll' % (suffix)))
337    dlls.append(os.path.join(gen_obj.apr_util_path, objdir,
338                             'libaprutil%s.dll' % (suffix)))
339
340  if gen_obj.libintl_path is not None:
341    dlls.append(os.path.join(gen_obj.libintl_path, 'bin', 'intl3_svn.dll'))
342
343  if gen_obj.bdb_lib is not None:
344    partial_path = os.path.join(gen_obj.bdb_path, 'bin', gen_obj.bdb_lib)
345    if objdir == 'Debug':
346      dlls.append(partial_path + 'd.dll')
347    else:
348      dlls.append(partial_path + '.dll')
349
350  if gen_obj.sasl_path is not None:
351    dlls.append(os.path.join(gen_obj.sasl_path, 'lib', 'libsasl.dll'))
352
353  for dll in dlls:
354    copy_changed_file(dll, abs_objdir)
355
356  # Copy the Subversion library DLLs
357  if not cp.has_option('options', '--disable-shared'):
358    for svn_dll in svn_dlls:
359      copy_changed_file(os.path.join(abs_objdir, svn_dll), abs_objdir)
360
361  # Copy the Apache modules
362  if run_httpd and cp.has_option('options', '--with-httpd'):
363    mod_dav_svn_path = os.path.join(abs_objdir, 'subversion',
364                                    'mod_dav_svn', 'mod_dav_svn.so')
365    mod_authz_svn_path = os.path.join(abs_objdir, 'subversion',
366                                      'mod_authz_svn', 'mod_authz_svn.so')
367    mod_dontdothat_path = os.path.join(abs_objdir, 'tools', 'server-side',
368                                        'mod_dontdothat', 'mod_dontdothat.so')
369
370    copy_changed_file(mod_dav_svn_path, abs_objdir)
371    copy_changed_file(mod_authz_svn_path, abs_objdir)
372    copy_changed_file(mod_dontdothat_path, abs_objdir)
373
374  os.environ['PATH'] = abs_objdir + os.pathsep + os.environ['PATH']
375
376def fix_case(path):
377    path = os.path.normpath(path)
378    parts = path.split(os.path.sep)
379    drive = parts[0].upper()
380    parts = parts[1:]
381    path = drive + os.path.sep
382    for part in parts:
383        dirs = os.listdir(path)
384        for dir in dirs:
385            if dir.lower() == part.lower():
386                path = os.path.join(path, dir)
387                break
388    return path
389
390class Svnserve:
391  "Run svnserve for ra_svn tests"
392  def __init__(self, svnserve_args, objdir, abs_objdir, abs_builddir):
393    self.args = svnserve_args
394    self.name = 'svnserve.exe'
395    self.kind = objdir
396    self.path = os.path.join(abs_objdir,
397                             'subversion', 'svnserve', self.name)
398    self.root = os.path.join(abs_builddir, CMDLINE_TEST_SCRIPT_NATIVE_PATH)
399    self.proc_handle = None
400
401  def __del__(self):
402    "Stop svnserve when the object is deleted"
403    self.stop()
404
405  def _quote(self, arg):
406    if ' ' in arg:
407      return '"' + arg + '"'
408    else:
409      return arg
410
411  def start(self):
412    if not self.args:
413      args = [self.name, '-d', '-r', self.root]
414    else:
415      args = [self.name] + self.args
416    print('Starting %s %s' % (self.kind, self.name))
417    try:
418      import win32process
419      import win32con
420      args = ' '.join([self._quote(x) for x in args])
421      self.proc_handle = (
422        win32process.CreateProcess(self._quote(self.path), args,
423                                   None, None, 0,
424                                   win32con.CREATE_NEW_CONSOLE,
425                                   None, None, win32process.STARTUPINFO()))[0]
426    except ImportError:
427      os.spawnv(os.P_NOWAIT, self.path, args)
428
429  def stop(self):
430    if self.proc_handle is not None:
431      try:
432        import win32process
433        print('Stopping %s' % self.name)
434        win32process.TerminateProcess(self.proc_handle, 0)
435        return
436      except ImportError:
437        pass
438    print('Svnserve.stop not implemented')
439
440class Httpd:
441  "Run httpd for DAV tests"
442  def __init__(self, abs_httpd_dir, abs_objdir, abs_builddir, httpd_port,
443               service, no_log, httpv2, short_circuit, bulk_updates):
444    self.name = 'apache.exe'
445    self.httpd_port = httpd_port
446    self.httpd_dir = abs_httpd_dir
447
448    if httpv2:
449      self.httpv2_option = 'on'
450    else:
451      self.httpv2_option = 'off'
452
453    if bulk_updates:
454      self.bulkupdates_option = 'on'
455    else:
456      self.bulkupdates_option = 'off'
457
458    self.service = service
459    self.proc_handle = None
460    self.path = os.path.join(self.httpd_dir, 'bin', self.name)
461
462    if short_circuit:
463      self.path_authz_option = 'short_circuit'
464    else:
465      self.path_authz_option = 'on'
466
467    if not os.path.exists(self.path):
468      self.name = 'httpd.exe'
469      self.path = os.path.join(self.httpd_dir, 'bin', self.name)
470      if not os.path.exists(self.path):
471        raise RuntimeError("Could not find a valid httpd binary!")
472
473    self.root_dir = os.path.join(CMDLINE_TEST_SCRIPT_NATIVE_PATH, 'httpd')
474    self.root = os.path.join(abs_builddir, self.root_dir)
475    self.authz_file = os.path.join(abs_builddir,
476                                   CMDLINE_TEST_SCRIPT_NATIVE_PATH,
477                                   'svn-test-work', 'authz')
478    self.dontdothat_file = os.path.join(abs_builddir,
479                                         CMDLINE_TEST_SCRIPT_NATIVE_PATH,
480                                         'svn-test-work', 'dontdothat')
481    self.httpd_config = os.path.join(self.root, 'httpd.conf')
482    self.httpd_users = os.path.join(self.root, 'users')
483    self.httpd_mime_types = os.path.join(self.root, 'mime.types')
484    self.httpd_groups = os.path.join(self.root, 'groups')
485    self.abs_builddir = abs_builddir
486    self.abs_objdir = abs_objdir
487    self.service_name = 'svn-test-httpd-' + str(httpd_port)
488
489    if self.service:
490      self.httpd_args = [self.name, '-n', self._quote(self.service_name),
491                         '-f', self._quote(self.httpd_config)]
492    else:
493      self.httpd_args = [self.name, '-f', self._quote(self.httpd_config)]
494
495    create_target_dir(self.root_dir)
496
497    self._create_users_file()
498    self._create_groups_file()
499    self._create_mime_types_file()
500    self._create_dontdothat_file()
501
502    # Determine version.
503    if os.path.exists(os.path.join(self.httpd_dir,
504                                   'modules', 'mod_access_compat.so')):
505      self.httpd_ver = 2.3
506    elif os.path.exists(os.path.join(self.httpd_dir,
507                                     'modules', 'mod_auth_basic.so')):
508      self.httpd_ver = 2.2
509    else:
510      self.httpd_ver = 2.0
511
512    # Create httpd config file
513    fp = open(self.httpd_config, 'w')
514
515    # Limit the number of threads (default = 64)
516    fp.write('<IfModule mpm_winnt.c>\n')
517    fp.write('ThreadsPerChild 16\n')
518    fp.write('</IfModule>\n')
519
520    # Global Environment
521    fp.write('ServerRoot   ' + self._quote(self.root) + '\n')
522    fp.write('DocumentRoot ' + self._quote(self.root) + '\n')
523    fp.write('ServerName   localhost\n')
524    fp.write('PidFile      pid\n')
525    fp.write('ErrorLog     log\n')
526    fp.write('Listen       ' + str(self.httpd_port) + '\n')
527
528    if not no_log:
529      fp.write('LogFormat    "%h %l %u %t \\"%r\\" %>s %b" common\n')
530      fp.write('Customlog    log common\n')
531      fp.write('LogLevel     Debug\n')
532    else:
533      fp.write('LogLevel     Crit\n')
534
535    # Write LoadModule for minimal system module
536    fp.write(self._sys_module('dav_module', 'mod_dav.so'))
537    if self.httpd_ver >= 2.3:
538      fp.write(self._sys_module('access_compat_module', 'mod_access_compat.so'))
539      fp.write(self._sys_module('authz_core_module', 'mod_authz_core.so'))
540      fp.write(self._sys_module('authz_user_module', 'mod_authz_user.so'))
541      fp.write(self._sys_module('authn_core_module', 'mod_authn_core.so'))
542    if self.httpd_ver >= 2.2:
543      fp.write(self._sys_module('auth_basic_module', 'mod_auth_basic.so'))
544      fp.write(self._sys_module('authn_file_module', 'mod_authn_file.so'))
545      fp.write(self._sys_module('authz_groupfile_module', 'mod_authz_groupfile.so'))
546      fp.write(self._sys_module('authz_host_module', 'mod_authz_host.so'))
547    else:
548      fp.write(self._sys_module('auth_module', 'mod_auth.so'))
549    fp.write(self._sys_module('alias_module', 'mod_alias.so'))
550    fp.write(self._sys_module('mime_module', 'mod_mime.so'))
551    fp.write(self._sys_module('log_config_module', 'mod_log_config.so'))
552
553    # Write LoadModule for Subversion modules
554    fp.write(self._svn_module('dav_svn_module', 'mod_dav_svn.so'))
555    fp.write(self._svn_module('authz_svn_module', 'mod_authz_svn.so'))
556
557    # And for mod_dontdothat
558    fp.write(self._svn_module('dontdothat_module', 'mod_dontdothat.so'))
559
560    # Don't handle .htaccess, symlinks, etc.
561    fp.write('<Directory />\n')
562    fp.write('AllowOverride None\n')
563    fp.write('Options None\n')
564    fp.write('</Directory>\n\n')
565
566    # Define two locations for repositories
567    fp.write(self._svn_repo('repositories'))
568    fp.write(self._svn_repo('local_tmp'))
569    fp.write(self._svn_authz_repo())
570
571    # And two redirects for the redirect tests
572    fp.write('RedirectMatch permanent ^/svn-test-work/repositories/'
573             'REDIRECT-PERM-(.*)$ /svn-test-work/repositories/$1\n')
574    fp.write('RedirectMatch           ^/svn-test-work/repositories/'
575             'REDIRECT-TEMP-(.*)$ /svn-test-work/repositories/$1\n')
576
577    fp.write('TypesConfig     ' + self._quote(self.httpd_mime_types) + '\n')
578    fp.write('HostNameLookups Off\n')
579
580    fp.close()
581
582  def __del__(self):
583    "Stop httpd when the object is deleted"
584    self.stop()
585
586  def _quote(self, arg):
587    if ' ' in arg:
588      return '"' + arg + '"'
589    else:
590      return arg
591
592  def _create_users_file(self):
593    "Create users file"
594    htpasswd = os.path.join(self.httpd_dir, 'bin', 'htpasswd.exe')
595    # Create the cheapest to compare password form for our testsuite
596    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bcp', self.httpd_users,
597                                    'jrandom', 'rayjandom'])
598    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp',  self.httpd_users,
599                                    'jconstant', 'rayjandom'])
600    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp',  self.httpd_users,
601                                    'JRANDOM', 'rayjandom'])
602    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp',  self.httpd_users,
603                                    'JCONSTANT', 'rayjandom'])
604
605  def _create_groups_file(self):
606    "Create groups for mod_authz_svn tests"
607    fp = open(self.httpd_groups, 'w')
608    fp.write('random: jrandom\n')
609    fp.write('constant: jconstant\n')
610    fp.close()
611
612  def _create_mime_types_file(self):
613    "Create empty mime.types file"
614    fp = open(self.httpd_mime_types, 'w')
615    fp.close()
616
617  def _create_dontdothat_file(self):
618    "Create empty mime.types file"
619    # If the tests have not previously been run or were cleaned
620    # up, then 'svn-test-work' does not exist yet.
621    parent_dir = os.path.dirname(self.dontdothat_file)
622    if not os.path.exists(parent_dir):
623      os.makedirs(parent_dir)
624
625    fp = open(self.dontdothat_file, 'w')
626    fp.write('[recursive-actions]\n')
627    fp.write('/ = deny\n')
628    fp.close()
629
630  def _sys_module(self, name, path):
631    full_path = os.path.join(self.httpd_dir, 'modules', path)
632    return 'LoadModule ' + name + " " + self._quote(full_path) + '\n'
633
634  def _svn_module(self, name, path):
635    full_path = os.path.join(self.abs_objdir, path)
636    return 'LoadModule ' + name + ' ' + self._quote(full_path) + '\n'
637
638  def _svn_repo(self, name):
639    path = os.path.join(self.abs_builddir,
640                        CMDLINE_TEST_SCRIPT_NATIVE_PATH,
641                        'svn-test-work', name)
642    location = '/svn-test-work/' + name
643    ddt_location = '/ddt-test-work/' + name
644    return \
645      '<Location ' + location + '>\n' \
646      '  DAV             svn\n' \
647      '  SVNParentPath   ' + self._quote(path) + '\n' \
648      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
649      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
650      '  SVNAllowBulkUpdates ' + self.bulkupdates_option + '\n' \
651      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
652      '  AuthType        Basic\n' \
653      '  AuthName        "Subversion Repository"\n' \
654      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
655      '  Require         valid-user\n' \
656      '</Location>\n' \
657      '<Location ' + ddt_location + '>\n' \
658      '  DAV             svn\n' \
659      '  SVNParentPath   ' + self._quote(path) + '\n' \
660      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
661      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
662      '  SVNAllowBulkUpdates ' + self.bulkupdates_option + '\n' \
663      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
664      '  AuthType        Basic\n' \
665      '  AuthName        "Subversion Repository"\n' \
666      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
667      '  Require         valid-user\n' \
668      '  DontDoThatConfigFile ' + self._quote(self.dontdothat_file) + '\n' \
669      '</Location>\n'
670
671  def _svn_authz_repo(self):
672    local_tmp = os.path.join(self.abs_builddir,
673                             CMDLINE_TEST_SCRIPT_NATIVE_PATH,
674                             'svn-test-work', 'local_tmp')
675    return \
676      '<Location /authz-test-work/anon>' + '\n' \
677      '  DAV               svn' + '\n' \
678      '  SVNParentPath     ' + local_tmp + '\n' \
679      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
680      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
681      '  SVNListParentPath On' + '\n' \
682      '  <IfModule mod_authz_core.c>' + '\n' \
683      '    Require all granted' + '\n' \
684      '  </IfModule>' + '\n' \
685      '  <IfModule !mod_authz_core.c>' + '\n' \
686      '    Allow from all' + '\n' \
687      '  </IfModule>' + '\n' \
688      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
689      '</Location>' + '\n' \
690      '<Location /authz-test-work/mixed>' + '\n' \
691      '  DAV               svn' + '\n' \
692      '  SVNParentPath     ' + local_tmp + '\n' \
693      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
694      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
695      '  SVNListParentPath On' + '\n' \
696      '  AuthType          Basic' + '\n' \
697      '  AuthName          "Subversion Repository"' + '\n' \
698      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
699      '  Require           valid-user' + '\n' \
700      '  Satisfy Any' + '\n' \
701      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
702      '</Location>' + '\n' \
703      '<Location /authz-test-work/mixed-noauthwhenanon>' + '\n' \
704      '  DAV               svn' + '\n' \
705      '  SVNParentPath     ' + local_tmp + '\n' \
706      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
707      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
708      '  SVNListParentPath On' + '\n' \
709      '  AuthType          Basic' + '\n' \
710      '  AuthName          "Subversion Repository"' + '\n' \
711      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
712      '  Require           valid-user' + '\n' \
713      '  AuthzSVNNoAuthWhenAnonymousAllowed On' + '\n' \
714      '  SVNPathAuthz On' + '\n' \
715      '</Location>' + '\n' \
716      '<Location /authz-test-work/authn>' + '\n' \
717      '  DAV               svn' + '\n' \
718      '  SVNParentPath     ' + local_tmp + '\n' \
719      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
720      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
721      '  SVNListParentPath On' + '\n' \
722      '  AuthType          Basic' + '\n' \
723      '  AuthName          "Subversion Repository"' + '\n' \
724      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
725      '  Require           valid-user' + '\n' \
726      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
727      '</Location>' + '\n' \
728      '<Location /authz-test-work/authn-anonoff>' + '\n' \
729      '  DAV               svn' + '\n' \
730      '  SVNParentPath     ' + local_tmp + '\n' \
731      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
732      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
733      '  SVNListParentPath On' + '\n' \
734      '  AuthType          Basic' + '\n' \
735      '  AuthName          "Subversion Repository"' + '\n' \
736      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
737      '  Require           valid-user' + '\n' \
738      '  AuthzSVNAnonymous Off' + '\n' \
739      '  SVNPathAuthz On' + '\n' \
740      '</Location>' + '\n' \
741      '<Location /authz-test-work/authn-lcuser>' + '\n' \
742      '  DAV               svn' + '\n' \
743      '  SVNParentPath     ' + local_tmp + '\n' \
744      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
745      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
746      '  SVNListParentPath On' + '\n' \
747      '  AuthType          Basic' + '\n' \
748      '  AuthName          "Subversion Repository"' + '\n' \
749      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
750      '  Require           valid-user' + '\n' \
751      '  AuthzForceUsernameCase Lower' + '\n' \
752      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
753      '</Location>' + '\n' \
754      '<Location /authz-test-work/authn-lcuser>' + '\n' \
755      '  DAV               svn' + '\n' \
756      '  SVNParentPath     ' + local_tmp + '\n' \
757      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
758      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
759      '  SVNListParentPath On' + '\n' \
760      '  AuthType          Basic' + '\n' \
761      '  AuthName          "Subversion Repository"' + '\n' \
762      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
763      '  Require           valid-user' + '\n' \
764      '  AuthzForceUsernameCase Lower' + '\n' \
765      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
766      '</Location>' + '\n' \
767      '<Location /authz-test-work/authn-group>' + '\n' \
768      '  DAV               svn' + '\n' \
769      '  SVNParentPath     ' + local_tmp + '\n' \
770      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
771      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
772      '  SVNListParentPath On' + '\n' \
773      '  AuthType          Basic' + '\n' \
774      '  AuthName          "Subversion Repository"' + '\n' \
775      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
776      '  AuthGroupFile    ' + self._quote(self.httpd_groups) + '\n' \
777      '  Require           group random' + '\n' \
778      '  AuthzSVNAuthoritative Off' + '\n' \
779      '  SVNPathAuthz On' + '\n' \
780      '</Location>' + '\n' \
781      '<IfModule mod_authz_core.c>' + '\n' \
782      '<Location /authz-test-work/sallrany>' + '\n' \
783      '  DAV               svn' + '\n' \
784      '  SVNParentPath     ' + local_tmp + '\n' \
785      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
786      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
787      '  SVNListParentPath On' + '\n' \
788      '  AuthType          Basic' + '\n' \
789      '  AuthName          "Subversion Repository"' + '\n' \
790      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
791      '  AuthzSendForbiddenOnFailure On' + '\n' \
792      '  Satisfy All' + '\n' \
793      '  <RequireAny>' + '\n' \
794      '    Require valid-user' + '\n' \
795      '    Require expr req(\'ALLOW\') == \'1\'' + '\n' \
796      '  </RequireAny>' + '\n' \
797      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
798      '</Location>' + '\n' \
799      '<Location /authz-test-work/sallrall>'+ '\n' \
800      '  DAV               svn' + '\n' \
801      '  SVNParentPath     ' + local_tmp + '\n' \
802      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
803      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
804      '  SVNListParentPath On' + '\n' \
805      '  AuthType          Basic' + '\n' \
806      '  AuthName          "Subversion Repository"' + '\n' \
807      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
808      '  AuthzSendForbiddenOnFailure On' + '\n' \
809      '  Satisfy All' + '\n' \
810      '  <RequireAll>' + '\n' \
811      '    Require valid-user' + '\n' \
812      '    Require expr req(\'ALLOW\') == \'1\'' + '\n' \
813      '  </RequireAll>' + '\n' \
814      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
815      '</Location>' + '\n' \
816      '</IfModule>' + '\n' \
817
818  def start(self):
819    if self.service:
820      self._start_service()
821    else:
822      self._start_daemon()
823
824  def stop(self):
825    if self.service:
826      self._stop_service()
827    else:
828      self._stop_daemon()
829
830  def _start_service(self):
831    "Install and start HTTPD service"
832    print('Installing service %s' % self.service_name)
833    os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'install'])
834    print('Starting service %s' % self.service_name)
835    os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'start'])
836
837  def _stop_service(self):
838    "Stop and uninstall HTTPD service"
839    os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'stop'])
840    os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'uninstall'])
841
842  def _start_daemon(self):
843    "Start HTTPD as daemon"
844    print('Starting httpd as daemon')
845    print(self.httpd_args)
846    try:
847      import win32process
848      import win32con
849      args = ' '.join([self._quote(x) for x in self.httpd_args])
850      self.proc_handle = (
851        win32process.CreateProcess(self._quote(self.path), args,
852                                   None, None, 0,
853                                   win32con.CREATE_NEW_CONSOLE,
854                                   None, None, win32process.STARTUPINFO()))[0]
855    except ImportError:
856      os.spawnv(os.P_NOWAIT, self.path, self.httpd_args)
857
858  def _stop_daemon(self):
859    "Stop the HTTPD daemon"
860    if self.proc_handle is not None:
861      try:
862        import win32process
863        print('Stopping %s' % self.name)
864        win32process.TerminateProcess(self.proc_handle, 0)
865        return
866      except ImportError:
867        pass
868    print('Httpd.stop_daemon not implemented')
869
870# Move the binaries to the test directory
871locate_libs()
872if create_dirs:
873  old_cwd = os.getcwd()
874  try:
875    os.chdir(abs_objdir)
876    baton = copied_execs
877    for dirpath, dirs, files in os.walk('subversion'):
878      copy_execs(baton, dirpath, files)
879    for dirpath, dirs, files in os.walk('tools/server-side'):
880      copy_execs(baton, dirpath, files)
881  except:
882    os.chdir(old_cwd)
883    raise
884  else:
885    os.chdir(old_cwd)
886
887# Create the base directory for Python tests
888create_target_dir(CMDLINE_TEST_SCRIPT_NATIVE_PATH)
889
890# Ensure the tests directory is correctly cased
891abs_builddir = fix_case(abs_builddir)
892
893daemon = None
894# Run the tests
895
896# No need to start any servers if we are only listing the tests.
897if not list_tests:
898  if run_svnserve:
899    daemon = Svnserve(svnserve_args, objdir, abs_objdir, abs_builddir)
900
901  if run_httpd:
902    daemon = Httpd(abs_httpd_dir, abs_objdir, abs_builddir, httpd_port,
903                   httpd_service, httpd_no_log,
904                   advertise_httpv2, http_short_circuit,
905                   http_bulk_updates)
906
907  # Start service daemon, if any
908  if daemon:
909    daemon.start()
910
911# Find the full path and filename of any test that is specified just by
912# its base name.
913if len(tests_to_run) != 0:
914  tests = []
915  for t in tests_to_run:
916    tns = None
917    if '#' in t:
918      t, tns = t.split('#')
919
920    test = [x for x in all_tests if x.split('/')[-1] == t]
921    if not test and not (t.endswith('-test.exe') or t.endswith('_tests.py')):
922      # The lengths of '-test.exe' and of '_tests.py' are both 9.
923      test = [x for x in all_tests if x.split('/')[-1][:-9] == t]
924
925    if not test:
926      print("Skipping test '%s', test not found." % t)
927    elif tns:
928      tests.append('%s#%s' % (test[0], tns))
929    else:
930      tests.extend(test)
931
932  tests_to_run = tests
933else:
934  tests_to_run = all_tests
935
936
937if list_tests:
938  print('Listing %s configuration on %s' % (objdir, repo_loc))
939else:
940  print('Testing %s configuration on %s' % (objdir, repo_loc))
941sys.path.insert(0, os.path.join(abs_srcdir, 'build'))
942
943if not test_javahl:
944  import run_tests
945  if log_to_stdout:
946    log_file = None
947    fail_log_file = None
948  else:
949    log_file = os.path.join(abs_builddir, log)
950    fail_log_file = os.path.join(abs_builddir, faillog)
951
952  if run_httpd:
953    httpd_version = "%.1f" % daemon.httpd_ver
954  else:
955    httpd_version = None
956  th = run_tests.TestHarness(abs_srcdir, abs_builddir,
957                             log_file,
958                             fail_log_file,
959                             base_url, fs_type, 'serf',
960                             server_minor_version, not quiet,
961                             cleanup, enable_sasl, parallel, config_file,
962                             fsfs_sharding, fsfs_packing,
963                             list_tests, svn_bin, mode_filter,
964                             milestone_filter,
965                             httpd_version=httpd_version,
966                             set_log_level=log_level, ssl_cert=ssl_cert)
967  old_cwd = os.getcwd()
968  try:
969    os.chdir(abs_builddir)
970    failed = th.run(tests_to_run)
971  except:
972    os.chdir(old_cwd)
973    raise
974  else:
975    os.chdir(old_cwd)
976else:
977  failed = False
978  args = (
979          'java.exe',
980          '-Dtest.rootdir=' + os.path.join(abs_builddir, 'javahl'),
981          '-Dtest.srcdir=' + os.path.join(abs_srcdir,
982                                          'subversion/bindings/javahl'),
983          '-Dtest.rooturl=',
984          '-Dtest.fstype=' + fs_type ,
985          '-Dtest.tests=',
986
987          '-Djava.library.path='
988                    + os.path.join(abs_objdir,
989                                   'subversion/bindings/javahl/native'),
990          '-classpath',
991          os.path.join(abs_srcdir, 'subversion/bindings/javahl/classes') +';' +
992            gen_obj.junit_path
993         )
994
995  sys.stderr.flush()
996  print('Running org.apache.subversion tests:')
997  sys.stdout.flush()
998
999  r = subprocess.call(args + tuple(['org.apache.subversion.javahl.RunTests']))
1000  sys.stdout.flush()
1001  sys.stderr.flush()
1002  if (r != 0):
1003    print('[Test runner reported failure]')
1004    failed = True
1005
1006  print('Running org.tigris.subversion tests:')
1007  sys.stdout.flush()
1008  r = subprocess.call(args + tuple(['org.tigris.subversion.javahl.RunTests']))
1009  sys.stdout.flush()
1010  sys.stderr.flush()
1011  if (r != 0):
1012    print('[Test runner reported failure]')
1013    failed = True
1014
1015# Stop service daemon, if any
1016if daemon:
1017  del daemon
1018
1019# Remove the execs again
1020for tgt in copied_execs:
1021  try:
1022    if os.path.isfile(tgt):
1023      if verbose:
1024        print("kill: %s" % tgt)
1025      os.unlink(tgt)
1026  except:
1027    traceback.print_exc(file=sys.stdout)
1028    pass
1029
1030
1031if failed:
1032  sys.exit(1)
1033