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 # Copyright (c) 2000-2008 CollabNet. All rights reserved.
10 # This software is licensed as described in the file COPYING, which
11 # you should have received as part of this distribution. The terms
12 # are also available at http://subversion.tigris.org/license-1.html.
13 # If newer versions of this license are posted there, you may use a
14 # newer version instead, at your option.
16 ######################################################################
18 import sys
# for argv[]
20 import shutil
# for rmtree()
22 import stat
# for ST_MODE
23 import copy
# for deepcopy()
24 import time
# for time()
25 import traceback
# for print_exc()
32 my_getopt
= getopt
.gnu_getopt
33 except AttributeError:
34 my_getopt
= getopt
.getopt
36 from svntest
import Failure
37 from svntest
import Skip
38 from svntest
import testcase
39 from svntest
import wc
41 ######################################################################
43 # HOW TO USE THIS MODULE:
45 # Write a new python script that
47 # 1) imports this 'svntest' package
49 # 2) contains a number of related 'test' routines. (Each test
50 # routine should take no arguments, and return None on success
51 # or throw a Failure exception on failure. Each test should
52 # also contain a short docstring.)
54 # 3) places all the tests into a list that begins with None.
56 # 4) calls svntest.main.client_test() on the list.
58 # Also, your tests will probably want to use some of the common
59 # routines in the 'Utilities' section below.
61 #####################################################################
64 class SVNProcessTerminatedBySignal(Failure
):
65 "Exception raised if a spawned process segfaulted, aborted, etc."
68 class SVNLineUnequal(Failure
):
69 "Exception raised if two lines are unequal"
72 class SVNUnmatchedError(Failure
):
73 "Exception raised if an expected error is not found"
76 class SVNCommitFailure(Failure
):
77 "Exception raised if a commit failed"
80 class SVNRepositoryCopyFailure(Failure
):
81 "Exception raised if unable to copy a repository"
84 class SVNRepositoryCreateFailure(Failure
):
85 "Exception raised if unable to create a repository"
89 if sys
.platform
== 'win32':
91 file_scheme_prefix
= 'file:///'
96 file_scheme_prefix
= 'file://'
101 from popen2
import Popen3
102 platform_with_popen3_class
= True
104 platform_with_popen3_class
= False
106 # The location of our mock svneditor script.
107 if sys
.platform
== 'win32':
108 svneditor_script
= os
.path
.join(sys
.path
[0], 'svneditor.bat')
110 svneditor_script
= os
.path
.join(sys
.path
[0], 'svneditor.py')
112 # Username and password used by the working copies
113 wc_author
= 'jrandom'
114 wc_passwd
= 'rayjandom'
116 # Username and password used by the working copies for "second user"
118 wc_author2
= 'jconstant' # use the same password as wc_author
120 # Set C locale for command line programs
121 os
.environ
['LC_ALL'] = 'C'
123 # This function mimics the Python 2.3 urllib function of the same name.
124 def pathname2url(path
):
125 """Convert the pathname PATH from the local syntax for a path to the form
126 used in the path component of a URL. This does not produce a complete URL.
127 The return value will already be quoted using the quote() function."""
128 return urllib
.quote(path
.replace('\\', '/'))
130 # This function mimics the Python 2.3 urllib function of the same name.
131 def url2pathname(path
):
132 """Convert the path component PATH from an encoded URL to the local syntax
133 for a path. This does not accept a complete URL. This function uses
134 unquote() to decode PATH."""
135 return os
.path
.normpath(urllib
.unquote(path
))
137 ######################################################################
138 # Global variables set during option parsing. These should not be used
139 # until the variable command_line_parsed has been set to True, as is
140 # done in run_tests below.
141 command_line_parsed
= False
143 # The locations of the svn, svnadmin and svnlook binaries, relative to
144 # the only scripts that import this file right now (they live in ../).
145 # Use --bin to override these defaults.
146 svn_binary
= os
.path
.abspath('../../svn/svn' + _exe
)
147 svnadmin_binary
= os
.path
.abspath('../../svnadmin/svnadmin' + _exe
)
148 svnlook_binary
= os
.path
.abspath('../../svnlook/svnlook' + _exe
)
149 svnsync_binary
= os
.path
.abspath('../../svnsync/svnsync' + _exe
)
150 svnversion_binary
= os
.path
.abspath('../../svnversion/svnversion' + _exe
)
151 svndumpfilter_binary
= os
.path
.abspath('../../svndumpfilter/svndumpfilter' + \
154 # Global variable indicating if we want verbose output, that is,
155 # details of what commands each test does as it does them. This is
156 # incompatible with quiet_mode.
159 # Global variable indicating if we want quiet output, that is, don't
160 # show PASS, XFAIL, or SKIP notices, but do show FAIL and XPASS. This
161 # is incompatible with verbose_mode.
164 # Global variable indicating if we want test data cleaned up after success
167 # Global variable indicating if svnserve should use Cyrus SASL
170 # Global variable indicating that SVNKit binaries should be used
173 # Global variable indicating which DAV library, if any, is in use
177 # Configuration file (copied into FSFS fsfs.conf).
180 # Global variable indicating what the minor version of the server
181 # tested against is (4 for 1.4.x, for example).
182 server_minor_version
= 5
184 # Global variable indicating if this is a child process and no cleanup
185 # of global directories is needed.
186 is_child_process
= False
188 # Global URL to testing area. Default to ra_local, current working dir.
189 test_area_url
= file_scheme_prefix
+ pathname2url(os
.path
.abspath(os
.getcwd()))
191 # Location to the pristine repository, will be calculated from test_area_url
192 # when we know what the user specified for --url.
195 # Global variable indicating the FS type for repository creations.
198 # End of command-line-set global variables.
199 ######################################################################
201 # All temporary repositories and working copies are created underneath
202 # this dir, so there's one point at which to mount, e.g., a ramdisk.
203 work_dir
= "svn-test-work"
205 # Constant for the merge info property.
206 SVN_PROP_MERGEINFO
= "svn:mergeinfo"
208 # Where we want all the repositories and working copies to live.
209 # Each test will have its own!
210 general_repo_dir
= os
.path
.join(work_dir
, "repositories")
211 general_wc_dir
= os
.path
.join(work_dir
, "working_copies")
213 # temp directory in which we will create our 'pristine' local
214 # repository and other scratch data. This should be removed when we
215 # quit and when we startup.
216 temp_dir
= os
.path
.join(work_dir
, 'local_tmp')
218 # (derivatives of the tmp dir.)
219 pristine_dir
= os
.path
.join(temp_dir
, "repos")
220 greek_dump_dir
= os
.path
.join(temp_dir
, "greekfiles")
221 default_config_dir
= os
.path
.abspath(os
.path
.join(temp_dir
, "config"))
224 # Our pristine greek-tree state.
226 # If a test wishes to create an "expected" working-copy tree, it should
227 # call main.greek_state.copy(). That method will return a copy of this
228 # State object which can then be edited.
231 greek_state
= wc
.State('', {
232 'iota' : _item("This is the file 'iota'.\n"),
234 'A/mu' : _item("This is the file 'mu'.\n"),
236 'A/B/lambda' : _item("This is the file 'lambda'.\n"),
238 'A/B/E/alpha' : _item("This is the file 'alpha'.\n"),
239 'A/B/E/beta' : _item("This is the file 'beta'.\n"),
243 'A/D/gamma' : _item("This is the file 'gamma'.\n"),
245 'A/D/G/pi' : _item("This is the file 'pi'.\n"),
246 'A/D/G/rho' : _item("This is the file 'rho'.\n"),
247 'A/D/G/tau' : _item("This is the file 'tau'.\n"),
249 'A/D/H/chi' : _item("This is the file 'chi'.\n"),
250 'A/D/H/psi' : _item("This is the file 'psi'.\n"),
251 'A/D/H/omega' : _item("This is the file 'omega'.\n"),
255 ######################################################################
256 # Utilities shared by the tests
258 "Wrap a function, catch, print and ignore exceptions"
259 def w(*args
, **kwds
):
261 return func(*args
, **kwds
)
263 if ex
.__class
__ != Failure
or ex
.args
:
266 print('EXCEPTION: %s: %s' % (ex
.__class
__.__name
__, ex_args
))
268 print('EXCEPTION: %s' % ex
.__class
__.__name
__)
271 def setup_development_mode():
272 "Wraps functions in module actions"
273 l
= [ 'run_and_verify_svn',
274 'run_and_verify_svnversion',
275 'run_and_verify_load',
276 'run_and_verify_dump',
277 'run_and_verify_checkout',
278 'run_and_verify_export',
279 'run_and_verify_update',
280 'run_and_verify_merge',
281 'run_and_verify_merge2',
282 'run_and_verify_switch',
283 'run_and_verify_commit',
284 'run_and_verify_unquiet_status',
285 'run_and_verify_status',
286 'run_and_verify_diff_summarize',
287 'run_and_verify_diff_summarize_xml',
288 'run_and_validate_lock']
291 setattr(actions
, func
, wrap_ex(getattr(actions
, func
)))
293 def get_admin_name():
294 "Return name of SVN administrative subdirectory."
296 if (windows
or sys
.platform
== 'cygwin') \
297 and 'SVN_ASP_DOT_NET_HACK' in os
.environ
:
302 def get_start_commit_hook_path(repo_dir
):
303 "Return the path of the start-commit-hook conf file in REPO_DIR."
305 return os
.path
.join(repo_dir
, "hooks", "start-commit")
308 def get_pre_commit_hook_path(repo_dir
):
309 "Return the path of the pre-commit-hook conf file in REPO_DIR."
311 return os
.path
.join(repo_dir
, "hooks", "pre-commit")
314 def get_post_commit_hook_path(repo_dir
):
315 "Return the path of the post-commit-hook conf file in REPO_DIR."
317 return os
.path
.join(repo_dir
, "hooks", "post-commit")
319 def get_pre_revprop_change_hook_path(repo_dir
):
320 "Return the path of the pre-revprop-change hook script in REPO_DIR."
322 return os
.path
.join(repo_dir
, "hooks", "pre-revprop-change")
324 def get_svnserve_conf_file_path(repo_dir
):
325 "Return the path of the svnserve.conf file in REPO_DIR."
327 return os
.path
.join(repo_dir
, "conf", "svnserve.conf")
329 def get_fsfs_conf_file_path(repo_dir
):
330 "Return the path of the fsfs.conf file in REPO_DIR."
332 return os
.path
.join(repo_dir
, "db", "fsfs.conf")
334 # Run any binary, logging the command line and return code
335 def run_command(command
, error_expected
, binary_mode
=0, *varargs
):
336 """Run COMMAND with VARARGS; return exit code as int; stdout, stderr
338 If ERROR_EXPECTED is None, any stderr also will be printed."""
340 return run_command_stdin(command
, error_expected
, binary_mode
,
343 # A regular expression that matches arguments that are trivially safe
344 # to pass on a command line without quoting on any supported operating
346 _safe_arg_re
= re
.compile(r
'^[A-Za-z\d\.\_\/\-\:\@]+$')
349 """Quote ARG for a command line.
351 Simply surround every argument in double-quotes unless it contains
352 only universally harmless characters.
354 WARNING: This function cannot handle arbitrary command-line
355 arguments. It can easily be confused by shell metacharacters. A
356 perfect job would be difficult and OS-dependent (see, for example,
357 http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp).
358 In other words, this function is just good enough for what we need
362 if _safe_arg_re
.match(arg
):
366 arg
= arg
.replace('$', '\$')
367 return '"%s"' % (arg
,)
369 def open_pipe(command
, mode
):
370 """Opens a popen3 pipe to COMMAND in MODE.
372 Returns (infile, outfile, errfile, waiter); waiter
373 should be passed to wait_on_pipe."""
374 if platform_with_popen3_class
:
375 kid
= Popen3(command
, True)
376 return kid
.tochild
, kid
.fromchild
, kid
.childerr
, (kid
, command
)
378 inf
, outf
, errf
= os
.popen3(command
, mode
)
379 return inf
, outf
, errf
, None
381 def wait_on_pipe(waiter
, stdout_lines
, stderr_lines
):
382 """Waits for KID (opened with open_pipe) to finish, dying
383 if it does. Uses STDOUT_LINES and STDERR_LINES for error message
384 if kid fails. Returns kid's exit code."""
388 kid
, command
= waiter
390 wait_code
= kid
.wait()
392 if os
.WIFSIGNALED(wait_code
):
393 exit_signal
= os
.WTERMSIG(wait_code
)
394 if stdout_lines
is not None:
395 sys
.stdout
.write("".join(stdout_lines
))
396 if stderr_lines
is not None:
397 sys
.stderr
.write("".join(stderr_lines
))
399 # show the whole path to make it easier to start a debugger
400 sys
.stderr
.write("CMD: %s terminated by signal %d\n"
401 % (command
, exit_signal
))
402 raise SVNProcessTerminatedBySignal
404 exit_code
= os
.WEXITSTATUS(wait_code
)
405 if exit_code
and verbose_mode
:
406 sys
.stderr
.write("CMD: %s exited with %d\n" % (command
, exit_code
))
409 # Run any binary, supplying input text, logging the command line
410 def spawn_process(command
, binary_mode
=0,stdin_lines
=None, *varargs
):
411 args
= ' '.join(map(_quote_arg
, varargs
))
413 # Log the command line
414 if verbose_mode
and not command
.endswith('.py'):
415 sys
.stdout
.write('CMD: %s %s ' % (os
.path
.basename(command
), args
))
423 infile
, outfile
, errfile
, kid
= open_pipe(command
+ ' ' + args
, mode
)
426 for x
in stdin_lines
:
431 stdout_lines
= outfile
.readlines()
432 stderr_lines
= errfile
.readlines()
437 exit_code
= wait_on_pipe(kid
, stdout_lines
, stderr_lines
)
439 return exit_code
, stdout_lines
, stderr_lines
441 def run_command_stdin(command
, error_expected
, binary_mode
=0,
442 stdin_lines
=None, *varargs
):
443 """Run COMMAND with VARARGS; input STDIN_LINES (a list of strings
444 which should include newline characters) to program via stdin - this
445 should not be very large, as if the program outputs more than the OS
446 is willing to buffer, this will deadlock, with both Python and
447 COMMAND waiting to write to each other for ever.
448 Return exit code as int; stdout, stderr as lists of lines.
449 If ERROR_EXPECTED is None, any stderr also will be printed."""
454 exit_code
, stdout_lines
, stderr_lines
= spawn_process(command
,
461 print('<TIME = %.6f>' % (stop
- start
))
462 for x
in stdout_lines
:
464 for x
in stderr_lines
:
467 if (not error_expected
) and (stderr_lines
):
469 for x
in stderr_lines
:
473 return exit_code
, stdout_lines
, stderr_lines
475 def create_config_dir(cfgdir
, config_contents
=None, server_contents
=None):
476 "Create config directories and files"
479 cfgfile_cfg
= os
.path
.join(cfgdir
, 'config')
480 cfgfile_srv
= os
.path
.join(cfgdir
, 'servers')
482 # create the directory
483 if not os
.path
.isdir(cfgdir
):
486 # define default config file contents if none provided
487 if config_contents
is None:
488 config_contents
= """
491 interactive-conflicts = false
494 # define default server file contents if none provided
495 if server_contents
is None:
496 http_library_str
= ""
498 http_library_str
= "http-library=%s" % (http_library
)
499 server_contents
= """
503 store-plaintext-passwords=yes
505 """ % (http_library_str
)
507 file_write(cfgfile_cfg
, config_contents
)
508 file_write(cfgfile_srv
, server_contents
)
510 def _with_config_dir(args
):
511 if '--config-dir' in args
:
514 return args
+ ('--config-dir', default_config_dir
)
516 def _with_auth(args
):
517 assert '--password' not in args
518 args
= args
+ ('--password', wc_passwd
,
520 if '--username' in args
:
523 return args
+ ('--username', wc_author
)
525 # For running subversion and returning the output
526 def run_svn(error_expected
, *varargs
):
527 """Run svn with VARARGS; return exit code as int; stdout, stderr as
529 If ERROR_EXPECTED is None, any stderr also will be printed. If
530 you're just checking that something does/doesn't come out of
531 stdout/stderr, you might want to use actions.run_and_verify_svn()."""
532 return run_command(svn_binary
, error_expected
, 0,
533 *(_with_auth(_with_config_dir(varargs
))))
535 # For running svnadmin. Ignores the output.
536 def run_svnadmin(*varargs
):
537 """Run svnadmin with VARARGS, returns exit code as int; stdout, stderr as
539 return run_command(svnadmin_binary
, 1, 0, *varargs
)
541 # For running svnlook. Ignores the output.
542 def run_svnlook(*varargs
):
543 """Run svnlook with VARARGS, returns exit code as int; stdout, stderr as
545 return run_command(svnlook_binary
, 1, 0, *varargs
)
547 def run_svnsync(*varargs
):
548 """Run svnsync with VARARGS, returns exit code as int; stdout, stderr as
550 return run_command(svnsync_binary
, 1, 0, *(_with_config_dir(varargs
)))
552 def run_svnversion(*varargs
):
553 """Run svnversion with VARARGS, returns exit code as int; stdout, stderr
555 return run_command(svnversion_binary
, 1, 0, *varargs
)
557 # Chmod recursively on a whole subtree
558 def chmod_tree(path
, mode
, mask
):
559 def visit(arg
, dirname
, names
):
562 fullname
= os
.path
.join(dirname
, name
)
563 if not os
.path
.islink(fullname
):
564 new_mode
= (os
.stat(fullname
)[stat
.ST_MODE
] & ~mask
) | mode
565 os
.chmod(fullname
, new_mode
)
566 os
.path
.walk(path
, visit
, (mode
, mask
))
568 # For clearing away working copies
569 def safe_rmtree(dirname
, retry
=0):
570 "Remove the tree at DIRNAME, making it writable first"
572 chmod_tree(dirname
, 0666, 0666)
573 shutil
.rmtree(dirname
)
575 if not os
.path
.exists(dirname
):
579 for delay
in (0.5, 1, 2, 4):
590 # For making local mods to files
591 def file_append(path
, new_text
):
592 "Append NEW_TEXT to file at PATH"
593 file_write(path
, new_text
, 'a') # open in (a)ppend mode
595 # Append in binary mode
596 def file_append_binary(path
, new_text
):
597 "Append NEW_TEXT to file at PATH in binary mode"
598 file_write(path
, new_text
, 'ab') # open in (a)ppend mode
600 # For creating new files, and making local mods to existing files.
601 def file_write(path
, contents
, mode
= 'w'):
602 """Write the CONTENTS to the file at PATH, opening file using MODE,
603 which is (w)rite by default."""
604 fp
= open(path
, mode
)
608 # For reading the contents of a file
609 def file_read(path
, mode
= 'r'):
610 """Return the contents of the file at PATH, opening file using MODE,
611 which is (r)ead by default."""
612 fp
= open(path
, mode
)
617 # For replacing parts of contents in an existing file, with new content.
618 def file_substitute(path
, contents
, new_contents
):
619 """Replace the CONTENTS in the file at PATH using the NEW_CONTENTS"""
623 fcontent
= fcontent
.replace(contents
, new_contents
)
628 # For creating blank new repositories
629 def create_repos(path
):
630 """Create a brand-new SVN repository at PATH. If PATH does not yet
633 if not os
.path
.exists(path
):
634 os
.makedirs(path
) # this creates all the intermediate dirs, if neccessary
636 opts
= ("--bdb-txn-nosync",)
637 if server_minor_version
< 5:
638 opts
+= ("--pre-1.5-compatible",)
639 if fs_type
is not None:
640 opts
+= ("--fs-type=" + fs_type
,)
641 exit_code
, stdout
, stderr
= run_command(svnadmin_binary
, 1, 0, "create",
644 # Skip tests if we can't create the repository.
647 if line
.find('Unknown FS type') != -1:
649 # If the FS type is known, assume the repos couldn't be created
650 # (e.g. due to a missing 'svnadmin' binary).
651 raise SVNRepositoryCreateFailure("".join(stderr
).rstrip())
653 # Allow unauthenticated users to write to the repos, for ra_svn testing.
654 file_write(get_svnserve_conf_file_path(path
),
655 "[general]\nauth-access = write\n");
657 file_append(get_svnserve_conf_file_path(path
),
658 "realm = svntest\n[sasl]\nuse-sasl = true\n")
660 file_append(get_svnserve_conf_file_path(path
), "password-db = passwd\n")
661 file_append(os
.path
.join(path
, "conf", "passwd"),
662 "[users]\njrandom = rayjandom\njconstant = rayjandom\n");
664 if config_file
is not None and (fs_type
is None or fs_type
== 'fsfs'):
665 shutil
.copy(config_file
, get_fsfs_conf_file_path(path
))
667 # make the repos world-writeable, for mod_dav_svn's sake.
668 chmod_tree(path
, 0666, 0666)
670 # For copying a repository
671 def copy_repos(src_path
, dst_path
, head_revision
, ignore_uuid
= 1):
672 "Copy the repository SRC_PATH, with head revision HEAD_REVISION, to DST_PATH"
674 # Do an svnadmin dump|svnadmin load cycle. Print a fake pipe command so that
675 # the displayed CMDs can be run by hand
676 create_repos(dst_path
)
677 dump_args
= ' dump "' + src_path
+ '"'
678 load_args
= ' load "' + dst_path
+ '"'
681 load_args
= load_args
+ " --ignore-uuid"
683 sys
.stdout
.write('CMD: %s%s | %s%s ' % (os
.path
.basename(svnadmin_binary
), \
684 dump_args
, os
.path
.basename(svnadmin_binary
), load_args
))
688 dump_in
, dump_out
, dump_err
, dump_kid
= \
689 open_pipe(svnadmin_binary
+ dump_args
, 'b')
691 load_in
, load_out
, load_err
, load_kid
= \
692 open_pipe(svnadmin_binary
+ load_args
, 'b')
695 print('<TIME = %.6f>' % (stop
- start
))
698 data
= dump_out
.read(1024*1024) # Arbitrary buffer size
702 load_in
.close() # Tell load we are done
704 dump_lines
= dump_err
.readlines()
705 load_lines
= load_out
.readlines()
710 # Wait on the pipes; ignore return code.
711 wait_on_pipe(dump_kid
, None, dump_lines
)
712 wait_on_pipe(load_kid
, load_lines
, None)
714 dump_re
= re
.compile(r
'^\* Dumped revision (\d+)\.\r?$')
716 for dump_line
in dump_lines
:
717 match
= dump_re
.match(dump_line
)
718 if not match
or match
.group(1) != str(expect_revision
):
719 sys
.stdout
.write('ERROR: dump failed: %s ' % dump_line
)
721 raise SVNRepositoryCopyFailure
723 if expect_revision
!= head_revision
+ 1:
724 print('ERROR: dump failed; did not see revision %s' % head_revision
)
725 raise SVNRepositoryCopyFailure
727 load_re
= re
.compile(r
'^------- Committed revision (\d+) >>>\r?$')
729 for load_line
in load_lines
:
730 match
= load_re
.match(load_line
)
732 if match
.group(1) != str(expect_revision
):
733 sys
.stdout
.write('ERROR: load failed: %s ' % load_line
)
735 raise SVNRepositoryCopyFailure
737 if expect_revision
!= head_revision
+ 1:
738 print('ERROR: load failed; did not see revision %s' % head_revision
)
739 raise SVNRepositoryCopyFailure
742 def canonicalize_url(input):
743 "Canonicalize the url, if the scheme is unknown, returns intact input"
745 m
= re
.match(r
"^((file://)|((svn|svn\+ssh|http|https)(://)))", input)
748 return scheme
+ re
.sub(r
'//*', '/', input[len(scheme
):])
753 def create_python_hook_script (hook_path
, hook_script_code
):
754 """Create a Python hook script at HOOK_PATH with the specified
757 if sys
.platform
== 'win32':
758 # Use an absolute path since the working directory is not guaranteed
759 hook_path
= os
.path
.abspath(hook_path
)
760 # Fill the python file.
761 file_write ("%s.py" % hook_path
, hook_script_code
)
762 # Fill the batch wrapper file.
763 file_append ("%s.bat" % hook_path
,
764 "@\"%s\" %s.py %%*\n" % (sys
.executable
, hook_path
))
766 # For all other platforms
767 file_write (hook_path
, "#!%s\n%s" % (sys
.executable
, hook_script_code
))
768 os
.chmod (hook_path
, 0755)
770 def write_restrictive_svnserve_conf(repo_dir
, anon_access
="none"):
771 "Create a restrictive authz file ( no anynomous access )."
773 fp
= open(get_svnserve_conf_file_path(repo_dir
), 'w')
774 fp
.write("[general]\nanon-access = %s\nauth-access = write\n"
775 "authz-db = authz\n" % anon_access
)
777 fp
.write("realm = svntest\n[sasl]\nuse-sasl = true\n");
779 fp
.write("password-db = passwd\n")
782 # Warning: because mod_dav_svn uses one shared authz file for all
783 # repositories, you *cannot* use write_authz_file in any test that
784 # might be run in parallel.
786 # write_authz_file can *only* be used in test suites which disable
787 # parallel execution at the bottom like so
788 # if __name__ == '__main__':
789 # svntest.main.run_tests(test_list, serial_only = True)
790 def write_authz_file(sbox
, rules
, sections
=None):
791 """Write an authz file to SBOX, appropriate for the RA method used,
792 with authorizations rules RULES mapping paths to strings containing
793 the rules. You can add sections SECTIONS (ex. groups, aliases...) with
794 an appropriate list of mappings.
796 fp
= open(sbox
.authz_file
, 'w')
798 # When the sandbox repository is read only it's name will be different from
799 # the repository name.
800 repo_name
= sbox
.repo_dir
801 while repo_name
[-1] == '/':
802 repo_name
= repo_name
[:-1]
803 repo_name
= os
.path
.basename(repo_name
)
805 if sbox
.repo_url
.startswith("http"):
806 prefix
= repo_name
+ ":"
810 for p
, r
in sections
.items():
811 fp
.write("[%s]\n%s\n" % (p
, r
))
813 for p
, r
in rules
.items():
814 fp
.write("[%s%s]\n%s\n" % (prefix
, p
, r
))
817 def use_editor(func
):
818 os
.environ
['SVN_EDITOR'] = svneditor_script
819 os
.environ
['SVN_MERGE'] = svneditor_script
820 os
.environ
['SVNTEST_EDITOR_FUNC'] = func
823 def merge_notify_line(revstart
=None, revend
=None, same_URL
=True,
825 """Return an expected output line that describes the beginning of a
826 merge operation on revisions REVSTART through REVEND. Omit both
827 REVSTART and REVEND for the case where the left and right sides of
828 the merge are from different URLs."""
829 from_foreign_phrase
= foreign
and "\(from foreign repository\) " or ""
831 return "--- Merging differences between %srepository URLs into '.+':\n" \
832 % (foreign
and "foreign " or "")
835 # The left and right sides of the merge are from different URLs.
836 return "--- Merging differences between %srepository URLs into '.+':\n" \
837 % (foreign
and "foreign " or "")
839 return "--- Reverse-merging %sr%ld into '.+':\n" \
840 % (from_foreign_phrase
, abs(revstart
))
842 return "--- Merging %sr%ld into '.+':\n" \
843 % (from_foreign_phrase
, revstart
)
845 if revstart
> revend
:
846 return "--- Reverse-merging %sr%ld through r%ld into '.+':\n" \
847 % (from_foreign_phrase
, revstart
, revend
)
849 return "--- Merging %sr%ld through r%ld into '.+':\n" \
850 % (from_foreign_phrase
, revstart
, revend
)
853 ######################################################################
854 # Functions which check the test configuration
855 # (useful for conditional XFails)
857 def _check_command_line_parsed():
858 """Raise an exception if the command line has not yet been parsed."""
859 if not command_line_parsed
:
860 raise Failure("Condition cannot be tested until command line is parsed")
862 def is_ra_type_dav():
863 _check_command_line_parsed()
864 return test_area_url
.startswith('http')
866 def is_ra_type_svn():
867 _check_command_line_parsed()
868 return test_area_url
.startswith('svn')
870 def is_ra_type_file():
871 _check_command_line_parsed()
872 return test_area_url
.startswith('file')
874 def is_fs_type_fsfs():
875 _check_command_line_parsed()
876 # This assumes that fsfs is the default fs implementation.
877 return fs_type
== 'fsfs' or fs_type
is None
880 return os
.name
== 'nt'
883 return os
.name
== 'posix'
886 return sys
.platform
== 'darwin'
888 def server_has_mergeinfo():
889 _check_command_line_parsed()
890 return server_minor_version
>= 5
892 def server_has_revprop_commit():
893 _check_command_line_parsed()
894 return server_minor_version
>= 5
896 def server_sends_copyfrom_on_update():
897 _check_command_line_parsed()
898 return server_minor_version
>= 5
900 def server_authz_has_aliases():
901 _check_command_line_parsed()
902 return server_minor_version
>= 5
904 def server_gets_client_capabilities():
905 _check_command_line_parsed()
906 return server_minor_version
>= 5
908 def server_has_partial_replay():
909 _check_command_line_parsed()
910 return server_minor_version
>= 5
912 def server_enforces_date_syntax():
913 _check_command_line_parsed()
914 return server_minor_version
>= 5
917 ######################################################################
921 """Manages a sandbox (one or more repository/working copy pairs) for
922 a test to operate within."""
926 def __init__(self
, module
, idx
):
927 self
._set
_name
("%s-%d" % (module
, idx
))
929 def _set_name(self
, name
, read_only
= False):
930 """A convenience method for renaming a sandbox, useful when
931 working with multiple repositories in the same unit test."""
934 self
.read_only
= read_only
935 self
.wc_dir
= os
.path
.join(general_wc_dir
, self
.name
)
937 self
.repo_dir
= os
.path
.join(general_repo_dir
, self
.name
)
938 self
.repo_url
= test_area_url
+ '/' + pathname2url(self
.repo_dir
)
940 self
.repo_dir
= pristine_dir
941 self
.repo_url
= pristine_url
943 ### TODO: Move this into to the build() method
944 # For dav tests we need a single authz file which must be present,
945 # so we recreate it each time a sandbox is created with some default
947 if self
.repo_url
.startswith("http"):
948 # this dir doesn't exist out of the box, so we may have to make it
949 if not os
.path
.exists(work_dir
):
950 os
.makedirs(work_dir
)
951 self
.authz_file
= os
.path
.join(work_dir
, "authz")
952 file_write(self
.authz_file
, "[/]\n* = rw\n")
954 # For svnserve tests we have a per-repository authz file, and it
955 # doesn't need to be there in order for things to work, so we don't
956 # have any default contents.
957 elif self
.repo_url
.startswith("svn"):
958 self
.authz_file
= os
.path
.join(self
.repo_dir
, "conf", "authz")
960 self
.test_paths
= [self
.wc_dir
, self
.repo_dir
]
962 def clone_dependent(self
, copy_wc
=False):
963 """A convenience method for creating a near-duplicate of this
964 sandbox, useful when working with multiple repositories in the
965 same unit test. If COPY_WC is true, make an exact copy of this
966 sandbox's working copy at the new sandbox's working copy
967 directory. Any necessary cleanup operations are triggered by
968 cleanup of the original sandbox."""
970 if not self
.dependents
:
972 clone
= copy
.deepcopy(self
)
973 self
.dependents
.append(clone
)
974 clone
._set
_name
("%s-%d" % (self
.name
, len(self
.dependents
)))
976 self
.add_test_path(clone
.wc_dir
)
977 shutil
.copytree(self
.wc_dir
, clone
.wc_dir
, symlinks
=True)
980 def build(self
, name
= None, create_wc
= True, read_only
= False):
981 self
._set
_name
(name
, read_only
)
982 if actions
.make_repo_and_wc(self
, create_wc
, read_only
):
983 raise Failure("Could not build repository and sandbox '%s'" % self
.name
)
985 def add_test_path(self
, path
, remove
=True):
986 self
.test_paths
.append(path
)
990 def add_repo_path(self
, suffix
, remove
=1):
991 path
= os
.path
.join(general_repo_dir
, self
.name
) + '.' + suffix
992 url
= test_area_url
+ '/' + pathname2url(path
)
993 self
.add_test_path(path
, remove
)
996 def add_wc_path(self
, suffix
, remove
=1):
997 path
= self
.wc_dir
+ '.' + suffix
998 self
.add_test_path(path
, remove
)
1001 def cleanup_test_paths(self
):
1002 "Clean up detritus from this sandbox, and any dependents."
1004 # Recursively cleanup any dependent sandboxes.
1005 for sbox
in self
.dependents
:
1006 sbox
.cleanup_test_paths()
1007 # cleanup all test specific working copies and repositories
1008 for path
in self
.test_paths
:
1009 if not path
is pristine_dir
:
1010 _cleanup_test_path(path
)
1013 _deferred_test_paths
= []
1014 def _cleanup_deferred_test_paths():
1015 global _deferred_test_paths
1016 test_paths
= _deferred_test_paths
[:]
1017 _deferred_test_paths
= []
1018 for path
in test_paths
:
1019 _cleanup_test_path(path
, 1)
1021 def _cleanup_test_path(path
, retrying
=None):
1024 print("CLEANUP: RETRY: %s" % path
)
1026 print("CLEANUP: %s" % path
)
1031 print("WARNING: cleanup failed, will try again later")
1032 _deferred_test_paths
.append(path
)
1034 class TestSpawningThread(threading
.Thread
):
1035 """A thread that runs test cases in their own processes.
1036 Receives test numbers to run from the queue, and saves results into
1037 the results field."""
1038 def __init__(self
, queue
):
1039 threading
.Thread
.__init
__(self
)
1046 next_index
= self
.queue
.get_nowait()
1050 self
.run_one(next_index
)
1052 def run_one(self
, index
):
1053 command
= sys
.argv
[0]
1056 args
.append(str(index
))
1058 # add some startup arguments from this process
1060 args
.append('--fs-type=' + fs_type
)
1062 args
.append('--url=' + test_area_url
)
1066 args
.append('--cleanup')
1068 args
.append('--enable-sasl')
1070 args
.append('--http-library=' + http_library
)
1071 if server_minor_version
:
1072 args
.append('--server-minor-version=' + str(server_minor_version
))
1074 result
, stdout_lines
, stderr_lines
= spawn_process(command
, 1, None, *args
)
1075 # "result" will be None on platforms without Popen3 (e.g. Windows)
1076 if [x
for x
in stdout_lines
if x
.startswith('FAIL: ') or x
.startswith('XPASS: ')]:
1078 self
.results
.append((index
, result
, stdout_lines
, stderr_lines
))
1079 sys
.stdout
.write('.')
1083 """Encapsulate a single test case (predicate), including logic for
1084 runing the test and test list output."""
1086 def __init__(self
, func
, index
):
1087 self
.pred
= testcase
.create_test_case(func
)
1091 print(" %2d %-5s %s" % (self
.index
,
1092 self
.pred
.list_mode(),
1093 self
.pred
.get_description()))
1094 self
.pred
.check_description()
1096 def _print_name(self
):
1097 print("%s %s: %s" % (os
.path
.basename(sys
.argv
[0]), str(self
.index
),
1098 self
.pred
.get_description()))
1099 self
.pred
.check_description()
1102 """Run self.pred and return the result. The return value is
1103 - 0 if the test was successful
1104 - 1 if it errored in a way that indicates test failure
1105 - 2 if the test skipped
1107 if self
.pred
.need_sandbox():
1108 # ooh! this function takes a sandbox argument
1109 sandbox
= Sandbox(self
.pred
.get_sandbox_name(), self
.index
)
1110 kw
= { 'sandbox' : sandbox
}
1115 # Explicitly set this so that commands that commit but don't supply a
1116 # log message will fail rather than invoke an editor.
1117 # Tests that want to use an editor should invoke svntest.main.use_editor.
1118 os
.environ
['SVN_EDITOR'] = ''
1119 os
.environ
['SVNTEST_EDITOR_FUNC'] = ''
1122 # Set this SVNKit specific variable to the current test (test name plus
1123 # its index) being run so that SVNKit daemon could use this test name
1124 # for its separate log file
1125 os
.environ
['SVN_CURRENT_TEST'] = os
.path
.basename(sys
.argv
[0]) + "_" + \
1128 actions
.no_sleep_for_timestamps()
1130 saved_dir
= os
.getcwd()
1132 rc
= self
.pred
.run(**kw
)
1134 sys
.stdout
.write('STYLE ERROR in ')
1137 print('Test driver returned a status code.')
1144 # We captured Failure and its subclasses. We don't want to print
1145 # anything for plain old Failure since that just indicates test
1146 # failure, rather than relevant information. However, if there
1147 # *is* information in the exception's arguments, then print it.
1148 if ex
.__class
__ != Failure
or ex
.args
:
1151 print('EXCEPTION: %s: %s' % (ex
.__class
__.__name
__, ex_args
))
1153 print('EXCEPTION: %s' % ex
.__class
__.__name
__)
1154 traceback
.print_exc(file=sys
.stdout
)
1155 except KeyboardInterrupt:
1156 print('Interrupted')
1158 except SystemExit, ex
:
1159 print('EXCEPTION: SystemExit(%d), skipping cleanup' % ex
.code
)
1160 sys
.stdout
.write(ex
.code
and 'FAIL: ' or 'PASS: ')
1166 print('UNEXPECTED EXCEPTION:')
1167 traceback
.print_exc(file=sys
.stdout
)
1170 result
= self
.pred
.convert_result(result
)
1171 (result_text
, result_benignity
) = self
.pred
.run_text(result
)
1172 if not (quiet_mode
and result_benignity
):
1173 sys
.stdout
.write("%s " % result_text
)
1177 if sandbox
is not None and result
!= 1 and cleanup_mode
:
1178 sandbox
.cleanup_test_paths()
1181 ######################################################################
1182 # Main testing functions
1184 # These two functions each take a TEST_LIST as input. The TEST_LIST
1185 # should be a list of test functions; each test function should take
1186 # no arguments and return a 0 on success, non-zero on failure.
1187 # Ideally, each test should also have a short, one-line docstring (so
1188 # it can be displayed by the 'list' command.)
1190 # Func to run one test in the list.
1191 def run_one_test(n
, test_list
, parallel
= 0, finished_tests
= None):
1192 """Run the Nth client test in TEST_LIST, return the result.
1194 If we're running the tests in parallel spawn the test in a new process.
1197 if (n
< 1) or (n
> len(test_list
) - 1):
1198 print("There is no test %s.\n" % n
)
1203 st
= SpawnTest(n
, finished_tests
)
1207 exit_code
= TestRunner(test_list
[n
], n
).run()
1210 def _internal_run_tests(test_list
, testnums
, parallel
):
1211 """Run the tests from TEST_LIST whose indices are listed in TESTNUMS.
1213 If we're running the tests in parallel spawn as much parallel processes
1214 as requested and gather the results in a temp. buffer when a child
1215 process is finished.
1223 for testnum
in testnums
:
1224 if run_one_test(testnum
, test_list
) == 1:
1227 number_queue
= Queue
.Queue()
1228 for num
in testnums
:
1229 number_queue
.put(num
)
1231 threads
= [ TestSpawningThread(number_queue
) for i
in range(parallel
) ]
1238 # list of (index, result, stdout, stderr)
1241 results
+= t
.results
1244 # terminate the line of dots
1247 # all tests are finished, find out the result and print the logs.
1248 for (index
, result
, stdout_lines
, stderr_lines
) in results
:
1250 for line
in stdout_lines
:
1251 sys
.stdout
.write(line
)
1253 for line
in stderr_lines
:
1254 sys
.stdout
.write(line
)
1258 _cleanup_deferred_test_paths()
1263 prog_name
= os
.path
.basename(sys
.argv
[0])
1264 print("%s [--url] [--fs-type] [--verbose|--quiet] [--parallel] \\" %
1266 print("%s [--enable-sasl] [--cleanup] [--bin] [<test> ...]"
1267 % (" " * len(prog_name
)))
1268 print("%s " % (" " * len(prog_name
)))
1269 print("%s [--list] [<test> ...]\n" % prog_name
)
1271 print(" <test> The number of the test to run, or a range of test\n"
1272 " numbers, like 10:12 or 10-12. Multiple numbers and\n"
1273 " ranges are ok. If you supply none, all tests are run.\n")
1275 print(" --list Print test doc strings instead of running them")
1276 print(" --fs-type Subversion file system type (fsfs or bdb)")
1277 print(" --http-library DAV library to use (neon or serf)")
1278 print(" --url Base url to the repos (e.g. svn://localhost)")
1279 print(" --verbose Print binary command-lines (not with --quiet)")
1280 print(" --quiet Print only unexpected results (not with --verbose)")
1281 print(" --cleanup Whether to clean up")
1282 print(" --enable-sasl Whether to enable SASL authentication")
1283 print(" --parallel Run the tests in parallel")
1284 print(" --bin Use the svn binaries installed in this path")
1285 print(" --use-jsvn Use the jsvn (SVNKit based) binaries. Can be\n"
1286 " combined with --bin to point to a specific path")
1287 print(" --development Test development mode: provides more detailed test\n"
1288 " output and ignores all exceptions in the \n"
1289 " run_and_verify* functions. This option is only \n"
1290 " useful during test development!")
1291 print(" --server-minor-version Set the minor version for the server.\n"
1292 " Supports version 4 or 5.")
1293 print(" --config-file Configuration file for tests.")
1294 print(" --help This information")
1297 # Main func. This is the "entry point" that all the test scripts call
1298 # to run their list of tests.
1300 # This routine parses sys.argv to decide what to do.
1301 def run_tests(test_list
, serial_only
= False):
1302 """Main routine to run all tests in TEST_LIST.
1304 NOTE: this function does not return. It does a sys.exit() with the
1305 appropriate exit code.
1308 global test_area_url
1315 global is_child_process
1317 global svnadmin_binary
1318 global svnlook_binary
1319 global svnsync_binary
1320 global svndumpfilter_binary
1321 global svnversion_binary
1322 global command_line_parsed
1325 global server_minor_version
1329 # Should the tests be listed (as opposed to executed)?
1338 opts
, args
= my_getopt(sys
.argv
[1:], 'vqhpc',
1339 ['url=', 'fs-type=', 'verbose', 'quiet', 'cleanup',
1340 'list', 'enable-sasl', 'help', 'parallel',
1341 'bin=', 'http-library=', 'server-minor-version=',
1342 'use-jsvn', 'development', 'config-file='])
1343 except getopt
.GetoptError
, e
:
1344 print("ERROR: %s\n" % e
)
1350 # This is an old deprecated variant of the "--list" option:
1352 elif arg
.startswith('BASE_URL='):
1353 test_area_url
= arg
[9:]
1357 testnums
.append(int(arg
))
1360 # Do nothing for now.
1365 # Check if the argument is a range
1366 numberstrings
= arg
.split(':');
1367 if len(numberstrings
) != 2:
1368 numberstrings
= arg
.split('-');
1369 if len(numberstrings
) != 2:
1371 left
= int(numberstrings
[0])
1372 right
= int(numberstrings
[1])
1376 for nr
in range(left
,right
+1):
1384 print("ERROR: invalid test number or range '%s'\n" % arg
)
1388 for opt
, val
in opts
:
1392 elif opt
== "--fs-type":
1395 elif opt
== "-v" or opt
== "--verbose":
1398 elif opt
== "-q" or opt
== "--quiet":
1401 elif opt
== "--cleanup":
1404 elif opt
== "--list":
1407 elif opt
== "--enable-sasl":
1410 elif opt
== "-h" or opt
== "--help":
1414 elif opt
== '-p' or opt
== "--parallel":
1415 parallel
= 5 # use 5 parallel threads.
1418 is_child_process
= True
1420 elif opt
== '--bin':
1423 elif opt
== '--http-library':
1426 elif opt
== '--server-minor-version':
1427 server_minor_version
= int(val
)
1428 if server_minor_version
< 4 or server_minor_version
> 6:
1429 print("ERROR: test harness only supports server minor version 4 or 5")
1432 elif opt
== '--use-jsvn':
1435 elif opt
== '--development':
1436 setup_development_mode()
1438 elif opt
== '--config-file':
1441 if test_area_url
[-1:] == '/': # Normalize url to have no trailing slash
1442 test_area_url
= test_area_url
[:-1]
1444 if verbose_mode
and quiet_mode
:
1445 sys
.stderr
.write("ERROR: 'verbose' and 'quiet' are incompatible\n")
1448 # Calculate pristine_url from test_area_url.
1449 pristine_url
= test_area_url
+ '/' + pathname2url(pristine_dir
)
1454 svn_binary
= os
.path
.join(svn_bin
, 'jsvn' + _bat
)
1455 svnadmin_binary
= os
.path
.join(svn_bin
, 'jsvnadmin' + _bat
)
1456 svnlook_binary
= os
.path
.join(svn_bin
, 'jsvnlook' + _bat
)
1457 svnsync_binary
= os
.path
.join(svn_bin
, 'jsvnsync' + _bat
)
1458 svndumpfilter_binary
= os
.path
.join(svn_bin
, 'jsvndumpfilter' + _bat
)
1459 svnversion_binary
= os
.path
.join(svn_bin
, 'jsvnversion' + _bat
)
1462 svn_binary
= os
.path
.join(svn_bin
, 'svn' + _exe
)
1463 svnadmin_binary
= os
.path
.join(svn_bin
, 'svnadmin' + _exe
)
1464 svnlook_binary
= os
.path
.join(svn_bin
, 'svnlook' + _exe
)
1465 svnsync_binary
= os
.path
.join(svn_bin
, 'svnsync' + _exe
)
1466 svndumpfilter_binary
= os
.path
.join(svn_bin
, 'svndumpfilter' + _exe
)
1467 svnversion_binary
= os
.path
.join(svn_bin
, 'svnversion' + _exe
)
1469 command_line_parsed
= True
1471 ######################################################################
1473 # Cleanup: if a previous run crashed or interrupted the python
1474 # interpreter, then `temp_dir' was never removed. This can cause wonkiness.
1475 if not is_child_process
:
1476 safe_rmtree(temp_dir
, 1)
1479 # If no test numbers were listed explicitly, include all of them:
1480 testnums
= list(range(1, len(test_list
)))
1483 print("Test # Mode Test Description")
1484 print("------ ----- ----------------")
1485 for testnum
in testnums
:
1486 TestRunner(test_list
[testnum
], testnum
).list()
1488 # done. just exit with success.
1491 # don't run tests in parallel when the tests don't support it or there
1492 # are only a few tests to run.
1493 if serial_only
or len(testnums
) < 2:
1496 # Build out the default configuration directory
1497 create_config_dir(default_config_dir
)
1499 # Setup the pristine repository
1500 actions
.setup_pristine_repository()
1503 exit_code
= _internal_run_tests(test_list
, testnums
, parallel
)
1505 # Remove all scratchwork: the 'pristine' repository, greek tree, etc.
1506 # This ensures that an 'import' will happen the next time we run.
1507 if not is_child_process
:
1508 safe_rmtree(temp_dir
, 1)
1510 # Cleanup after ourselves.
1511 _cleanup_deferred_test_paths()
1513 # Return the appropriate exit code from the tests.
1516 # the modules import each other, so we do this import very late, to ensure
1517 # that the definitions in "main" have been completed.