Extract a base class for RCSRevisionReader and CVSRevisionReader.
[cvs2svn.git] / svntest / main.py
blob1bb12d0b456082283735be1c6f56f831d2d818ea
2 # main.py: a shared, automated test suite for Subversion
4 # Subversion is a tool for revision control.
5 # See http://subversion.tigris.org for more information.
7 # ====================================================================
8 # Licensed to the Apache Software Foundation (ASF) under one
9 # or more contributor license agreements. See the NOTICE file
10 # distributed with this work for additional information
11 # regarding copyright ownership. The ASF licenses this file
12 # to you under the Apache License, Version 2.0 (the
13 # "License"); you may not use this file except in compliance
14 # with the License. You may obtain a copy of the License at
16 # http://www.apache.org/licenses/LICENSE-2.0
18 # Unless required by applicable law or agreed to in writing,
19 # software distributed under the License is distributed on an
20 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 # KIND, either express or implied. See the License for the
22 # specific language governing permissions and limitations
23 # under the License.
24 ######################################################################
26 import sys # for argv[]
27 import os
28 import shutil # for rmtree()
29 import re
30 import stat # for ST_MODE
31 import subprocess
32 import copy # for deepcopy()
33 import time # for time()
34 import traceback # for print_exc()
35 import threading
36 import optparse # for argument parsing
38 try:
39 # Python >=3.0
40 import queue
41 from urllib.parse import quote as urllib_parse_quote
42 from urllib.parse import unquote as urllib_parse_unquote
43 except ImportError:
44 # Python <3.0
45 import Queue as queue
46 from urllib import quote as urllib_parse_quote
47 from urllib import unquote as urllib_parse_unquote
49 import svntest
50 from svntest import Failure
51 from svntest import Skip
54 ######################################################################
56 # HOW TO USE THIS MODULE:
58 # Write a new python script that
60 # 1) imports this 'svntest' package
62 # 2) contains a number of related 'test' routines. (Each test
63 # routine should take no arguments, and return None on success
64 # or throw a Failure exception on failure. Each test should
65 # also contain a short docstring.)
67 # 3) places all the tests into a list that begins with None.
69 # 4) calls svntest.main.client_test() on the list.
71 # Also, your tests will probably want to use some of the common
72 # routines in the 'Utilities' section below.
74 #####################################################################
75 # Global stuff
77 class SVNProcessTerminatedBySignal(Failure):
78 "Exception raised if a spawned process segfaulted, aborted, etc."
79 pass
81 class SVNLineUnequal(Failure):
82 "Exception raised if two lines are unequal"
83 pass
85 class SVNUnmatchedError(Failure):
86 "Exception raised if an expected error is not found"
87 pass
89 class SVNCommitFailure(Failure):
90 "Exception raised if a commit failed"
91 pass
93 class SVNRepositoryCopyFailure(Failure):
94 "Exception raised if unable to copy a repository"
95 pass
97 class SVNRepositoryCreateFailure(Failure):
98 "Exception raised if unable to create a repository"
99 pass
101 # Windows specifics
102 if sys.platform == 'win32':
103 windows = True
104 file_scheme_prefix = 'file:///'
105 _exe = '.exe'
106 _bat = '.bat'
107 os.environ['SVN_DBG_STACKTRACES_TO_STDERR'] = 'y'
108 else:
109 windows = False
110 file_scheme_prefix = 'file://'
111 _exe = ''
112 _bat = ''
114 # The location of our mock svneditor script.
115 if windows:
116 svneditor_script = os.path.join(sys.path[0], 'svneditor.bat')
117 else:
118 svneditor_script = os.path.join(sys.path[0], 'svneditor.py')
120 # Username and password used by the working copies
121 wc_author = 'jrandom'
122 wc_passwd = 'rayjandom'
124 # Username and password used by the working copies for "second user"
125 # scenarios
126 wc_author2 = 'jconstant' # use the same password as wc_author
128 # Set C locale for command line programs
129 os.environ['LC_ALL'] = 'C'
131 # This function mimics the Python 2.3 urllib function of the same name.
132 def pathname2url(path):
133 """Convert the pathname PATH from the local syntax for a path to the form
134 used in the path component of a URL. This does not produce a complete URL.
135 The return value will already be quoted using the quote() function."""
137 # Don't leave ':' in file://C%3A/ escaped as our canonicalization
138 # rules will replace this with a ':' on input.
139 return urllib_parse_quote(path.replace('\\', '/')).replace('%3A', ':')
141 # This function mimics the Python 2.3 urllib function of the same name.
142 def url2pathname(path):
143 """Convert the path component PATH from an encoded URL to the local syntax
144 for a path. This does not accept a complete URL. This function uses
145 unquote() to decode PATH."""
146 return os.path.normpath(urllib_parse_unquote(path))
148 ######################################################################
149 # The locations of the svn, svnadmin and svnlook binaries, relative to
150 # the only scripts that import this file right now (they live in ../).
151 # Use --bin to override these defaults.
152 svn_binary = os.path.abspath('../../svn/svn' + _exe)
153 svnadmin_binary = os.path.abspath('../../svnadmin/svnadmin' + _exe)
154 svnlook_binary = os.path.abspath('../../svnlook/svnlook' + _exe)
155 svnsync_binary = os.path.abspath('../../svnsync/svnsync' + _exe)
156 svnversion_binary = os.path.abspath('../../svnversion/svnversion' + _exe)
157 svndumpfilter_binary = os.path.abspath('../../svndumpfilter/svndumpfilter' + \
158 _exe)
159 entriesdump_binary = os.path.abspath('entries-dump' + _exe)
161 # Location to the pristine repository, will be calculated from test_area_url
162 # when we know what the user specified for --url.
163 pristine_url = None
165 # Global variable to track all of our options
166 options = None
168 # End of command-line-set global variables.
169 ######################################################################
171 # All temporary repositories and working copies are created underneath
172 # this dir, so there's one point at which to mount, e.g., a ramdisk.
173 work_dir = "svn-test-work"
175 # Constant for the merge info property.
176 SVN_PROP_MERGEINFO = "svn:mergeinfo"
178 # Where we want all the repositories and working copies to live.
179 # Each test will have its own!
180 general_repo_dir = os.path.join(work_dir, "repositories")
181 general_wc_dir = os.path.join(work_dir, "working_copies")
183 # temp directory in which we will create our 'pristine' local
184 # repository and other scratch data. This should be removed when we
185 # quit and when we startup.
186 temp_dir = os.path.join(work_dir, 'local_tmp')
188 # (derivatives of the tmp dir.)
189 pristine_dir = os.path.join(temp_dir, "repos")
190 greek_dump_dir = os.path.join(temp_dir, "greekfiles")
191 default_config_dir = os.path.abspath(os.path.join(temp_dir, "config"))
194 # Our pristine greek-tree state.
196 # If a test wishes to create an "expected" working-copy tree, it should
197 # call main.greek_state.copy(). That method will return a copy of this
198 # State object which can then be edited.
200 _item = svntest.wc.StateItem
201 greek_state = svntest.wc.State('', {
202 'iota' : _item("This is the file 'iota'.\n"),
203 'A' : _item(),
204 'A/mu' : _item("This is the file 'mu'.\n"),
205 'A/B' : _item(),
206 'A/B/lambda' : _item("This is the file 'lambda'.\n"),
207 'A/B/E' : _item(),
208 'A/B/E/alpha' : _item("This is the file 'alpha'.\n"),
209 'A/B/E/beta' : _item("This is the file 'beta'.\n"),
210 'A/B/F' : _item(),
211 'A/C' : _item(),
212 'A/D' : _item(),
213 'A/D/gamma' : _item("This is the file 'gamma'.\n"),
214 'A/D/G' : _item(),
215 'A/D/G/pi' : _item("This is the file 'pi'.\n"),
216 'A/D/G/rho' : _item("This is the file 'rho'.\n"),
217 'A/D/G/tau' : _item("This is the file 'tau'.\n"),
218 'A/D/H' : _item(),
219 'A/D/H/chi' : _item("This is the file 'chi'.\n"),
220 'A/D/H/psi' : _item("This is the file 'psi'.\n"),
221 'A/D/H/omega' : _item("This is the file 'omega'.\n"),
225 ######################################################################
226 # Utilities shared by the tests
227 def wrap_ex(func):
228 "Wrap a function, catch, print and ignore exceptions"
229 def w(*args, **kwds):
230 try:
231 return func(*args, **kwds)
232 except Failure, ex:
233 if ex.__class__ != Failure or ex.args:
234 ex_args = str(ex)
235 if ex_args:
236 print('EXCEPTION: %s: %s' % (ex.__class__.__name__, ex_args))
237 else:
238 print('EXCEPTION: %s' % ex.__class__.__name__)
239 return w
241 def setup_development_mode():
242 "Wraps functions in module actions"
243 l = [ 'run_and_verify_svn',
244 'run_and_verify_svnversion',
245 'run_and_verify_load',
246 'run_and_verify_dump',
247 'run_and_verify_checkout',
248 'run_and_verify_export',
249 'run_and_verify_update',
250 'run_and_verify_merge',
251 'run_and_verify_switch',
252 'run_and_verify_commit',
253 'run_and_verify_unquiet_status',
254 'run_and_verify_status',
255 'run_and_verify_diff_summarize',
256 'run_and_verify_diff_summarize_xml',
257 'run_and_validate_lock']
259 for func in l:
260 setattr(svntest.actions, func, wrap_ex(getattr(svntest.actions, func)))
262 def get_admin_name():
263 "Return name of SVN administrative subdirectory."
265 if (windows or sys.platform == 'cygwin') \
266 and 'SVN_ASP_DOT_NET_HACK' in os.environ:
267 return '_svn'
268 else:
269 return '.svn'
271 def get_start_commit_hook_path(repo_dir):
272 "Return the path of the start-commit-hook conf file in REPO_DIR."
274 return os.path.join(repo_dir, "hooks", "start-commit")
277 def get_pre_commit_hook_path(repo_dir):
278 "Return the path of the pre-commit-hook conf file in REPO_DIR."
280 return os.path.join(repo_dir, "hooks", "pre-commit")
283 def get_post_commit_hook_path(repo_dir):
284 "Return the path of the post-commit-hook conf file in REPO_DIR."
286 return os.path.join(repo_dir, "hooks", "post-commit")
288 def get_pre_revprop_change_hook_path(repo_dir):
289 "Return the path of the pre-revprop-change hook script in REPO_DIR."
291 return os.path.join(repo_dir, "hooks", "pre-revprop-change")
293 def get_svnserve_conf_file_path(repo_dir):
294 "Return the path of the svnserve.conf file in REPO_DIR."
296 return os.path.join(repo_dir, "conf", "svnserve.conf")
298 def get_fsfs_conf_file_path(repo_dir):
299 "Return the path of the fsfs.conf file in REPO_DIR."
301 return os.path.join(repo_dir, "db", "fsfs.conf")
303 def get_fsfs_format_file_path(repo_dir):
304 "Return the path of the format file in REPO_DIR."
306 return os.path.join(repo_dir, "db", "format")
308 # Run any binary, logging the command line and return code
309 def run_command(command, error_expected, binary_mode=0, *varargs):
310 """Run COMMAND with VARARGS. Return exit code as int; stdout, stderr
311 as lists of lines (including line terminators). See run_command_stdin()
312 for details. If ERROR_EXPECTED is None, any stderr also will be printed."""
314 return run_command_stdin(command, error_expected, 0, binary_mode,
315 None, *varargs)
317 # A regular expression that matches arguments that are trivially safe
318 # to pass on a command line without quoting on any supported operating
319 # system:
320 _safe_arg_re = re.compile(r'^[A-Za-z\d\.\_\/\-\:\@]+$')
322 def _quote_arg(arg):
323 """Quote ARG for a command line.
325 Simply surround every argument in double-quotes unless it contains
326 only universally harmless characters.
328 WARNING: This function cannot handle arbitrary command-line
329 arguments. It can easily be confused by shell metacharacters. A
330 perfect job would be difficult and OS-dependent (see, for example,
331 http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp).
332 In other words, this function is just good enough for what we need
333 here."""
335 arg = str(arg)
336 if _safe_arg_re.match(arg):
337 return arg
338 else:
339 if os.name != 'nt':
340 arg = arg.replace('$', '\$')
341 return '"%s"' % (arg,)
343 def open_pipe(command, bufsize=0, stdin=None, stdout=None, stderr=None):
344 """Opens a subprocess.Popen pipe to COMMAND using STDIN,
345 STDOUT, and STDERR. BUFSIZE is passed to subprocess.Popen's
346 argument of the same name.
348 Returns (infile, outfile, errfile, waiter); waiter
349 should be passed to wait_on_pipe."""
350 command = [str(x) for x in command]
352 # On Windows subprocess.Popen() won't accept a Python script as
353 # a valid program to execute, rather it wants the Python executable.
354 if (sys.platform == 'win32') and (command[0].endswith('.py')):
355 command.insert(0, sys.executable)
357 # Quote only the arguments on Windows. Later versions of subprocess,
358 # 2.5.2+ confirmed, don't require this quoting, but versions < 2.4.3 do.
359 if sys.platform == 'win32':
360 args = command[1:]
361 args = ' '.join([_quote_arg(x) for x in args])
362 command = command[0] + ' ' + args
363 command_string = command
364 else:
365 command_string = ' '.join(command)
367 if not stdin:
368 stdin = subprocess.PIPE
369 if not stdout:
370 stdout = subprocess.PIPE
371 if not stderr:
372 stderr = subprocess.PIPE
374 p = subprocess.Popen(command,
375 bufsize,
376 stdin=stdin,
377 stdout=stdout,
378 stderr=stderr,
379 close_fds=not windows)
380 return p.stdin, p.stdout, p.stderr, (p, command_string)
382 def wait_on_pipe(waiter, binary_mode, stdin=None):
383 """WAITER is (KID, COMMAND_STRING). Wait for KID (opened with open_pipe)
384 to finish, dying if it does. If KID fails, create an error message
385 containing any stdout and stderr from the kid. Show COMMAND_STRING in
386 diagnostic messages. Normalize Windows line endings of stdout and stderr
387 if not BINARY_MODE. Return KID's exit code as int; stdout, stderr as
388 lists of lines (including line terminators)."""
389 if waiter is None:
390 return
392 kid, command_string = waiter
393 stdout, stderr = kid.communicate(stdin)
394 exit_code = kid.returncode
396 # Normalize Windows line endings if in text mode.
397 if windows and not binary_mode:
398 stdout = stdout.replace('\r\n', '\n')
399 stderr = stderr.replace('\r\n', '\n')
401 # Convert output strings to lists.
402 stdout_lines = stdout.splitlines(True)
403 stderr_lines = stderr.splitlines(True)
405 if exit_code < 0:
406 if not windows:
407 exit_signal = os.WTERMSIG(-exit_code)
408 else:
409 exit_signal = exit_code
411 if stdout_lines is not None:
412 sys.stdout.write("".join(stdout_lines))
413 sys.stdout.flush()
414 if stderr_lines is not None:
415 sys.stderr.write("".join(stderr_lines))
416 sys.stderr.flush()
417 if options.verbose:
418 # show the whole path to make it easier to start a debugger
419 sys.stderr.write("CMD: %s terminated by signal %d\n"
420 % (command_string, exit_signal))
421 sys.stderr.flush()
422 raise SVNProcessTerminatedBySignal
423 else:
424 if exit_code and options.verbose:
425 sys.stderr.write("CMD: %s exited with %d\n"
426 % (command_string, exit_code))
427 sys.stderr.flush()
428 return stdout_lines, stderr_lines, exit_code
430 def spawn_process(command, bufsize=0, binary_mode=0, stdin_lines=None,
431 *varargs):
432 """Run any binary, supplying input text, logging the command line.
433 BUFSIZE dictates the pipe buffer size used in communication with the
434 subprocess: 0 means unbuffered, 1 means line buffered, any other
435 positive value means use a buffer of (approximately) that size.
436 A negative bufsize means to use the system default, which usually
437 means fully buffered. The default value for bufsize is 0 (unbuffered).
438 Normalize Windows line endings of stdout and stderr if not BINARY_MODE.
439 Return exit code as int; stdout, stderr as lists of lines (including
440 line terminators)."""
441 if stdin_lines and not isinstance(stdin_lines, list):
442 raise TypeError("stdin_lines should have list type")
444 # Log the command line
445 if options.verbose and not command.endswith('.py'):
446 sys.stdout.write('CMD: %s %s\n' % (os.path.basename(command),
447 ' '.join([_quote_arg(x) for x in varargs])))
448 sys.stdout.flush()
450 infile, outfile, errfile, kid = open_pipe([command] + list(varargs), bufsize)
452 if stdin_lines:
453 for x in stdin_lines:
454 infile.write(x)
456 stdout_lines, stderr_lines, exit_code = wait_on_pipe(kid, binary_mode)
457 infile.close()
459 outfile.close()
460 errfile.close()
462 return exit_code, stdout_lines, stderr_lines
464 def run_command_stdin(command, error_expected, bufsize=0, binary_mode=0,
465 stdin_lines=None, *varargs):
466 """Run COMMAND with VARARGS; input STDIN_LINES (a list of strings
467 which should include newline characters) to program via stdin - this
468 should not be very large, as if the program outputs more than the OS
469 is willing to buffer, this will deadlock, with both Python and
470 COMMAND waiting to write to each other for ever. For tests where this
471 is a problem, setting BUFSIZE to a sufficiently large value will prevent
472 the deadlock, see spawn_process().
473 Normalize Windows line endings of stdout and stderr if not BINARY_MODE.
474 Return exit code as int; stdout, stderr as lists of lines (including
475 line terminators).
476 If ERROR_EXPECTED is None, any stderr also will be printed."""
478 if options.verbose:
479 start = time.time()
481 exit_code, stdout_lines, stderr_lines = spawn_process(command,
482 bufsize,
483 binary_mode,
484 stdin_lines,
485 *varargs)
487 if options.verbose:
488 stop = time.time()
489 print('<TIME = %.6f>' % (stop - start))
490 for x in stdout_lines:
491 sys.stdout.write(x)
492 for x in stderr_lines:
493 sys.stdout.write(x)
495 if (not error_expected) and (stderr_lines):
496 if not options.verbose:
497 for x in stderr_lines:
498 sys.stdout.write(x)
499 raise Failure
501 return exit_code, \
502 [line for line in stdout_lines if not line.startswith("DBG:")], \
503 stderr_lines
505 def create_config_dir(cfgdir, config_contents=None, server_contents=None):
506 "Create config directories and files"
508 # config file names
509 cfgfile_cfg = os.path.join(cfgdir, 'config')
510 cfgfile_srv = os.path.join(cfgdir, 'servers')
512 # create the directory
513 if not os.path.isdir(cfgdir):
514 os.makedirs(cfgdir)
516 # define default config file contents if none provided
517 if config_contents is None:
518 config_contents = """
520 [auth]
521 password-stores =
523 [miscellany]
524 interactive-conflicts = false
527 # define default server file contents if none provided
528 if server_contents is None:
529 http_library_str = ""
530 if options.http_library:
531 http_library_str = "http-library=%s" % (options.http_library)
532 server_contents = """
534 [global]
536 store-plaintext-passwords=yes
537 store-passwords=yes
538 """ % (http_library_str)
540 file_write(cfgfile_cfg, config_contents)
541 file_write(cfgfile_srv, server_contents)
543 def _with_config_dir(args):
544 if '--config-dir' in args:
545 return args
546 else:
547 return args + ('--config-dir', default_config_dir)
549 def _with_auth(args):
550 assert '--password' not in args
551 args = args + ('--password', wc_passwd,
552 '--no-auth-cache' )
553 if '--username' in args:
554 return args
555 else:
556 return args + ('--username', wc_author )
558 # For running subversion and returning the output
559 def run_svn(error_expected, *varargs):
560 """Run svn with VARARGS; return exit code as int; stdout, stderr as
561 lists of lines (including line terminators).
562 If ERROR_EXPECTED is None, any stderr also will be printed. If
563 you're just checking that something does/doesn't come out of
564 stdout/stderr, you might want to use actions.run_and_verify_svn()."""
565 return run_command(svn_binary, error_expected, 0,
566 *(_with_auth(_with_config_dir(varargs))))
568 # For running svnadmin. Ignores the output.
569 def run_svnadmin(*varargs):
570 """Run svnadmin with VARARGS, returns exit code as int; stdout, stderr as
571 list of lines (including line terminators)."""
572 return run_command(svnadmin_binary, 1, 0, *varargs)
574 # For running svnlook. Ignores the output.
575 def run_svnlook(*varargs):
576 """Run svnlook with VARARGS, returns exit code as int; stdout, stderr as
577 list of lines (including line terminators)."""
578 return run_command(svnlook_binary, 1, 0, *varargs)
580 def run_svnsync(*varargs):
581 """Run svnsync with VARARGS, returns exit code as int; stdout, stderr as
582 list of lines (including line terminators)."""
583 return run_command(svnsync_binary, 1, 0, *(_with_config_dir(varargs)))
585 def run_svnversion(*varargs):
586 """Run svnversion with VARARGS, returns exit code as int; stdout, stderr
587 as list of lines (including line terminators)."""
588 return run_command(svnversion_binary, 1, 0, *varargs)
590 def run_entriesdump(path):
591 """Run the entries-dump helper, returning a dict of Entry objects."""
592 # use spawn_process rather than run_command to avoid copying all the data
593 # to stdout in verbose mode.
594 exit_code, stdout_lines, stderr_lines = spawn_process(entriesdump_binary,
595 0, 0, None, path)
596 if options.verbose:
597 ### finish the CMD output
598 print
599 if exit_code or stderr_lines:
600 ### report on this? or continue to just skip it?
601 return None
603 class Entry(object):
604 pass
605 entries = { }
606 exec(''.join([line for line in stdout_lines if not line.startswith("DBG:")]))
607 return entries
610 # Chmod recursively on a whole subtree
611 def chmod_tree(path, mode, mask):
612 for dirpath, dirs, files in os.walk(path):
613 for name in dirs + files:
614 fullname = os.path.join(dirpath, name)
615 if not os.path.islink(fullname):
616 new_mode = (os.stat(fullname)[stat.ST_MODE] & ~mask) | mode
617 os.chmod(fullname, new_mode)
619 # For clearing away working copies
620 def safe_rmtree(dirname, retry=0):
621 "Remove the tree at DIRNAME, making it writable first"
622 def rmtree(dirname):
623 chmod_tree(dirname, 0666, 0666)
624 shutil.rmtree(dirname)
626 if not os.path.exists(dirname):
627 return
629 if retry:
630 for delay in (0.5, 1, 2, 4):
631 try:
632 rmtree(dirname)
633 break
634 except:
635 time.sleep(delay)
636 else:
637 rmtree(dirname)
638 else:
639 rmtree(dirname)
641 # For making local mods to files
642 def file_append(path, new_text):
643 "Append NEW_TEXT to file at PATH"
644 open(path, 'a').write(new_text)
646 # Append in binary mode
647 def file_append_binary(path, new_text):
648 "Append NEW_TEXT to file at PATH in binary mode"
649 open(path, 'ab').write(new_text)
651 # For creating new files, and making local mods to existing files.
652 def file_write(path, contents, mode='w'):
653 """Write the CONTENTS to the file at PATH, opening file using MODE,
654 which is (w)rite by default."""
655 open(path, mode).write(contents)
657 # For replacing parts of contents in an existing file, with new content.
658 def file_substitute(path, contents, new_contents):
659 """Replace the CONTENTS in the file at PATH using the NEW_CONTENTS"""
660 fcontent = open(path, 'r').read().replace(contents, new_contents)
661 open(path, 'w').write(fcontent)
663 # For creating blank new repositories
664 def create_repos(path):
665 """Create a brand-new SVN repository at PATH. If PATH does not yet
666 exist, create it."""
668 if not os.path.exists(path):
669 os.makedirs(path) # this creates all the intermediate dirs, if neccessary
671 opts = ("--bdb-txn-nosync",)
672 if options.server_minor_version < 5:
673 opts += ("--pre-1.5-compatible",)
674 elif options.server_minor_version < 6:
675 opts += ("--pre-1.6-compatible",)
676 elif options.server_minor_version < 7:
677 opts += ("--pre-1.7-compatible",)
678 if options.fs_type is not None:
679 opts += ("--fs-type=" + options.fs_type,)
680 exit_code, stdout, stderr = run_command(svnadmin_binary, 1, 0, "create",
681 path, *opts)
683 # Skip tests if we can't create the repository.
684 if stderr:
685 for line in stderr:
686 if line.find('Unknown FS type') != -1:
687 raise Skip
688 # If the FS type is known, assume the repos couldn't be created
689 # (e.g. due to a missing 'svnadmin' binary).
690 raise SVNRepositoryCreateFailure("".join(stderr).rstrip())
692 # Allow unauthenticated users to write to the repos, for ra_svn testing.
693 file_write(get_svnserve_conf_file_path(path),
694 "[general]\nauth-access = write\n");
695 if options.enable_sasl:
696 file_append(get_svnserve_conf_file_path(path),
697 "realm = svntest\n[sasl]\nuse-sasl = true\n")
698 else:
699 file_append(get_svnserve_conf_file_path(path), "password-db = passwd\n")
700 file_append(os.path.join(path, "conf", "passwd"),
701 "[users]\njrandom = rayjandom\njconstant = rayjandom\n");
703 if options.fs_type is None or options.fs_type == 'fsfs':
704 # fsfs.conf file
705 if options.config_file is not None:
706 shutil.copy(options.config_file, get_fsfs_conf_file_path(path))
708 # format file
709 if options.fsfs_sharding is not None:
710 def transform_line(line):
711 if line.startswith('layout '):
712 if options.fsfs_sharding > 0:
713 line = 'layout sharded %d' % options.fsfs_sharding
714 else:
715 line = 'layout linear'
716 return line
718 # read it
719 format_file_path = get_fsfs_format_file_path(path)
720 contents = open(format_file_path, 'rb').read()
722 # tweak it
723 new_contents = "".join([transform_line(line) + "\n"
724 for line in contents.split("\n")])
725 if new_contents[-1] == "\n":
726 # we don't currently allow empty lines (\n\n) in the format file.
727 new_contents = new_contents[:-1]
729 # replace it
730 os.chmod(format_file_path, 0666)
731 file_write(format_file_path, new_contents, 'wb')
733 # post-commit
734 # Note that some tests (currently only commit_tests) create their own
735 # post-commit hooks, which would override this one. :-(
736 if options.fsfs_packing:
737 # some tests chdir.
738 abs_path = os.path.abspath(path)
739 create_python_hook_script(get_post_commit_hook_path(abs_path),
740 "import subprocess\n"
741 "import sys\n"
742 "command = %s\n"
743 "sys.exit(subprocess.Popen(command).wait())\n"
744 % repr([svnadmin_binary, 'pack', abs_path]))
746 # make the repos world-writeable, for mod_dav_svn's sake.
747 chmod_tree(path, 0666, 0666)
749 # For copying a repository
750 def copy_repos(src_path, dst_path, head_revision, ignore_uuid = 1):
751 "Copy the repository SRC_PATH, with head revision HEAD_REVISION, to DST_PATH"
753 # Save any previous value of SVN_DBG_QUIET
754 saved_quiet = os.environ.get('SVN_DBG_QUIET')
755 os.environ['SVN_DBG_QUIET'] = 'y'
757 # Do an svnadmin dump|svnadmin load cycle. Print a fake pipe command so that
758 # the displayed CMDs can be run by hand
759 create_repos(dst_path)
760 dump_args = ['dump', src_path]
761 load_args = ['load', dst_path]
763 if ignore_uuid:
764 load_args = load_args + ['--ignore-uuid']
765 if options.verbose:
766 sys.stdout.write('CMD: %s %s | %s %s\n' %
767 (os.path.basename(svnadmin_binary), ' '.join(dump_args),
768 os.path.basename(svnadmin_binary), ' '.join(load_args)))
769 sys.stdout.flush()
770 start = time.time()
772 dump_in, dump_out, dump_err, dump_kid = open_pipe(
773 [svnadmin_binary] + dump_args)
774 load_in, load_out, load_err, load_kid = open_pipe(
775 [svnadmin_binary] + load_args,
776 stdin=dump_out) # Attached to dump_kid
778 stop = time.time()
779 if options.verbose:
780 print('<TIME = %.6f>' % (stop - start))
782 load_stdout, load_stderr, load_exit_code = wait_on_pipe(load_kid, True)
783 dump_stdout, dump_stderr, dump_exit_code = wait_on_pipe(dump_kid, True)
785 dump_in.close()
786 dump_out.close()
787 dump_err.close()
788 #load_in is dump_out so it's already closed.
789 load_out.close()
790 load_err.close()
792 if saved_quiet is None:
793 del os.environ['SVN_DBG_QUIET']
794 else:
795 os.environ['SVN_DBG_QUIET'] = saved_quiet
797 dump_re = re.compile(r'^\* Dumped revision (\d+)\.\r?$')
798 expect_revision = 0
799 for dump_line in dump_stderr:
800 match = dump_re.match(dump_line)
801 if not match or match.group(1) != str(expect_revision):
802 print('ERROR: dump failed: %s' % dump_line.strip())
803 raise SVNRepositoryCopyFailure
804 expect_revision += 1
805 if expect_revision != head_revision + 1:
806 print('ERROR: dump failed; did not see revision %s' % head_revision)
807 raise SVNRepositoryCopyFailure
809 load_re = re.compile(r'^------- Committed revision (\d+) >>>\r?$')
810 expect_revision = 1
811 for load_line in load_stdout:
812 match = load_re.match(load_line)
813 if match:
814 if match.group(1) != str(expect_revision):
815 print('ERROR: load failed: %s' % load_line.strip())
816 raise SVNRepositoryCopyFailure
817 expect_revision += 1
818 if expect_revision != head_revision + 1:
819 print('ERROR: load failed; did not see revision %s' % head_revision)
820 raise SVNRepositoryCopyFailure
823 def canonicalize_url(input):
824 "Canonicalize the url, if the scheme is unknown, returns intact input"
826 m = re.match(r"^((file://)|((svn|svn\+ssh|http|https)(://)))", input)
827 if m:
828 scheme = m.group(1)
829 return scheme + re.sub(r'//*', '/', input[len(scheme):])
830 else:
831 return input
834 def create_python_hook_script(hook_path, hook_script_code):
835 """Create a Python hook script at HOOK_PATH with the specified
836 HOOK_SCRIPT_CODE."""
838 if windows:
839 # Use an absolute path since the working directory is not guaranteed
840 hook_path = os.path.abspath(hook_path)
841 # Fill the python file.
842 file_write("%s.py" % hook_path, hook_script_code)
843 # Fill the batch wrapper file.
844 file_append("%s.bat" % hook_path,
845 "@\"%s\" %s.py %%*\n" % (sys.executable, hook_path))
846 else:
847 # For all other platforms
848 file_write(hook_path, "#!%s\n%s" % (sys.executable, hook_script_code))
849 os.chmod(hook_path, 0755)
851 def write_restrictive_svnserve_conf(repo_dir, anon_access="none"):
852 "Create a restrictive authz file ( no anynomous access )."
854 fp = open(get_svnserve_conf_file_path(repo_dir), 'w')
855 fp.write("[general]\nanon-access = %s\nauth-access = write\n"
856 "authz-db = authz\n" % anon_access)
857 if options.enable_sasl:
858 fp.write("realm = svntest\n[sasl]\nuse-sasl = true\n");
859 else:
860 fp.write("password-db = passwd\n")
861 fp.close()
863 # Warning: because mod_dav_svn uses one shared authz file for all
864 # repositories, you *cannot* use write_authz_file in any test that
865 # might be run in parallel.
867 # write_authz_file can *only* be used in test suites which disable
868 # parallel execution at the bottom like so
869 # if __name__ == '__main__':
870 # svntest.main.run_tests(test_list, serial_only = True)
871 def write_authz_file(sbox, rules, sections=None):
872 """Write an authz file to SBOX, appropriate for the RA method used,
873 with authorizations rules RULES mapping paths to strings containing
874 the rules. You can add sections SECTIONS (ex. groups, aliases...) with
875 an appropriate list of mappings.
877 fp = open(sbox.authz_file, 'w')
879 # When the sandbox repository is read only it's name will be different from
880 # the repository name.
881 repo_name = sbox.repo_dir
882 while repo_name[-1] == '/':
883 repo_name = repo_name[:-1]
884 repo_name = os.path.basename(repo_name)
886 if sbox.repo_url.startswith("http"):
887 prefix = repo_name + ":"
888 else:
889 prefix = ""
890 if sections:
891 for p, r in sections.items():
892 fp.write("[%s]\n%s\n" % (p, r))
894 for p, r in rules.items():
895 fp.write("[%s%s]\n%s\n" % (prefix, p, r))
896 fp.close()
898 def use_editor(func):
899 os.environ['SVN_EDITOR'] = svneditor_script
900 os.environ['SVN_MERGE'] = svneditor_script
901 os.environ['SVNTEST_EDITOR_FUNC'] = func
902 os.environ['SVN_TEST_PYTHON'] = sys.executable
904 def mergeinfo_notify_line(revstart, revend):
905 """Return an expected output line that describes the beginning of a
906 mergeinfo recording notification on revisions REVSTART through REVEND."""
907 if (revend is None):
908 if (revstart < 0):
909 revstart = abs(revstart)
910 return "--- Recording mergeinfo for reverse merge of r%ld .*:\n" \
911 % (revstart)
912 else:
913 return "--- Recording mergeinfo for merge of r%ld .*:\n" % (revstart)
914 elif (revstart < revend):
915 return "--- Recording mergeinfo for merge of r%ld through r%ld .*:\n" \
916 % (revstart, revend)
917 else:
918 return "--- Recording mergeinfo for reverse merge of r%ld through " \
919 "r%ld .*:\n" % (revstart, revend)
921 def merge_notify_line(revstart=None, revend=None, same_URL=True,
922 foreign=False):
923 """Return an expected output line that describes the beginning of a
924 merge operation on revisions REVSTART through REVEND. Omit both
925 REVSTART and REVEND for the case where the left and right sides of
926 the merge are from different URLs."""
927 from_foreign_phrase = foreign and "\(from foreign repository\) " or ""
928 if not same_URL:
929 return "--- Merging differences between %srepository URLs into '.+':\n" \
930 % (foreign and "foreign " or "")
931 if revend is None:
932 if revstart is None:
933 # The left and right sides of the merge are from different URLs.
934 return "--- Merging differences between %srepository URLs into '.+':\n" \
935 % (foreign and "foreign " or "")
936 elif revstart < 0:
937 return "--- Reverse-merging %sr%ld into '.+':\n" \
938 % (from_foreign_phrase, abs(revstart))
939 else:
940 return "--- Merging %sr%ld into '.+':\n" \
941 % (from_foreign_phrase, revstart)
942 else:
943 if revstart > revend:
944 return "--- Reverse-merging %sr%ld through r%ld into '.+':\n" \
945 % (from_foreign_phrase, revstart, revend)
946 else:
947 return "--- Merging %sr%ld through r%ld into '.+':\n" \
948 % (from_foreign_phrase, revstart, revend)
951 def make_log_msg():
952 "Conjure up a log message based on the calling test."
954 for idx in range(1, 100):
955 frame = sys._getframe(idx)
957 # If this frame isn't from a function in *_tests.py, then skip it.
958 filename = frame.f_code.co_filename
959 if not filename.endswith('_tests.py'):
960 continue
962 # There should be a test_list in this module.
963 test_list = frame.f_globals.get('test_list')
964 if test_list is None:
965 continue
967 # If the function is not in the test_list, then skip it.
968 func_name = frame.f_code.co_name
969 func_ob = frame.f_globals.get(func_name)
970 if func_ob not in test_list:
971 continue
973 # Make the log message look like a line from a traceback.
974 # Well...close. We use single quotes to avoid interfering with the
975 # double-quote quoting performed on Windows
976 return "File '%s', line %d, in %s" % (filename, frame.f_lineno, func_name)
979 ######################################################################
980 # Functions which check the test configuration
981 # (useful for conditional XFails)
983 def is_ra_type_dav():
984 return options.test_area_url.startswith('http')
986 def is_ra_type_dav_neon():
987 """Return True iff running tests over RA-Neon.
988 CAUTION: Result is only valid if svn was built to support both."""
989 return options.test_area_url.startswith('http') and \
990 (options.http_library == "neon")
992 def is_ra_type_dav_serf():
993 """Return True iff running tests over RA-Serf.
994 CAUTION: Result is only valid if svn was built to support both."""
995 return options.test_area_url.startswith('http') and \
996 (options.http_library == "serf")
998 def is_ra_type_svn():
999 """Return True iff running tests over RA-svn."""
1000 return options.test_area_url.startswith('svn')
1002 def is_ra_type_file():
1003 """Return True iff running tests over RA-local."""
1004 return options.test_area_url.startswith('file')
1006 def is_fs_type_fsfs():
1007 # This assumes that fsfs is the default fs implementation.
1008 return options.fs_type == 'fsfs' or options.fs_type is None
1010 def is_os_windows():
1011 return os.name == 'nt'
1013 def is_posix_os():
1014 return os.name == 'posix'
1016 def is_os_darwin():
1017 return sys.platform == 'darwin'
1019 def is_fs_case_insensitive():
1020 return (is_os_darwin() or is_os_windows())
1022 def server_has_mergeinfo():
1023 return options.server_minor_version >= 5
1025 def server_has_revprop_commit():
1026 return options.server_minor_version >= 5
1028 def server_sends_copyfrom_on_update():
1029 return options.server_minor_version >= 5
1031 def server_authz_has_aliases():
1032 return options.server_minor_version >= 5
1034 def server_gets_client_capabilities():
1035 return options.server_minor_version >= 5
1037 def server_has_partial_replay():
1038 return options.server_minor_version >= 5
1040 def server_enforces_date_syntax():
1041 return options.server_minor_version >= 5
1043 ######################################################################
1046 class TestSpawningThread(threading.Thread):
1047 """A thread that runs test cases in their own processes.
1048 Receives test numbers to run from the queue, and saves results into
1049 the results field."""
1050 def __init__(self, queue):
1051 threading.Thread.__init__(self)
1052 self.queue = queue
1053 self.results = []
1055 def run(self):
1056 while True:
1057 try:
1058 next_index = self.queue.get_nowait()
1059 except queue.Empty:
1060 return
1062 self.run_one(next_index)
1064 def run_one(self, index):
1065 command = sys.argv[0]
1067 args = []
1068 args.append(str(index))
1069 args.append('-c')
1070 # add some startup arguments from this process
1071 if options.fs_type:
1072 args.append('--fs-type=' + options.fs_type)
1073 if options.test_area_url:
1074 args.append('--url=' + options.test_area_url)
1075 if options.verbose:
1076 args.append('-v')
1077 if options.cleanup:
1078 args.append('--cleanup')
1079 if options.enable_sasl:
1080 args.append('--enable-sasl')
1081 if options.http_library:
1082 args.append('--http-library=' + options.http_library)
1083 if options.server_minor_version:
1084 args.append('--server-minor-version=' + str(options.server_minor_version))
1086 result, stdout_lines, stderr_lines = spawn_process(command, 0, 0, None,
1087 *args)
1088 self.results.append((index, result, stdout_lines, stderr_lines))
1090 if result != 1:
1091 sys.stdout.write('.')
1092 else:
1093 sys.stdout.write('F')
1095 sys.stdout.flush()
1097 class TestRunner:
1098 """Encapsulate a single test case (predicate), including logic for
1099 runing the test and test list output."""
1101 def __init__(self, func, index):
1102 self.pred = svntest.testcase.create_test_case(func)
1103 self.index = index
1105 def list(self):
1106 if options.verbose and self.pred.inprogress:
1107 print(" %2d %-5s %s [[%s]]" % (self.index,
1108 self.pred.list_mode(),
1109 self.pred.description,
1110 self.pred.inprogress))
1111 else:
1112 print(" %2d %-5s %s" % (self.index,
1113 self.pred.list_mode(),
1114 self.pred.description))
1115 sys.stdout.flush()
1117 def get_function_name(self):
1118 return self.pred.get_function_name()
1120 def _print_name(self, prefix):
1121 if self.pred.inprogress:
1122 print("%s %s %s: %s [[WIMP: %s]]" % (prefix,
1123 os.path.basename(sys.argv[0]),
1124 str(self.index),
1125 self.pred.description,
1126 self.pred.inprogress))
1127 else:
1128 print("%s %s %s: %s" % (prefix,
1129 os.path.basename(sys.argv[0]),
1130 str(self.index),
1131 self.pred.description))
1132 sys.stdout.flush()
1134 def run(self):
1135 """Run self.pred and return the result. The return value is
1136 - 0 if the test was successful
1137 - 1 if it errored in a way that indicates test failure
1138 - 2 if the test skipped
1140 sbox_name = self.pred.get_sandbox_name()
1141 if sbox_name:
1142 sandbox = svntest.sandbox.Sandbox(sbox_name, self.index)
1143 else:
1144 sandbox = None
1146 # Explicitly set this so that commands that commit but don't supply a
1147 # log message will fail rather than invoke an editor.
1148 # Tests that want to use an editor should invoke svntest.main.use_editor.
1149 os.environ['SVN_EDITOR'] = ''
1150 os.environ['SVNTEST_EDITOR_FUNC'] = ''
1152 if options.use_jsvn:
1153 # Set this SVNKit specific variable to the current test (test name plus
1154 # its index) being run so that SVNKit daemon could use this test name
1155 # for its separate log file
1156 os.environ['SVN_CURRENT_TEST'] = os.path.basename(sys.argv[0]) + "_" + \
1157 str(self.index)
1159 svntest.actions.no_sleep_for_timestamps()
1161 saved_dir = os.getcwd()
1162 try:
1163 rc = self.pred.run(sandbox)
1164 if rc is not None:
1165 self._print_name('STYLE ERROR in')
1166 print('Test driver returned a status code.')
1167 sys.exit(255)
1168 result = svntest.testcase.RESULT_OK
1169 except Skip, ex:
1170 result = svntest.testcase.RESULT_SKIP
1171 except Failure, ex:
1172 result = svntest.testcase.RESULT_FAIL
1173 # We captured Failure and its subclasses. We don't want to print
1174 # anything for plain old Failure since that just indicates test
1175 # failure, rather than relevant information. However, if there
1176 # *is* information in the exception's arguments, then print it.
1177 if ex.__class__ != Failure or ex.args:
1178 ex_args = str(ex)
1179 if ex_args:
1180 print('EXCEPTION: %s: %s' % (ex.__class__.__name__, ex_args))
1181 else:
1182 print('EXCEPTION: %s' % ex.__class__.__name__)
1183 traceback.print_exc(file=sys.stdout)
1184 sys.stdout.flush()
1185 except KeyboardInterrupt:
1186 print('Interrupted')
1187 sys.exit(0)
1188 except SystemExit, ex:
1189 print('EXCEPTION: SystemExit(%d), skipping cleanup' % ex.code)
1190 self._print_name(ex.code and 'FAIL: ' or 'PASS: ')
1191 raise
1192 except:
1193 result = svntest.testcase.RESULT_FAIL
1194 print('UNEXPECTED EXCEPTION:')
1195 traceback.print_exc(file=sys.stdout)
1196 sys.stdout.flush()
1198 os.chdir(saved_dir)
1199 exit_code, result_text, result_benignity = self.pred.results(result)
1200 if not (options.quiet and result_benignity):
1201 self._print_name(result_text)
1202 if sandbox is not None and exit_code != 1 and options.cleanup:
1203 sandbox.cleanup_test_paths()
1204 return exit_code
1206 ######################################################################
1207 # Main testing functions
1209 # These two functions each take a TEST_LIST as input. The TEST_LIST
1210 # should be a list of test functions; each test function should take
1211 # no arguments and return a 0 on success, non-zero on failure.
1212 # Ideally, each test should also have a short, one-line docstring (so
1213 # it can be displayed by the 'list' command.)
1215 # Func to run one test in the list.
1216 def run_one_test(n, test_list, finished_tests = None):
1217 """Run the Nth client test in TEST_LIST, return the result.
1219 If we're running the tests in parallel spawn the test in a new process.
1222 if (n < 1) or (n > len(test_list) - 1):
1223 print("There is no test %s.\n" % n)
1224 return 1
1226 # Run the test.
1227 exit_code = TestRunner(test_list[n], n).run()
1228 return exit_code
1230 def _internal_run_tests(test_list, testnums, parallel, progress_func):
1231 """Run the tests from TEST_LIST whose indices are listed in TESTNUMS.
1233 If we're running the tests in parallel spawn as much parallel processes
1234 as requested and gather the results in a temp. buffer when a child
1235 process is finished.
1238 exit_code = 0
1239 finished_tests = []
1240 tests_started = 0
1242 if not parallel:
1243 for i, testnum in enumerate(testnums):
1244 if run_one_test(testnum, test_list) == 1:
1245 exit_code = 1
1246 # signal progress
1247 if progress_func:
1248 progress_func(i+1, len(testnums))
1249 else:
1250 number_queue = queue.Queue()
1251 for num in testnums:
1252 number_queue.put(num)
1254 threads = [ TestSpawningThread(number_queue) for i in range(parallel) ]
1255 for t in threads:
1256 t.start()
1258 for t in threads:
1259 t.join()
1261 # list of (index, result, stdout, stderr)
1262 results = []
1263 for t in threads:
1264 results += t.results
1265 results.sort()
1267 # signal some kind of progress
1268 if progress_func:
1269 progress_func(len(testnums), len(testnums))
1271 # terminate the line of dots
1272 print("")
1274 # all tests are finished, find out the result and print the logs.
1275 for (index, result, stdout_lines, stderr_lines) in results:
1276 if stdout_lines:
1277 for line in stdout_lines:
1278 sys.stdout.write(line)
1279 if stderr_lines:
1280 for line in stderr_lines:
1281 sys.stdout.write(line)
1282 if result == 1:
1283 exit_code = 1
1285 svntest.sandbox.cleanup_deferred_test_paths()
1286 return exit_code
1289 def create_default_options():
1290 """Set the global options to the defaults, as provided by the argument
1291 parser."""
1292 _parse_options([])
1295 def _create_parser():
1296 """Return a parser for our test suite."""
1297 # set up the parser
1298 usage = 'usage: %prog [options] [<test> ...]'
1299 parser = optparse.OptionParser(usage=usage)
1300 parser.add_option('-l', '--list', action='store_true', dest='list_tests',
1301 help='Print test doc strings instead of running them')
1302 parser.add_option('-v', '--verbose', action='store_true',
1303 help='Print binary command-lines (not with --quiet)')
1304 parser.add_option('-q', '--quiet', action='store_true',
1305 help='Print only unexpected results (not with --verbose)')
1306 parser.add_option('-p', '--parallel', action='store_const', const=5,
1307 dest='parallel',
1308 help='Run the tests in parallel')
1309 parser.add_option('-c', action='store_true', dest='is_child_process',
1310 help='Flag if we are running this python test as a ' +
1311 'child process')
1312 parser.add_option('--url', action='store',
1313 help='Base url to the repos (e.g. svn://localhost)')
1314 parser.add_option('--fs-type', action='store',
1315 help='Subversion file system type (fsfs or bdb)')
1316 parser.add_option('--cleanup', action='store_true',
1317 help='Whether to clean up')
1318 parser.add_option('--enable-sasl', action='store_true',
1319 help='Whether to enable SASL authentication')
1320 parser.add_option('--bin', action='store', dest='svn_bin',
1321 help='Use the svn binaries installed in this path')
1322 parser.add_option('--use-jsvn', action='store_true',
1323 help="Use the jsvn (SVNKit based) binaries. Can be " +
1324 "combined with --bin to point to a specific path")
1325 parser.add_option('--http-library', action='store',
1326 help="Make svn use this DAV library (neon or serf) if " +
1327 "it supports both, else assume it's using this " +
1328 "one; the default is neon")
1329 parser.add_option('--server-minor-version', type='int', action='store',
1330 help="Set the minor version for the server ('4', " +
1331 "'5', or '6').")
1332 parser.add_option('--fsfs-packing', action='store_true',
1333 help="Run 'svnadmin pack' automatically")
1334 parser.add_option('--fsfs-sharding', action='store', type='int',
1335 help='Default shard size (for fsfs)')
1336 parser.add_option('--config-file', action='store',
1337 help="Configuration file for tests.")
1338 parser.add_option('--keep-local-tmp', action='store_true',
1339 help="Don't remove svn-test-work/local_tmp after test " +
1340 "run is complete. Useful for debugging failures.")
1341 parser.add_option('--development', action='store_true',
1342 help='Test development mode: provides more detailed ' +
1343 'test output and ignores all exceptions in the ' +
1344 'run_and_verify* functions. This option is only ' +
1345 'useful during test development!')
1347 # most of the defaults are None, but some are other values, set them here
1348 parser.set_defaults(
1349 server_minor_version=7,
1350 url=file_scheme_prefix + pathname2url(os.path.abspath(os.getcwd())),
1351 http_library='serf')
1353 return parser
1356 def _parse_options(arglist=sys.argv[1:]):
1357 """Parse the arguments in arg_list, and set the global options object with
1358 the results"""
1360 global options
1362 parser = _create_parser()
1363 (options, args) = parser.parse_args(arglist)
1365 # some sanity checking
1366 if options.verbose and options.quiet:
1367 parser.error("'verbose' and 'quiet' are incompatible")
1368 if options.fsfs_packing and not options.fsfs_sharding:
1369 parser.error("--fsfs-packing requires --fsfs-sharding")
1370 if options.server_minor_version < 4 or options.server_minor_version > 7:
1371 parser.error("test harness only supports server minor versions 4-7")
1373 if options.url:
1374 if options.url[-1:] == '/': # Normalize url to have no trailing slash
1375 options.test_area_url = options.url[:-1]
1376 else:
1377 options.test_area_url = options.url
1379 return (parser, args)
1382 def run_tests(test_list, serial_only = False):
1383 """Main routine to run all tests in TEST_LIST.
1385 NOTE: this function does not return. It does a sys.exit() with the
1386 appropriate exit code.
1389 sys.exit(execute_tests(test_list, serial_only))
1392 # Main func. This is the "entry point" that all the test scripts call
1393 # to run their list of tests.
1395 # This routine parses sys.argv to decide what to do.
1396 def execute_tests(test_list, serial_only = False, test_name = None,
1397 progress_func = None):
1398 """Similar to run_tests(), but just returns the exit code, rather than
1399 exiting the process. This function can be used when a caller doesn't
1400 want the process to die."""
1402 global pristine_url
1403 global svn_binary
1404 global svnadmin_binary
1405 global svnlook_binary
1406 global svnsync_binary
1407 global svndumpfilter_binary
1408 global svnversion_binary
1409 global options
1411 if test_name:
1412 sys.argv[0] = test_name
1414 testnums = []
1416 if not options:
1417 (parser, args) = _parse_options()
1418 else:
1419 args = []
1420 parser = _create_parser()
1422 # parse the positional arguments (test nums, names)
1423 for arg in args:
1424 appended = False
1425 try:
1426 testnums.append(int(arg))
1427 appended = True
1428 except ValueError:
1429 # Do nothing for now.
1430 appended = False
1432 if not appended:
1433 try:
1434 # Check if the argument is a range
1435 numberstrings = arg.split(':');
1436 if len(numberstrings) != 2:
1437 numberstrings = arg.split('-');
1438 if len(numberstrings) != 2:
1439 raise ValueError
1440 left = int(numberstrings[0])
1441 right = int(numberstrings[1])
1442 if left > right:
1443 raise ValueError
1445 for nr in range(left,right+1):
1446 testnums.append(nr)
1447 else:
1448 appended = True
1449 except ValueError:
1450 appended = False
1452 if not appended:
1453 try:
1454 # Check if the argument is a function name, and translate
1455 # it to a number if possible
1456 for testnum in list(range(1, len(test_list))):
1457 test_case = TestRunner(test_list[testnum], testnum)
1458 if test_case.get_function_name() == str(arg):
1459 testnums.append(testnum)
1460 appended = True
1461 break
1462 except ValueError:
1463 appended = False
1465 if not appended:
1466 parser.error("invalid test number, range of numbers, " +
1467 "or function '%s'\n" % arg)
1469 # Calculate pristine_url from test_area_url.
1470 pristine_url = options.test_area_url + '/' + pathname2url(pristine_dir)
1472 if options.use_jsvn:
1473 if options.svn_bin is None:
1474 options.svn_bin = ''
1475 svn_binary = os.path.join(options.svn_bin, 'jsvn' + _bat)
1476 svnadmin_binary = os.path.join(options.svn_bin, 'jsvnadmin' + _bat)
1477 svnlook_binary = os.path.join(options.svn_bin, 'jsvnlook' + _bat)
1478 svnsync_binary = os.path.join(options.svn_bin, 'jsvnsync' + _bat)
1479 svndumpfilter_binary = os.path.join(options.svn_bin,
1480 'jsvndumpfilter' + _bat)
1481 svnversion_binary = os.path.join(options.svn_bin,
1482 'jsvnversion' + _bat)
1483 else:
1484 if options.svn_bin:
1485 svn_binary = os.path.join(options.svn_bin, 'svn' + _exe)
1486 svnadmin_binary = os.path.join(options.svn_bin, 'svnadmin' + _exe)
1487 svnlook_binary = os.path.join(options.svn_bin, 'svnlook' + _exe)
1488 svnsync_binary = os.path.join(options.svn_bin, 'svnsync' + _exe)
1489 svndumpfilter_binary = os.path.join(options.svn_bin,
1490 'svndumpfilter' + _exe)
1491 svnversion_binary = os.path.join(options.svn_bin, 'svnversion' + _exe)
1493 ######################################################################
1495 # Cleanup: if a previous run crashed or interrupted the python
1496 # interpreter, then `temp_dir' was never removed. This can cause wonkiness.
1497 if not options.is_child_process:
1498 safe_rmtree(temp_dir, 1)
1500 if not testnums:
1501 # If no test numbers were listed explicitly, include all of them:
1502 testnums = list(range(1, len(test_list)))
1504 if options.list_tests:
1505 print("Test # Mode Test Description")
1506 print("------ ----- ----------------")
1507 for testnum in testnums:
1508 TestRunner(test_list[testnum], testnum).list()
1510 # done. just exit with success.
1511 sys.exit(0)
1513 # don't run tests in parallel when the tests don't support it or there
1514 # are only a few tests to run.
1515 if serial_only or len(testnums) < 2:
1516 options.parallel = 0
1518 if not options.is_child_process:
1519 # Build out the default configuration directory
1520 create_config_dir(default_config_dir)
1522 # Setup the pristine repository
1523 svntest.actions.setup_pristine_repository()
1525 # Run the tests.
1526 exit_code = _internal_run_tests(test_list, testnums, options.parallel,
1527 progress_func)
1529 # Remove all scratchwork: the 'pristine' repository, greek tree, etc.
1530 # This ensures that an 'import' will happen the next time we run.
1531 if not options.is_child_process and not options.keep_local_tmp:
1532 safe_rmtree(temp_dir, 1)
1534 # Cleanup after ourselves.
1535 svntest.sandbox.cleanup_deferred_test_paths()
1537 # Return the appropriate exit code from the tests.
1538 return exit_code