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()
39 from urllib
.parse
import quote
as urllib_parse_quote
40 from urllib
.parse
import unquote
as urllib_parse_unquote
44 from urllib
import quote
as urllib_parse_quote
45 from urllib
import unquote
as urllib_parse_unquote
49 my_getopt
= getopt
.gnu_getopt
50 except AttributeError:
51 my_getopt
= getopt
.getopt
54 from svntest
import Failure
55 from svntest
import Skip
58 ######################################################################
60 # HOW TO USE THIS MODULE:
62 # Write a new python script that
64 # 1) imports this 'svntest' package
66 # 2) contains a number of related 'test' routines. (Each test
67 # routine should take no arguments, and return None on success
68 # or throw a Failure exception on failure. Each test should
69 # also contain a short docstring.)
71 # 3) places all the tests into a list that begins with None.
73 # 4) calls svntest.main.client_test() on the list.
75 # Also, your tests will probably want to use some of the common
76 # routines in the 'Utilities' section below.
78 #####################################################################
81 class SVNProcessTerminatedBySignal(Failure
):
82 "Exception raised if a spawned process segfaulted, aborted, etc."
85 class SVNLineUnequal(Failure
):
86 "Exception raised if two lines are unequal"
89 class SVNUnmatchedError(Failure
):
90 "Exception raised if an expected error is not found"
93 class SVNCommitFailure(Failure
):
94 "Exception raised if a commit failed"
97 class SVNRepositoryCopyFailure(Failure
):
98 "Exception raised if unable to copy a repository"
101 class SVNRepositoryCreateFailure(Failure
):
102 "Exception raised if unable to create a repository"
106 if sys
.platform
== 'win32':
108 file_scheme_prefix
= 'file:///'
111 os
.environ
['SVN_DBG_STACKTRACES_TO_STDERR'] = 'y'
114 file_scheme_prefix
= 'file://'
118 # The location of our mock svneditor script.
120 svneditor_script
= os
.path
.join(sys
.path
[0], 'svneditor.bat')
122 svneditor_script
= os
.path
.join(sys
.path
[0], 'svneditor.py')
124 # Username and password used by the working copies
125 wc_author
= 'jrandom'
126 wc_passwd
= 'rayjandom'
128 # Username and password used by the working copies for "second user"
130 wc_author2
= 'jconstant' # use the same password as wc_author
132 # Set C locale for command line programs
133 os
.environ
['LC_ALL'] = 'C'
135 # This function mimics the Python 2.3 urllib function of the same name.
136 def pathname2url(path
):
137 """Convert the pathname PATH from the local syntax for a path to the form
138 used in the path component of a URL. This does not produce a complete URL.
139 The return value will already be quoted using the quote() function."""
140 return urllib_parse_quote(path
.replace('\\', '/'))
142 # This function mimics the Python 2.3 urllib function of the same name.
143 def url2pathname(path
):
144 """Convert the path component PATH from an encoded URL to the local syntax
145 for a path. This does not accept a complete URL. This function uses
146 unquote() to decode PATH."""
147 return os
.path
.normpath(urllib_parse_unquote(path
))
149 ######################################################################
150 # Global variables set during option parsing. These should not be used
151 # until the variable command_line_parsed has been set to True, as is
152 # done in run_tests below.
153 command_line_parsed
= False
155 # The locations of the svn, svnadmin and svnlook binaries, relative to
156 # the only scripts that import this file right now (they live in ../).
157 # Use --bin to override these defaults.
158 svn_binary
= os
.path
.abspath('../../svn/svn' + _exe
)
159 svnadmin_binary
= os
.path
.abspath('../../svnadmin/svnadmin' + _exe
)
160 svnlook_binary
= os
.path
.abspath('../../svnlook/svnlook' + _exe
)
161 svnsync_binary
= os
.path
.abspath('../../svnsync/svnsync' + _exe
)
162 svnversion_binary
= os
.path
.abspath('../../svnversion/svnversion' + _exe
)
163 svndumpfilter_binary
= os
.path
.abspath('../../svndumpfilter/svndumpfilter' + \
165 entriesdump_binary
= os
.path
.abspath('entries-dump' + _exe
)
167 # Global variable indicating if we want verbose output, that is,
168 # details of what commands each test does as it does them. This is
169 # incompatible with quiet_mode.
172 # Global variable indicating if we want quiet output, that is, don't
173 # show PASS, XFAIL, or SKIP notices, but do show FAIL and XPASS. This
174 # is incompatible with verbose_mode.
177 # Global variable indicating if we want test data cleaned up after success
180 # Global variable indicating if svnserve should use Cyrus SASL
183 # Global variable indicating that SVNKit binaries should be used
186 # Global variable indicating which DAV library to use if both are available
187 # ('neon', 'serf'). The default is neon for backward compatibility of the
189 preferred_http_library
= 'neon'
191 # Global variable: Number of shards to use in FSFS
192 # 'None' means "use FSFS's default"
195 # Global variable: automatically pack FSFS repositories after every commit
198 # Configuration file (copied into FSFS fsfs.conf).
201 # Global variable indicating what the minor version of the server
202 # tested against is (4 for 1.4.x, for example).
203 server_minor_version
= 7
205 # Global variable indicating if this is a child process and no cleanup
206 # of global directories is needed.
207 is_child_process
= False
209 # Global URL to testing area. Default to ra_local, current working dir.
210 test_area_url
= file_scheme_prefix
+ pathname2url(os
.path
.abspath(os
.getcwd()))
212 # Location to the pristine repository, will be calculated from test_area_url
213 # when we know what the user specified for --url.
216 # Global variable indicating the FS type for repository creations.
219 # End of command-line-set global variables.
220 ######################################################################
222 # All temporary repositories and working copies are created underneath
223 # this dir, so there's one point at which to mount, e.g., a ramdisk.
224 work_dir
= "svn-test-work"
226 # Constant for the merge info property.
227 SVN_PROP_MERGEINFO
= "svn:mergeinfo"
229 # Where we want all the repositories and working copies to live.
230 # Each test will have its own!
231 general_repo_dir
= os
.path
.join(work_dir
, "repositories")
232 general_wc_dir
= os
.path
.join(work_dir
, "working_copies")
234 # temp directory in which we will create our 'pristine' local
235 # repository and other scratch data. This should be removed when we
236 # quit and when we startup.
237 temp_dir
= os
.path
.join(work_dir
, 'local_tmp')
239 # (derivatives of the tmp dir.)
240 pristine_dir
= os
.path
.join(temp_dir
, "repos")
241 greek_dump_dir
= os
.path
.join(temp_dir
, "greekfiles")
242 default_config_dir
= os
.path
.abspath(os
.path
.join(temp_dir
, "config"))
245 # Our pristine greek-tree state.
247 # If a test wishes to create an "expected" working-copy tree, it should
248 # call main.greek_state.copy(). That method will return a copy of this
249 # State object which can then be edited.
251 _item
= svntest
.wc
.StateItem
252 greek_state
= svntest
.wc
.State('', {
253 'iota' : _item("This is the file 'iota'.\n"),
255 'A/mu' : _item("This is the file 'mu'.\n"),
257 'A/B/lambda' : _item("This is the file 'lambda'.\n"),
259 'A/B/E/alpha' : _item("This is the file 'alpha'.\n"),
260 'A/B/E/beta' : _item("This is the file 'beta'.\n"),
264 'A/D/gamma' : _item("This is the file 'gamma'.\n"),
266 'A/D/G/pi' : _item("This is the file 'pi'.\n"),
267 'A/D/G/rho' : _item("This is the file 'rho'.\n"),
268 'A/D/G/tau' : _item("This is the file 'tau'.\n"),
270 'A/D/H/chi' : _item("This is the file 'chi'.\n"),
271 'A/D/H/psi' : _item("This is the file 'psi'.\n"),
272 'A/D/H/omega' : _item("This is the file 'omega'.\n"),
276 ######################################################################
277 # Utilities shared by the tests
279 "Wrap a function, catch, print and ignore exceptions"
280 def w(*args
, **kwds
):
282 return func(*args
, **kwds
)
284 if ex
.__class
__ != Failure
or ex
.args
:
287 print('EXCEPTION: %s: %s' % (ex
.__class
__.__name
__, ex_args
))
289 print('EXCEPTION: %s' % ex
.__class
__.__name
__)
292 def setup_development_mode():
293 "Wraps functions in module actions"
294 l
= [ 'run_and_verify_svn',
295 'run_and_verify_svnversion',
296 'run_and_verify_load',
297 'run_and_verify_dump',
298 'run_and_verify_checkout',
299 'run_and_verify_export',
300 'run_and_verify_update',
301 'run_and_verify_merge',
302 'run_and_verify_switch',
303 'run_and_verify_commit',
304 'run_and_verify_unquiet_status',
305 'run_and_verify_status',
306 'run_and_verify_diff_summarize',
307 'run_and_verify_diff_summarize_xml',
308 'run_and_validate_lock']
311 setattr(svntest
.actions
, func
, wrap_ex(getattr(svntest
.actions
, func
)))
313 def get_admin_name():
314 "Return name of SVN administrative subdirectory."
316 if (windows
or sys
.platform
== 'cygwin') \
317 and 'SVN_ASP_DOT_NET_HACK' in os
.environ
:
322 def get_start_commit_hook_path(repo_dir
):
323 "Return the path of the start-commit-hook conf file in REPO_DIR."
325 return os
.path
.join(repo_dir
, "hooks", "start-commit")
328 def get_pre_commit_hook_path(repo_dir
):
329 "Return the path of the pre-commit-hook conf file in REPO_DIR."
331 return os
.path
.join(repo_dir
, "hooks", "pre-commit")
334 def get_post_commit_hook_path(repo_dir
):
335 "Return the path of the post-commit-hook conf file in REPO_DIR."
337 return os
.path
.join(repo_dir
, "hooks", "post-commit")
339 def get_pre_revprop_change_hook_path(repo_dir
):
340 "Return the path of the pre-revprop-change hook script in REPO_DIR."
342 return os
.path
.join(repo_dir
, "hooks", "pre-revprop-change")
344 def get_svnserve_conf_file_path(repo_dir
):
345 "Return the path of the svnserve.conf file in REPO_DIR."
347 return os
.path
.join(repo_dir
, "conf", "svnserve.conf")
349 def get_fsfs_conf_file_path(repo_dir
):
350 "Return the path of the fsfs.conf file in REPO_DIR."
352 return os
.path
.join(repo_dir
, "db", "fsfs.conf")
354 def get_fsfs_format_file_path(repo_dir
):
355 "Return the path of the format file in REPO_DIR."
357 return os
.path
.join(repo_dir
, "db", "format")
359 # Run any binary, logging the command line and return code
360 def run_command(command
, error_expected
, binary_mode
=0, *varargs
):
361 """Run COMMAND with VARARGS. Return exit code as int; stdout, stderr
362 as lists of lines (including line terminators). See run_command_stdin()
363 for details. If ERROR_EXPECTED is None, any stderr also will be printed."""
365 return run_command_stdin(command
, error_expected
, binary_mode
,
368 # A regular expression that matches arguments that are trivially safe
369 # to pass on a command line without quoting on any supported operating
371 _safe_arg_re
= re
.compile(r
'^[A-Za-z\d\.\_\/\-\:\@]+$')
374 """Quote ARG for a command line.
376 Simply surround every argument in double-quotes unless it contains
377 only universally harmless characters.
379 WARNING: This function cannot handle arbitrary command-line
380 arguments. It can easily be confused by shell metacharacters. A
381 perfect job would be difficult and OS-dependent (see, for example,
382 http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp).
383 In other words, this function is just good enough for what we need
387 if _safe_arg_re
.match(arg
):
391 arg
= arg
.replace('$', '\$')
392 return '"%s"' % (arg
,)
394 def open_pipe(command
, stdin
=None, stdout
=None, stderr
=None):
395 """Opens a subprocess.Popen pipe to COMMAND using STDIN,
398 Returns (infile, outfile, errfile, waiter); waiter
399 should be passed to wait_on_pipe."""
400 command
= [str(x
) for x
in command
]
402 # On Windows subprocess.Popen() won't accept a Python script as
403 # a valid program to execute, rather it wants the Python executable.
404 if (sys
.platform
== 'win32') and (command
[0].endswith('.py')):
405 command
.insert(0, sys
.executable
)
407 # Quote only the arguments on Windows. Later versions of subprocess,
408 # 2.5.2+ confirmed, don't require this quoting, but versions < 2.4.3 do.
409 if sys
.platform
== 'win32':
411 args
= ' '.join([_quote_arg(x
) for x
in args
])
412 command
= command
[0] + ' ' + args
413 command_string
= command
415 command_string
= ' '.join(command
)
418 stdin
= subprocess
.PIPE
420 stdout
= subprocess
.PIPE
422 stderr
= subprocess
.PIPE
424 p
= subprocess
.Popen(command
,
428 close_fds
=not windows
)
429 return p
.stdin
, p
.stdout
, p
.stderr
, (p
, command_string
)
431 def wait_on_pipe(waiter
, binary_mode
, stdin
=None):
432 """WAITER is (KID, COMMAND_STRING). Wait for KID (opened with open_pipe)
433 to finish, dying if it does. If KID fails, create an error message
434 containing any stdout and stderr from the kid. Show COMMAND_STRING in
435 diagnostic messages. Normalize Windows line endings of stdout and stderr
436 if not BINARY_MODE. Return KID's exit code as int; stdout, stderr as
437 lists of lines (including line terminators)."""
441 kid
, command_string
= waiter
442 stdout
, stderr
= kid
.communicate(stdin
)
443 exit_code
= kid
.returncode
445 # Normalize Windows line endings if in text mode.
446 if windows
and not binary_mode
:
447 stdout
= stdout
.replace('\r\n', '\n')
448 stderr
= stderr
.replace('\r\n', '\n')
450 # Convert output strings to lists.
451 stdout_lines
= stdout
.splitlines(True)
452 stderr_lines
= stderr
.splitlines(True)
456 exit_signal
= os
.WTERMSIG(-exit_code
)
458 exit_signal
= exit_code
460 if stdout_lines
is not None:
461 sys
.stdout
.write("".join(stdout_lines
))
463 if stderr_lines
is not None:
464 sys
.stderr
.write("".join(stderr_lines
))
467 # show the whole path to make it easier to start a debugger
468 sys
.stderr
.write("CMD: %s terminated by signal %d\n"
469 % (command_string
, exit_signal
))
471 raise SVNProcessTerminatedBySignal
473 if exit_code
and verbose_mode
:
474 sys
.stderr
.write("CMD: %s exited with %d\n"
475 % (command_string
, exit_code
))
476 return stdout_lines
, stderr_lines
, exit_code
478 def spawn_process(command
, binary_mode
=0, stdin_lines
=None, *varargs
):
479 """Run any binary, supplying input text, logging the command line.
480 Normalize Windows line endings of stdout and stderr if not BINARY_MODE.
481 Return exit code as int; stdout, stderr as lists of lines (including
482 line terminators)."""
483 if stdin_lines
and not isinstance(stdin_lines
, list):
484 raise TypeError("stdin_lines should have list type")
486 # Log the command line
487 if verbose_mode
and not command
.endswith('.py'):
488 sys
.stdout
.write('CMD: %s %s\n' % (os
.path
.basename(command
),
489 ' '.join([_quote_arg(x
) for x
in varargs
])))
492 infile
, outfile
, errfile
, kid
= open_pipe([command
] + list(varargs
))
495 for x
in stdin_lines
:
498 stdout_lines
, stderr_lines
, exit_code
= wait_on_pipe(kid
, binary_mode
)
504 return exit_code
, stdout_lines
, stderr_lines
506 def run_command_stdin(command
, error_expected
, binary_mode
=0,
507 stdin_lines
=None, *varargs
):
508 """Run COMMAND with VARARGS; input STDIN_LINES (a list of strings
509 which should include newline characters) to program via stdin - this
510 should not be very large, as if the program outputs more than the OS
511 is willing to buffer, this will deadlock, with both Python and
512 COMMAND waiting to write to each other for ever.
513 Normalize Windows line endings of stdout and stderr if not BINARY_MODE.
514 Return exit code as int; stdout, stderr as lists of lines (including
516 If ERROR_EXPECTED is None, any stderr also will be printed."""
521 exit_code
, stdout_lines
, stderr_lines
= spawn_process(command
,
528 print('<TIME = %.6f>' % (stop
- start
))
529 for x
in stdout_lines
:
531 for x
in stderr_lines
:
534 if (not error_expected
) and (stderr_lines
):
536 for x
in stderr_lines
:
541 [line
for line
in stdout_lines
if not line
.startswith("DBG:")], \
544 def create_config_dir(cfgdir
, config_contents
=None, server_contents
=None):
545 "Create config directories and files"
548 cfgfile_cfg
= os
.path
.join(cfgdir
, 'config')
549 cfgfile_srv
= os
.path
.join(cfgdir
, 'servers')
551 # create the directory
552 if not os
.path
.isdir(cfgdir
):
555 # define default config file contents if none provided
556 if config_contents
is None:
557 config_contents
= """
563 interactive-conflicts = false
566 # define default server file contents if none provided
567 if server_contents
is None:
568 http_library_str
= ""
569 if preferred_http_library
:
570 http_library_str
= "http-library=%s" % (preferred_http_library
)
571 server_contents
= """
575 store-plaintext-passwords=yes
577 """ % (http_library_str
)
579 file_write(cfgfile_cfg
, config_contents
)
580 file_write(cfgfile_srv
, server_contents
)
582 def _with_config_dir(args
):
583 if '--config-dir' in args
:
586 return args
+ ('--config-dir', default_config_dir
)
588 def _with_auth(args
):
589 assert '--password' not in args
590 args
= args
+ ('--password', wc_passwd
,
592 if '--username' in args
:
595 return args
+ ('--username', wc_author
)
597 # For running subversion and returning the output
598 def run_svn(error_expected
, *varargs
):
599 """Run svn with VARARGS; return exit code as int; stdout, stderr as
600 lists of lines (including line terminators).
601 If ERROR_EXPECTED is None, any stderr also will be printed. If
602 you're just checking that something does/doesn't come out of
603 stdout/stderr, you might want to use actions.run_and_verify_svn()."""
604 return run_command(svn_binary
, error_expected
, 0,
605 *(_with_auth(_with_config_dir(varargs
))))
607 # For running svnadmin. Ignores the output.
608 def run_svnadmin(*varargs
):
609 """Run svnadmin with VARARGS, returns exit code as int; stdout, stderr as
610 list of lines (including line terminators)."""
611 return run_command(svnadmin_binary
, 1, 0, *varargs
)
613 # For running svnlook. Ignores the output.
614 def run_svnlook(*varargs
):
615 """Run svnlook with VARARGS, returns exit code as int; stdout, stderr as
616 list of lines (including line terminators)."""
617 return run_command(svnlook_binary
, 1, 0, *varargs
)
619 def run_svnsync(*varargs
):
620 """Run svnsync with VARARGS, returns exit code as int; stdout, stderr as
621 list of lines (including line terminators)."""
622 return run_command(svnsync_binary
, 1, 0, *(_with_config_dir(varargs
)))
624 def run_svnversion(*varargs
):
625 """Run svnversion with VARARGS, returns exit code as int; stdout, stderr
626 as list of lines (including line terminators)."""
627 return run_command(svnversion_binary
, 1, 0, *varargs
)
629 def run_entriesdump(path
):
630 """Run the entries-dump helper, returning a dict of Entry objects."""
631 # use spawn_process rather than run_command to avoid copying all the data
632 # to stdout in verbose mode.
633 exit_code
, stdout_lines
, stderr_lines
= spawn_process(entriesdump_binary
,
636 ### finish the CMD output
638 if exit_code
or stderr_lines
:
639 ### report on this? or continue to just skip it?
645 exec(''.join([line
for line
in stdout_lines
if not line
.startswith("DBG:")]))
649 # Chmod recursively on a whole subtree
650 def chmod_tree(path
, mode
, mask
):
651 for dirpath
, dirs
, files
in os
.walk(path
):
652 for name
in dirs
+ files
:
653 fullname
= os
.path
.join(dirpath
, name
)
654 if not os
.path
.islink(fullname
):
655 new_mode
= (os
.stat(fullname
)[stat
.ST_MODE
] & ~mask
) | mode
656 os
.chmod(fullname
, new_mode
)
658 # For clearing away working copies
659 def safe_rmtree(dirname
, retry
=0):
660 "Remove the tree at DIRNAME, making it writable first"
662 chmod_tree(dirname
, 0666, 0666)
663 shutil
.rmtree(dirname
)
665 if not os
.path
.exists(dirname
):
669 for delay
in (0.5, 1, 2, 4):
680 # For making local mods to files
681 def file_append(path
, new_text
):
682 "Append NEW_TEXT to file at PATH"
683 open(path
, 'a').write(new_text
)
685 # Append in binary mode
686 def file_append_binary(path
, new_text
):
687 "Append NEW_TEXT to file at PATH in binary mode"
688 open(path
, 'ab').write(new_text
)
690 # For creating new files, and making local mods to existing files.
691 def file_write(path
, contents
, mode
='w'):
692 """Write the CONTENTS to the file at PATH, opening file using MODE,
693 which is (w)rite by default."""
694 open(path
, mode
).write(contents
)
696 # For reading the contents of a file
697 def file_read(path
, mode
= 'r'):
698 """Return the contents of the file at PATH, opening file using MODE,
699 which is (r)ead by default."""
700 fp
= open(path
, mode
)
705 # For replacing parts of contents in an existing file, with new content.
706 def file_substitute(path
, contents
, new_contents
):
707 """Replace the CONTENTS in the file at PATH using the NEW_CONTENTS"""
711 fcontent
= fcontent
.replace(contents
, new_contents
)
716 # For creating blank new repositories
717 def create_repos(path
):
718 """Create a brand-new SVN repository at PATH. If PATH does not yet
721 if not os
.path
.exists(path
):
722 os
.makedirs(path
) # this creates all the intermediate dirs, if neccessary
724 opts
= ("--bdb-txn-nosync",)
725 if server_minor_version
< 5:
726 opts
+= ("--pre-1.5-compatible",)
727 elif server_minor_version
< 6:
728 opts
+= ("--pre-1.6-compatible",)
729 if fs_type
is not None:
730 opts
+= ("--fs-type=" + fs_type
,)
731 exit_code
, stdout
, stderr
= run_command(svnadmin_binary
, 1, 0, "create",
734 # Skip tests if we can't create the repository.
737 if line
.find('Unknown FS type') != -1:
739 # If the FS type is known, assume the repos couldn't be created
740 # (e.g. due to a missing 'svnadmin' binary).
741 raise SVNRepositoryCreateFailure("".join(stderr
).rstrip())
743 # Allow unauthenticated users to write to the repos, for ra_svn testing.
744 file_write(get_svnserve_conf_file_path(path
),
745 "[general]\nauth-access = write\n");
747 file_append(get_svnserve_conf_file_path(path
),
748 "realm = svntest\n[sasl]\nuse-sasl = true\n")
750 file_append(get_svnserve_conf_file_path(path
), "password-db = passwd\n")
751 file_append(os
.path
.join(path
, "conf", "passwd"),
752 "[users]\njrandom = rayjandom\njconstant = rayjandom\n");
754 if fs_type
is None or fs_type
== 'fsfs':
756 if config_file
is not None:
757 shutil
.copy(config_file
, get_fsfs_conf_file_path(path
))
760 if fsfs_sharding
is not None:
761 def transform_line(line
):
762 if line
.startswith('layout '):
763 if fsfs_sharding
> 0:
764 line
= 'layout sharded %d' % fsfs_sharding
766 line
= 'layout linear'
770 format_file_path
= get_fsfs_format_file_path(path
)
771 contents
= file_read(format_file_path
, 'rb')
774 new_contents
= "".join([transform_line(line
) + "\n"
775 for line
in contents
.split("\n")])
776 if new_contents
[-1] == "\n":
777 # we don't currently allow empty lines (\n\n) in the format file.
778 new_contents
= new_contents
[:-1]
781 os
.chmod(format_file_path
, 0666)
782 file_write(format_file_path
, new_contents
, 'wb')
785 # Note that some tests (currently only commit_tests) create their own
786 # post-commit hooks, which would override this one. :-(
789 abs_path
= os
.path
.abspath(path
)
790 create_python_hook_script(get_post_commit_hook_path(abs_path
),
791 "import subprocess\n"
794 "sys.exit(subprocess.Popen(command).wait())\n"
795 % repr([svnadmin_binary
, 'pack', abs_path
]))
797 # make the repos world-writeable, for mod_dav_svn's sake.
798 chmod_tree(path
, 0666, 0666)
800 # For copying a repository
801 def copy_repos(src_path
, dst_path
, head_revision
, ignore_uuid
= 1):
802 "Copy the repository SRC_PATH, with head revision HEAD_REVISION, to DST_PATH"
804 # Do an svnadmin dump|svnadmin load cycle. Print a fake pipe command so that
805 # the displayed CMDs can be run by hand
806 os
.environ
['SVN_DBG_QUIET'] = 'y'
807 create_repos(dst_path
)
808 dump_args
= ['dump', src_path
]
809 load_args
= ['load', dst_path
]
812 load_args
= load_args
+ ['--ignore-uuid']
814 sys
.stdout
.write('CMD: %s %s | %s %s\n' %
815 (os
.path
.basename(svnadmin_binary
), ' '.join(dump_args
),
816 os
.path
.basename(svnadmin_binary
), ' '.join(load_args
)))
820 dump_in
, dump_out
, dump_err
, dump_kid
= open_pipe(
821 [svnadmin_binary
] + dump_args
)
822 load_in
, load_out
, load_err
, load_kid
= open_pipe(
823 [svnadmin_binary
] + load_args
,
824 stdin
=dump_out
) # Attached to dump_kid
828 print('<TIME = %.6f>' % (stop
- start
))
830 load_stdout
, load_stderr
, load_exit_code
= wait_on_pipe(load_kid
, True)
831 dump_stdout
, dump_stderr
, dump_exit_code
= wait_on_pipe(dump_kid
, True)
836 #load_in is dump_out so it's already closed.
840 del os
.environ
['SVN_DBG_QUIET']
842 dump_re
= re
.compile(r
'^\* Dumped revision (\d+)\.\r?$')
844 for dump_line
in dump_stderr
:
845 match
= dump_re
.match(dump_line
)
846 if not match
or match
.group(1) != str(expect_revision
):
847 print('ERROR: dump failed: %s' % dump_line
.strip())
848 raise SVNRepositoryCopyFailure
850 if expect_revision
!= head_revision
+ 1:
851 print('ERROR: dump failed; did not see revision %s' % head_revision
)
852 raise SVNRepositoryCopyFailure
854 load_re
= re
.compile(r
'^------- Committed revision (\d+) >>>\r?$')
856 for load_line
in load_stdout
:
857 match
= load_re
.match(load_line
)
859 if match
.group(1) != str(expect_revision
):
860 print('ERROR: load failed: %s' % load_line
.strip())
861 raise SVNRepositoryCopyFailure
863 if expect_revision
!= head_revision
+ 1:
864 print('ERROR: load failed; did not see revision %s' % head_revision
)
865 raise SVNRepositoryCopyFailure
868 def canonicalize_url(input):
869 "Canonicalize the url, if the scheme is unknown, returns intact input"
871 m
= re
.match(r
"^((file://)|((svn|svn\+ssh|http|https)(://)))", input)
874 return scheme
+ re
.sub(r
'//*', '/', input[len(scheme
):])
879 def create_python_hook_script (hook_path
, hook_script_code
):
880 """Create a Python hook script at HOOK_PATH with the specified
884 # Use an absolute path since the working directory is not guaranteed
885 hook_path
= os
.path
.abspath(hook_path
)
886 # Fill the python file.
887 file_write ("%s.py" % hook_path
, hook_script_code
)
888 # Fill the batch wrapper file.
889 file_append ("%s.bat" % hook_path
,
890 "@\"%s\" %s.py %%*\n" % (sys
.executable
, hook_path
))
892 # For all other platforms
893 file_write (hook_path
, "#!%s\n%s" % (sys
.executable
, hook_script_code
))
894 os
.chmod (hook_path
, 0755)
896 def write_restrictive_svnserve_conf(repo_dir
, anon_access
="none"):
897 "Create a restrictive authz file ( no anynomous access )."
899 fp
= open(get_svnserve_conf_file_path(repo_dir
), 'w')
900 fp
.write("[general]\nanon-access = %s\nauth-access = write\n"
901 "authz-db = authz\n" % anon_access
)
903 fp
.write("realm = svntest\n[sasl]\nuse-sasl = true\n");
905 fp
.write("password-db = passwd\n")
908 # Warning: because mod_dav_svn uses one shared authz file for all
909 # repositories, you *cannot* use write_authz_file in any test that
910 # might be run in parallel.
912 # write_authz_file can *only* be used in test suites which disable
913 # parallel execution at the bottom like so
914 # if __name__ == '__main__':
915 # svntest.main.run_tests(test_list, serial_only = True)
916 def write_authz_file(sbox
, rules
, sections
=None):
917 """Write an authz file to SBOX, appropriate for the RA method used,
918 with authorizations rules RULES mapping paths to strings containing
919 the rules. You can add sections SECTIONS (ex. groups, aliases...) with
920 an appropriate list of mappings.
922 fp
= open(sbox
.authz_file
, 'w')
924 # When the sandbox repository is read only it's name will be different from
925 # the repository name.
926 repo_name
= sbox
.repo_dir
927 while repo_name
[-1] == '/':
928 repo_name
= repo_name
[:-1]
929 repo_name
= os
.path
.basename(repo_name
)
931 if sbox
.repo_url
.startswith("http"):
932 prefix
= repo_name
+ ":"
936 for p
, r
in sections
.items():
937 fp
.write("[%s]\n%s\n" % (p
, r
))
939 for p
, r
in rules
.items():
940 fp
.write("[%s%s]\n%s\n" % (prefix
, p
, r
))
943 def use_editor(func
):
944 os
.environ
['SVN_EDITOR'] = svneditor_script
945 os
.environ
['SVN_MERGE'] = svneditor_script
946 os
.environ
['SVNTEST_EDITOR_FUNC'] = func
947 os
.environ
['SVN_TEST_PYTHON'] = sys
.executable
949 def mergeinfo_notify_line(revstart
, revend
):
950 """Return an expected output line that describes the beginning of a
951 mergeinfo recording notification on revisions REVSTART through REVEND."""
954 revstart
= abs(revstart
)
955 return "--- Recording mergeinfo for reverse merge of r%ld .*:\n" \
958 return "--- Recording mergeinfo for merge of r%ld .*:\n" % (revstart
)
959 elif (revstart
< revend
):
960 return "--- Recording mergeinfo for merge of r%ld through r%ld .*:\n" \
963 return "--- Recording mergeinfo for reverse merge of r%ld through " \
964 "r%ld .*:\n" % (revstart
, revend
)
966 def merge_notify_line(revstart
=None, revend
=None, same_URL
=True,
968 """Return an expected output line that describes the beginning of a
969 merge operation on revisions REVSTART through REVEND. Omit both
970 REVSTART and REVEND for the case where the left and right sides of
971 the merge are from different URLs."""
972 from_foreign_phrase
= foreign
and "\(from foreign repository\) " or ""
974 return "--- Merging differences between %srepository URLs into '.+':\n" \
975 % (foreign
and "foreign " or "")
978 # The left and right sides of the merge are from different URLs.
979 return "--- Merging differences between %srepository URLs into '.+':\n" \
980 % (foreign
and "foreign " or "")
982 return "--- Reverse-merging %sr%ld into '.+':\n" \
983 % (from_foreign_phrase
, abs(revstart
))
985 return "--- Merging %sr%ld into '.+':\n" \
986 % (from_foreign_phrase
, revstart
)
988 if revstart
> revend
:
989 return "--- Reverse-merging %sr%ld through r%ld into '.+':\n" \
990 % (from_foreign_phrase
, revstart
, revend
)
992 return "--- Merging %sr%ld through r%ld into '.+':\n" \
993 % (from_foreign_phrase
, revstart
, revend
)
996 ######################################################################
997 # Functions which check the test configuration
998 # (useful for conditional XFails)
1000 def _check_command_line_parsed():
1001 """Raise an exception if the command line has not yet been parsed."""
1002 if not command_line_parsed
:
1003 raise Failure("Condition cannot be tested until command line is parsed")
1005 def is_ra_type_dav():
1006 _check_command_line_parsed()
1007 return test_area_url
.startswith('http')
1009 def is_ra_type_dav_neon():
1010 """Return True iff running tests over RA-Neon.
1011 CAUTION: Result is only valid if svn was built to support both."""
1012 _check_command_line_parsed()
1013 return test_area_url
.startswith('http') and \
1014 (preferred_http_library
== "neon")
1016 def is_ra_type_dav_serf():
1017 """Return True iff running tests over RA-Serf.
1018 CAUTION: Result is only valid if svn was built to support both."""
1019 _check_command_line_parsed()
1020 return test_area_url
.startswith('http') and \
1021 (preferred_http_library
== "serf")
1023 def is_ra_type_svn():
1024 """Return True iff running tests over RA-svn."""
1025 _check_command_line_parsed()
1026 return test_area_url
.startswith('svn')
1028 def is_ra_type_file():
1029 """Return True iff running tests over RA-local."""
1030 _check_command_line_parsed()
1031 return test_area_url
.startswith('file')
1033 def is_fs_type_fsfs():
1034 _check_command_line_parsed()
1035 # This assumes that fsfs is the default fs implementation.
1036 return fs_type
== 'fsfs' or fs_type
is None
1038 def is_os_windows():
1039 return os
.name
== 'nt'
1042 return os
.name
== 'posix'
1045 return sys
.platform
== 'darwin'
1047 def is_fs_case_insensitive():
1048 return (is_os_darwin() or is_os_windows())
1050 def server_has_mergeinfo():
1051 _check_command_line_parsed()
1052 return server_minor_version
>= 5
1054 def server_has_revprop_commit():
1055 _check_command_line_parsed()
1056 return server_minor_version
>= 5
1058 def server_sends_copyfrom_on_update():
1059 _check_command_line_parsed()
1060 return server_minor_version
>= 5
1062 def server_authz_has_aliases():
1063 _check_command_line_parsed()
1064 return server_minor_version
>= 5
1066 def server_gets_client_capabilities():
1067 _check_command_line_parsed()
1068 return server_minor_version
>= 5
1070 def server_has_partial_replay():
1071 _check_command_line_parsed()
1072 return server_minor_version
>= 5
1074 def server_enforces_date_syntax():
1075 _check_command_line_parsed()
1076 return server_minor_version
>= 5
1078 ######################################################################
1081 class TestSpawningThread(threading
.Thread
):
1082 """A thread that runs test cases in their own processes.
1083 Receives test numbers to run from the queue, and saves results into
1084 the results field."""
1085 def __init__(self
, queue
):
1086 threading
.Thread
.__init
__(self
)
1093 next_index
= self
.queue
.get_nowait()
1097 self
.run_one(next_index
)
1099 def run_one(self
, index
):
1100 command
= sys
.argv
[0]
1103 args
.append(str(index
))
1105 # add some startup arguments from this process
1107 args
.append('--fs-type=' + fs_type
)
1109 args
.append('--url=' + test_area_url
)
1113 args
.append('--cleanup')
1115 args
.append('--enable-sasl')
1116 if preferred_http_library
:
1117 args
.append('--http-library=' + preferred_http_library
)
1118 if server_minor_version
:
1119 args
.append('--server-minor-version=' + str(server_minor_version
))
1121 result
, stdout_lines
, stderr_lines
= spawn_process(command
, 1, None, *args
)
1122 self
.results
.append((index
, result
, stdout_lines
, stderr_lines
))
1125 sys
.stdout
.write('.')
1127 sys
.stdout
.write('F')
1132 """Encapsulate a single test case (predicate), including logic for
1133 runing the test and test list output."""
1135 def __init__(self
, func
, index
):
1136 self
.pred
= svntest
.testcase
.create_test_case(func
)
1140 if verbose_mode
and self
.pred
.inprogress
:
1141 print(" %2d %-5s %s [[%s]]" % (self
.index
,
1142 self
.pred
.list_mode(),
1143 self
.pred
.description
,
1144 self
.pred
.inprogress
))
1146 print(" %2d %-5s %s" % (self
.index
,
1147 self
.pred
.list_mode(),
1148 self
.pred
.description
))
1151 def get_function_name(self
):
1152 return self
.pred
.get_function_name()
1154 def _print_name(self
, prefix
):
1155 if self
.pred
.inprogress
:
1156 print("%s %s %s: %s [[WIMP: %s]]" % (prefix
,
1157 os
.path
.basename(sys
.argv
[0]),
1159 self
.pred
.description
,
1160 self
.pred
.inprogress
))
1162 print("%s %s %s: %s" % (prefix
,
1163 os
.path
.basename(sys
.argv
[0]),
1165 self
.pred
.description
))
1169 """Run self.pred and return the result. The return value is
1170 - 0 if the test was successful
1171 - 1 if it errored in a way that indicates test failure
1172 - 2 if the test skipped
1174 sbox_name
= self
.pred
.get_sandbox_name()
1176 sandbox
= svntest
.sandbox
.Sandbox(sbox_name
, self
.index
)
1180 # Explicitly set this so that commands that commit but don't supply a
1181 # log message will fail rather than invoke an editor.
1182 # Tests that want to use an editor should invoke svntest.main.use_editor.
1183 os
.environ
['SVN_EDITOR'] = ''
1184 os
.environ
['SVNTEST_EDITOR_FUNC'] = ''
1187 # Set this SVNKit specific variable to the current test (test name plus
1188 # its index) being run so that SVNKit daemon could use this test name
1189 # for its separate log file
1190 os
.environ
['SVN_CURRENT_TEST'] = os
.path
.basename(sys
.argv
[0]) + "_" + \
1193 svntest
.actions
.no_sleep_for_timestamps()
1195 saved_dir
= os
.getcwd()
1197 rc
= self
.pred
.run(sandbox
)
1199 self
._print
_name
('STYLE ERROR in')
1200 print('Test driver returned a status code.')
1202 result
= svntest
.testcase
.RESULT_OK
1204 result
= svntest
.testcase
.RESULT_SKIP
1206 result
= svntest
.testcase
.RESULT_FAIL
1207 # We captured Failure and its subclasses. We don't want to print
1208 # anything for plain old Failure since that just indicates test
1209 # failure, rather than relevant information. However, if there
1210 # *is* information in the exception's arguments, then print it.
1211 if ex
.__class
__ != Failure
or ex
.args
:
1214 print('EXCEPTION: %s: %s' % (ex
.__class
__.__name
__, ex_args
))
1216 print('EXCEPTION: %s' % ex
.__class
__.__name
__)
1217 traceback
.print_exc(file=sys
.stdout
)
1219 except KeyboardInterrupt:
1220 print('Interrupted')
1222 except SystemExit, ex
:
1223 print('EXCEPTION: SystemExit(%d), skipping cleanup' % ex
.code
)
1224 self
._print
_name
(ex
.code
and 'FAIL: ' or 'PASS: ')
1227 result
= svntest
.testcase
.RESULT_FAIL
1228 print('UNEXPECTED EXCEPTION:')
1229 traceback
.print_exc(file=sys
.stdout
)
1233 exit_code
, result_text
, result_benignity
= self
.pred
.results(result
)
1234 if not (quiet_mode
and result_benignity
):
1235 self
._print
_name
(result_text
)
1236 if sandbox
is not None and exit_code
!= 1 and cleanup_mode
:
1237 sandbox
.cleanup_test_paths()
1240 ######################################################################
1241 # Main testing functions
1243 # These two functions each take a TEST_LIST as input. The TEST_LIST
1244 # should be a list of test functions; each test function should take
1245 # no arguments and return a 0 on success, non-zero on failure.
1246 # Ideally, each test should also have a short, one-line docstring (so
1247 # it can be displayed by the 'list' command.)
1249 # Func to run one test in the list.
1250 def run_one_test(n
, test_list
, finished_tests
= None):
1251 """Run the Nth client test in TEST_LIST, return the result.
1253 If we're running the tests in parallel spawn the test in a new process.
1256 if (n
< 1) or (n
> len(test_list
) - 1):
1257 print("There is no test %s.\n" % n
)
1261 exit_code
= TestRunner(test_list
[n
], n
).run()
1264 def _internal_run_tests(test_list
, testnums
, parallel
):
1265 """Run the tests from TEST_LIST whose indices are listed in TESTNUMS.
1267 If we're running the tests in parallel spawn as much parallel processes
1268 as requested and gather the results in a temp. buffer when a child
1269 process is finished.
1277 for testnum
in testnums
:
1278 if run_one_test(testnum
, test_list
) == 1:
1281 number_queue
= queue
.Queue()
1282 for num
in testnums
:
1283 number_queue
.put(num
)
1285 threads
= [ TestSpawningThread(number_queue
) for i
in range(parallel
) ]
1292 # list of (index, result, stdout, stderr)
1295 results
+= t
.results
1298 # terminate the line of dots
1301 # all tests are finished, find out the result and print the logs.
1302 for (index
, result
, stdout_lines
, stderr_lines
) in results
:
1304 for line
in stdout_lines
:
1305 sys
.stdout
.write(line
)
1307 for line
in stderr_lines
:
1308 sys
.stdout
.write(line
)
1312 svntest
.sandbox
.cleanup_deferred_test_paths()
1317 prog_name
= os
.path
.basename(sys
.argv
[0])
1318 print("%s [--url] [--fs-type] [--verbose|--quiet] [--parallel] \\" %
1320 print("%s [--enable-sasl] [--cleanup] [--bin] [<test> ...]"
1321 % (" " * len(prog_name
)))
1322 print("%s " % (" " * len(prog_name
)))
1323 print("%s [--list] [<test> ...]\n" % prog_name
)
1325 print(" <test> The number of the test to run, or a range of test\n"
1326 " numbers, like 10:12 or 10-12. Multiple numbers and\n"
1327 " ranges are ok. If you supply none, all tests are run.\n"
1328 " You can also pass the name of a test function to run.\n")
1330 print(" --list Print test doc strings instead of running them")
1331 print(" --fs-type Subversion file system type (fsfs or bdb)")
1332 print(" --http-library Make svn use this DAV library (neon or serf) if\n"
1333 " it supports both, else assume it's using this one;\n"
1334 " the default is neon")
1335 print(" --url Base url to the repos (e.g. svn://localhost)")
1336 print(" --verbose Print binary command-lines (not with --quiet)")
1337 print(" --quiet Print only unexpected results (not with --verbose)")
1338 print(" --cleanup Whether to clean up")
1339 print(" --enable-sasl Whether to enable SASL authentication")
1340 print(" --parallel Run the tests in parallel")
1341 print(" --bin Use the svn binaries installed in this path")
1342 print(" --use-jsvn Use the jsvn (SVNKit based) binaries. Can be\n"
1343 " combined with --bin to point to a specific path")
1344 print(" --development Test development mode: provides more detailed test\n"
1345 " output and ignores all exceptions in the \n"
1346 " run_and_verify* functions. This option is only \n"
1347 " useful during test development!")
1348 print(" --server-minor-version Set the minor version for the server.\n"
1349 " Supports version 4 or 5.")
1350 print(" --fsfs-sharding Default shard size (for fsfs)\n"
1351 " --fsfs-packing Run 'svnadmin pack' automatically")
1352 print(" --config-file Configuration file for tests.")
1353 print(" --keep-local-tmp Don't remove svn-test-work/local_tmp after test\n"
1354 " run is complete. Useful for debugging failures.")
1355 print(" --help This information")
1358 # Main func. This is the "entry point" that all the test scripts call
1359 # to run their list of tests.
1361 # This routine parses sys.argv to decide what to do.
1362 def run_tests(test_list
, serial_only
= False):
1363 """Main routine to run all tests in TEST_LIST.
1365 NOTE: this function does not return. It does a sys.exit() with the
1366 appropriate exit code.
1369 global test_area_url
1376 global is_child_process
1378 global svnadmin_binary
1379 global svnlook_binary
1380 global svnsync_binary
1381 global svndumpfilter_binary
1382 global svnversion_binary
1383 global command_line_parsed
1384 global preferred_http_library
1385 global fsfs_sharding
1388 global server_minor_version
1392 # Should the tests be listed (as opposed to executed)?
1398 keep_local_tmp
= False
1402 opts
, args
= my_getopt(sys
.argv
[1:], 'vqhpc',
1403 ['url=', 'fs-type=', 'verbose', 'quiet', 'cleanup',
1404 'list', 'enable-sasl', 'help', 'parallel',
1405 'bin=', 'http-library=', 'server-minor-version=',
1406 'fsfs-packing', 'fsfs-sharding=',
1407 'use-jsvn', 'development', 'keep-local-tmp',
1409 except getopt
.GetoptError
, e
:
1410 print("ERROR: %s\n" % e
)
1416 # This is an old deprecated variant of the "--list" option:
1418 elif arg
.startswith('BASE_URL='):
1419 test_area_url
= arg
[9:]
1423 testnums
.append(int(arg
))
1426 # Do nothing for now.
1431 # Check if the argument is a range
1432 numberstrings
= arg
.split(':');
1433 if len(numberstrings
) != 2:
1434 numberstrings
= arg
.split('-');
1435 if len(numberstrings
) != 2:
1437 left
= int(numberstrings
[0])
1438 right
= int(numberstrings
[1])
1442 for nr
in range(left
,right
+1):
1451 # Check if the argument is a function name, and translate
1452 # it to a number if possible
1453 for testnum
in list(range(1, len(test_list
))):
1454 test_case
= TestRunner(test_list
[testnum
], testnum
)
1455 if test_case
.get_function_name() == str(arg
):
1456 testnums
.append(testnum
)
1463 print("ERROR: invalid test number, range of numbers, " +
1464 "or function '%s'\n" % arg
)
1468 for opt
, val
in opts
:
1472 elif opt
== "--fs-type":
1475 elif opt
== "-v" or opt
== "--verbose":
1478 elif opt
== "-q" or opt
== "--quiet":
1481 elif opt
== "--cleanup":
1484 elif opt
== "--list":
1487 elif opt
== "--enable-sasl":
1490 elif opt
== "-h" or opt
== "--help":
1494 elif opt
== '-p' or opt
== "--parallel":
1495 parallel
= 5 # use 5 parallel threads.
1498 is_child_process
= True
1500 elif opt
== '--bin':
1503 elif opt
== '--http-library':
1504 preferred_http_library
= val
1506 elif opt
== '--fsfs-sharding':
1507 fsfs_sharding
= int(val
)
1508 elif opt
== '--fsfs-packing':
1511 elif opt
== '--server-minor-version':
1512 server_minor_version
= int(val
)
1513 if server_minor_version
< 4 or server_minor_version
> 7:
1514 print("ERROR: test harness only supports server minor versions 4-6")
1517 elif opt
== '--use-jsvn':
1520 elif opt
== '--keep-local-tmp':
1521 keep_local_tmp
= True
1523 elif opt
== '--development':
1524 setup_development_mode()
1526 elif opt
== '--config-file':
1529 if fsfs_packing
is not None and fsfs_sharding
is None:
1530 raise Exception('--fsfs-packing requires --fsfs-sharding')
1532 if test_area_url
[-1:] == '/': # Normalize url to have no trailing slash
1533 test_area_url
= test_area_url
[:-1]
1535 if verbose_mode
and quiet_mode
:
1536 sys
.stderr
.write("ERROR: 'verbose' and 'quiet' are incompatible\n")
1539 # Calculate pristine_url from test_area_url.
1540 pristine_url
= test_area_url
+ '/' + pathname2url(pristine_dir
)
1545 svn_binary
= os
.path
.join(svn_bin
, 'jsvn' + _bat
)
1546 svnadmin_binary
= os
.path
.join(svn_bin
, 'jsvnadmin' + _bat
)
1547 svnlook_binary
= os
.path
.join(svn_bin
, 'jsvnlook' + _bat
)
1548 svnsync_binary
= os
.path
.join(svn_bin
, 'jsvnsync' + _bat
)
1549 svndumpfilter_binary
= os
.path
.join(svn_bin
, 'jsvndumpfilter' + _bat
)
1550 svnversion_binary
= os
.path
.join(svn_bin
, 'jsvnversion' + _bat
)
1553 svn_binary
= os
.path
.join(svn_bin
, 'svn' + _exe
)
1554 svnadmin_binary
= os
.path
.join(svn_bin
, 'svnadmin' + _exe
)
1555 svnlook_binary
= os
.path
.join(svn_bin
, 'svnlook' + _exe
)
1556 svnsync_binary
= os
.path
.join(svn_bin
, 'svnsync' + _exe
)
1557 svndumpfilter_binary
= os
.path
.join(svn_bin
, 'svndumpfilter' + _exe
)
1558 svnversion_binary
= os
.path
.join(svn_bin
, 'svnversion' + _exe
)
1560 command_line_parsed
= True
1562 ######################################################################
1564 # Cleanup: if a previous run crashed or interrupted the python
1565 # interpreter, then `temp_dir' was never removed. This can cause wonkiness.
1566 if not is_child_process
:
1567 safe_rmtree(temp_dir
, 1)
1570 # If no test numbers were listed explicitly, include all of them:
1571 testnums
= list(range(1, len(test_list
)))
1574 print("Test # Mode Test Description")
1575 print("------ ----- ----------------")
1576 for testnum
in testnums
:
1577 TestRunner(test_list
[testnum
], testnum
).list()
1579 # done. just exit with success.
1582 # don't run tests in parallel when the tests don't support it or there
1583 # are only a few tests to run.
1584 if serial_only
or len(testnums
) < 2:
1587 # Build out the default configuration directory
1588 create_config_dir(default_config_dir
)
1590 # Setup the pristine repository
1591 svntest
.actions
.setup_pristine_repository()
1594 exit_code
= _internal_run_tests(test_list
, testnums
, parallel
)
1596 # Remove all scratchwork: the 'pristine' repository, greek tree, etc.
1597 # This ensures that an 'import' will happen the next time we run.
1598 if not is_child_process
and not keep_local_tmp
:
1599 safe_rmtree(temp_dir
, 1)
1601 # Cleanup after ourselves.
1602 svntest
.sandbox
.cleanup_deferred_test_paths()
1604 # Return the appropriate exit code from the tests.