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
24 ######################################################################
26 import sys
# for argv[]
28 import shutil
# for rmtree()
30 import stat
# for ST_MODE
32 import copy
# for deepcopy()
33 import time
# for time()
34 import traceback
# for print_exc()
36 import optparse
# for argument parsing
41 from urllib
.parse
import quote
as urllib_parse_quote
42 from urllib
.parse
import unquote
as urllib_parse_unquote
46 from urllib
import quote
as urllib_parse_quote
47 from urllib
import unquote
as urllib_parse_unquote
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 #####################################################################
77 class SVNProcessTerminatedBySignal(Failure
):
78 "Exception raised if a spawned process segfaulted, aborted, etc."
81 class SVNLineUnequal(Failure
):
82 "Exception raised if two lines are unequal"
85 class SVNUnmatchedError(Failure
):
86 "Exception raised if an expected error is not found"
89 class SVNCommitFailure(Failure
):
90 "Exception raised if a commit failed"
93 class SVNRepositoryCopyFailure(Failure
):
94 "Exception raised if unable to copy a repository"
97 class SVNRepositoryCreateFailure(Failure
):
98 "Exception raised if unable to create a repository"
102 if sys
.platform
== 'win32':
104 file_scheme_prefix
= 'file:///'
107 os
.environ
['SVN_DBG_STACKTRACES_TO_STDERR'] = 'y'
110 file_scheme_prefix
= 'file://'
114 # The location of our mock svneditor script.
116 svneditor_script
= os
.path
.join(sys
.path
[0], 'svneditor.bat')
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"
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' + \
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.
165 # Global variable to track all of our options
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"),
204 'A/mu' : _item("This is the file 'mu'.\n"),
206 'A/B/lambda' : _item("This is the file 'lambda'.\n"),
208 'A/B/E/alpha' : _item("This is the file 'alpha'.\n"),
209 'A/B/E/beta' : _item("This is the file 'beta'.\n"),
213 'A/D/gamma' : _item("This is the file 'gamma'.\n"),
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"),
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
228 "Wrap a function, catch, print and ignore exceptions"
229 def w(*args
, **kwds
):
231 return func(*args
, **kwds
)
233 if ex
.__class
__ != Failure
or ex
.args
:
236 print('EXCEPTION: %s: %s' % (ex
.__class
__.__name
__, ex_args
))
238 print('EXCEPTION: %s' % ex
.__class
__.__name
__)
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']
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
:
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
,
317 # A regular expression that matches arguments that are trivially safe
318 # to pass on a command line without quoting on any supported operating
320 _safe_arg_re
= re
.compile(r
'^[A-Za-z\d\.\_\/\-\:\@]+$')
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
336 if _safe_arg_re
.match(arg
):
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':
361 args
= ' '.join([_quote_arg(x
) for x
in args
])
362 command
= command
[0] + ' ' + args
363 command_string
= command
365 command_string
= ' '.join(command
)
368 stdin
= subprocess
.PIPE
370 stdout
= subprocess
.PIPE
372 stderr
= subprocess
.PIPE
374 p
= subprocess
.Popen(command
,
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)."""
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)
407 exit_signal
= os
.WTERMSIG(-exit_code
)
409 exit_signal
= exit_code
411 if stdout_lines
is not None:
412 sys
.stdout
.write("".join(stdout_lines
))
414 if stderr_lines
is not None:
415 sys
.stderr
.write("".join(stderr_lines
))
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
))
422 raise SVNProcessTerminatedBySignal
424 if exit_code
and options
.verbose
:
425 sys
.stderr
.write("CMD: %s exited with %d\n"
426 % (command_string
, exit_code
))
428 return stdout_lines
, stderr_lines
, exit_code
430 def spawn_process(command
, bufsize
=0, binary_mode
=0, stdin_lines
=None,
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
])))
450 infile
, outfile
, errfile
, kid
= open_pipe([command
] + list(varargs
), bufsize
)
453 for x
in stdin_lines
:
456 stdout_lines
, stderr_lines
, exit_code
= wait_on_pipe(kid
, binary_mode
)
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
476 If ERROR_EXPECTED is None, any stderr also will be printed."""
481 exit_code
, stdout_lines
, stderr_lines
= spawn_process(command
,
489 print('<TIME = %.6f>' % (stop
- start
))
490 for x
in stdout_lines
:
492 for x
in stderr_lines
:
495 if (not error_expected
) and (stderr_lines
):
496 if not options
.verbose
:
497 for x
in stderr_lines
:
502 [line
for line
in stdout_lines
if not line
.startswith("DBG:")], \
505 def create_config_dir(cfgdir
, config_contents
=None, server_contents
=None):
506 "Create config directories and files"
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
):
516 # define default config file contents if none provided
517 if config_contents
is None:
518 config_contents
= """
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
= """
536 store-plaintext-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
:
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
,
553 if '--username' in args
:
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
,
597 ### finish the CMD output
599 if exit_code
or stderr_lines
:
600 ### report on this? or continue to just skip it?
606 exec(''.join([line
for line
in stdout_lines
if not line
.startswith("DBG:")]))
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"
623 chmod_tree(dirname
, 0666, 0666)
624 shutil
.rmtree(dirname
)
626 if not os
.path
.exists(dirname
):
630 for delay
in (0.5, 1, 2, 4):
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
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",
683 # Skip tests if we can't create the repository.
686 if line
.find('Unknown FS type') != -1:
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")
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':
705 if options
.config_file
is not None:
706 shutil
.copy(options
.config_file
, get_fsfs_conf_file_path(path
))
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
715 line
= 'layout linear'
719 format_file_path
= get_fsfs_format_file_path(path
)
720 contents
= open(format_file_path
, 'rb').read()
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]
730 os
.chmod(format_file_path
, 0666)
731 file_write(format_file_path
, new_contents
, 'wb')
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
:
738 abs_path
= os
.path
.abspath(path
)
739 create_python_hook_script(get_post_commit_hook_path(abs_path
),
740 "import subprocess\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
]
764 load_args
= load_args
+ ['--ignore-uuid']
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
)))
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
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)
788 #load_in is dump_out so it's already closed.
792 if saved_quiet
is None:
793 del os
.environ
['SVN_DBG_QUIET']
795 os
.environ
['SVN_DBG_QUIET'] = saved_quiet
797 dump_re
= re
.compile(r
'^\* Dumped revision (\d+)\.\r?$')
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
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?$')
811 for load_line
in load_stdout
:
812 match
= load_re
.match(load_line
)
814 if match
.group(1) != str(expect_revision
):
815 print('ERROR: load failed: %s' % load_line
.strip())
816 raise SVNRepositoryCopyFailure
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)
829 return scheme
+ re
.sub(r
'//*', '/', input[len(scheme
):])
834 def create_python_hook_script(hook_path
, hook_script_code
):
835 """Create a Python hook script at HOOK_PATH with the specified
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
))
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");
860 fp
.write("password-db = passwd\n")
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
+ ":"
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
))
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."""
909 revstart
= abs(revstart
)
910 return "--- Recording mergeinfo for reverse merge of r%ld .*:\n" \
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" \
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,
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 ""
929 return "--- Merging differences between %srepository URLs into '.+':\n" \
930 % (foreign
and "foreign " or "")
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 "")
937 return "--- Reverse-merging %sr%ld into '.+':\n" \
938 % (from_foreign_phrase
, abs(revstart
))
940 return "--- Merging %sr%ld into '.+':\n" \
941 % (from_foreign_phrase
, revstart
)
943 if revstart
> revend
:
944 return "--- Reverse-merging %sr%ld through r%ld into '.+':\n" \
945 % (from_foreign_phrase
, revstart
, revend
)
947 return "--- Merging %sr%ld through r%ld into '.+':\n" \
948 % (from_foreign_phrase
, revstart
, revend
)
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'):
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:
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
:
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'
1014 return os
.name
== 'posix'
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
)
1058 next_index
= self
.queue
.get_nowait()
1062 self
.run_one(next_index
)
1064 def run_one(self
, index
):
1065 command
= sys
.argv
[0]
1068 args
.append(str(index
))
1070 # add some startup arguments from this process
1072 args
.append('--fs-type=' + options
.fs_type
)
1073 if options
.test_area_url
:
1074 args
.append('--url=' + options
.test_area_url
)
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,
1088 self
.results
.append((index
, result
, stdout_lines
, stderr_lines
))
1091 sys
.stdout
.write('.')
1093 sys
.stdout
.write('F')
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
)
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
))
1112 print(" %2d %-5s %s" % (self
.index
,
1113 self
.pred
.list_mode(),
1114 self
.pred
.description
))
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]),
1125 self
.pred
.description
,
1126 self
.pred
.inprogress
))
1128 print("%s %s %s: %s" % (prefix
,
1129 os
.path
.basename(sys
.argv
[0]),
1131 self
.pred
.description
))
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()
1142 sandbox
= svntest
.sandbox
.Sandbox(sbox_name
, self
.index
)
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]) + "_" + \
1159 svntest
.actions
.no_sleep_for_timestamps()
1161 saved_dir
= os
.getcwd()
1163 rc
= self
.pred
.run(sandbox
)
1165 self
._print
_name
('STYLE ERROR in')
1166 print('Test driver returned a status code.')
1168 result
= svntest
.testcase
.RESULT_OK
1170 result
= svntest
.testcase
.RESULT_SKIP
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
:
1180 print('EXCEPTION: %s: %s' % (ex
.__class
__.__name
__, ex_args
))
1182 print('EXCEPTION: %s' % ex
.__class
__.__name
__)
1183 traceback
.print_exc(file=sys
.stdout
)
1185 except KeyboardInterrupt:
1186 print('Interrupted')
1188 except SystemExit, ex
:
1189 print('EXCEPTION: SystemExit(%d), skipping cleanup' % ex
.code
)
1190 self
._print
_name
(ex
.code
and 'FAIL: ' or 'PASS: ')
1193 result
= svntest
.testcase
.RESULT_FAIL
1194 print('UNEXPECTED EXCEPTION:')
1195 traceback
.print_exc(file=sys
.stdout
)
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()
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
)
1227 exit_code
= TestRunner(test_list
[n
], n
).run()
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.
1243 for i
, testnum
in enumerate(testnums
):
1244 if run_one_test(testnum
, test_list
) == 1:
1248 progress_func(i
+1, len(testnums
))
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
) ]
1261 # list of (index, result, stdout, stderr)
1264 results
+= t
.results
1267 # signal some kind of progress
1269 progress_func(len(testnums
), len(testnums
))
1271 # terminate the line of dots
1274 # all tests are finished, find out the result and print the logs.
1275 for (index
, result
, stdout_lines
, stderr_lines
) in results
:
1277 for line
in stdout_lines
:
1278 sys
.stdout
.write(line
)
1280 for line
in stderr_lines
:
1281 sys
.stdout
.write(line
)
1285 svntest
.sandbox
.cleanup_deferred_test_paths()
1289 def create_default_options():
1290 """Set the global options to the defaults, as provided by the argument
1295 def _create_parser():
1296 """Return a parser for our test suite."""
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,
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 ' +
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', " +
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')
1356 def _parse_options(arglist
=sys
.argv
[1:]):
1357 """Parse the arguments in arg_list, and set the global options object with
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")
1374 if options
.url
[-1:] == '/': # Normalize url to have no trailing slash
1375 options
.test_area_url
= options
.url
[:-1]
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."""
1404 global svnadmin_binary
1405 global svnlook_binary
1406 global svnsync_binary
1407 global svndumpfilter_binary
1408 global svnversion_binary
1412 sys
.argv
[0] = test_name
1417 (parser
, args
) = _parse_options()
1420 parser
= _create_parser()
1422 # parse the positional arguments (test nums, names)
1426 testnums
.append(int(arg
))
1429 # Do nothing for now.
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:
1440 left
= int(numberstrings
[0])
1441 right
= int(numberstrings
[1])
1445 for nr
in range(left
,right
+1):
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
)
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
)
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)
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.
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()
1526 exit_code
= _internal_run_tests(test_list
, testnums
, options
.parallel
,
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.