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-2007 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()
31 my_getopt
= getopt
.gnu_getopt
32 except AttributeError:
33 my_getopt
= getopt
.getopt
35 from svntest
import Failure
36 from svntest
import Skip
37 from svntest
import testcase
38 from svntest
import wc
40 ######################################################################
42 # HOW TO USE THIS MODULE:
44 # Write a new python script that
46 # 1) imports this 'svntest' package
48 # 2) contains a number of related 'test' routines. (Each test
49 # routine should take no arguments, and return None on success
50 # or throw a Failure exception on failure. Each test should
51 # also contain a short docstring.)
53 # 3) places all the tests into a list that begins with None.
55 # 4) calls svntest.main.client_test() on the list.
57 # Also, your tests will probably want to use some of the common
58 # routines in the 'Utilities' section below.
60 #####################################################################
63 class SVNProcessTerminatedBySignal(Failure
):
64 "Exception raised if a spawned process segfaulted, aborted, etc."
67 class SVNLineUnequal(Failure
):
68 "Exception raised if two lines are unequal"
71 class SVNUnmatchedError(Failure
):
72 "Exception raised if an expected error is not found"
75 class SVNCommitFailure(Failure
):
76 "Exception raised if a commit failed"
79 class SVNRepositoryCopyFailure(Failure
):
80 "Exception raised if unable to copy a repository"
83 class SVNRepositoryCreateFailure(Failure
):
84 "Exception raised if unable to create a repository"
88 if sys
.platform
== 'win32':
90 file_scheme_prefix
= 'file:///'
95 file_scheme_prefix
= 'file://'
100 from popen2
import Popen3
101 platform_with_popen3_class
= True
103 platform_with_popen3_class
= False
105 # The location of our mock svneditor script.
106 if sys
.platform
== 'win32':
107 svneditor_script
= os
.path
.join(sys
.path
[0], 'svneditor.bat')
109 svneditor_script
= os
.path
.join(sys
.path
[0], 'svneditor.py')
111 # Username and password used by the working copies
112 wc_author
= 'jrandom'
113 wc_passwd
= 'rayjandom'
115 # Username and password used by the working copies for "second user"
117 wc_author2
= 'jconstant' # use the same password as wc_author
119 ######################################################################
120 # Global variables set during option parsing. These should not be used
121 # until the variable command_line_parsed has been set to True, as is
122 # done in run_tests below.
123 command_line_parsed
= False
125 # The locations of the svn, svnadmin and svnlook binaries, relative to
126 # the only scripts that import this file right now (they live in ../).
127 # Use --bin to override these defaults.
128 svn_binary
= os
.path
.abspath('../../svn/svn' + _exe
)
129 svnadmin_binary
= os
.path
.abspath('../../svnadmin/svnadmin' + _exe
)
130 svnlook_binary
= os
.path
.abspath('../../svnlook/svnlook' + _exe
)
131 svnsync_binary
= os
.path
.abspath('../../svnsync/svnsync' + _exe
)
132 svnversion_binary
= os
.path
.abspath('../../svnversion/svnversion' + _exe
)
133 svndumpfilter_binary
= os
.path
.abspath('../../svndumpfilter/svndumpfilter' + \
136 # Global variable indicating if we want verbose output, that is,
137 # details of what commands each test does as it does them. This is
138 # incompatible with quiet_mode.
141 # Global variable indicating if we want quiet output, that is, don't
142 # show PASS, XFAIL, or SKIP notices, but do show FAIL and XPASS. This
143 # is incompatible with verbose_mode.
146 # Global variable indicating if we want test data cleaned up after success
149 # Global variable indicating if svnserve should use Cyrus SASL
152 # Global variable indicating which DAV library, if any, is in use
156 # Global variable indicating what the minor version of the server
157 # tested against is (4 for 1.4.x, for example).
158 server_minor_version
= 5
160 # Global variable indicating if this is a child process and no cleanup
161 # of global directories is needed.
162 is_child_process
= False
164 # Global URL to testing area. Default to ra_local, current working dir.
165 test_area_url
= file_scheme_prefix
+ os
.path
.abspath(os
.getcwd())
167 test_area_url
= test_area_url
.replace('\\', '/')
169 # Location to the pristine repository, will be calculated from test_area_url
170 # when we know what the user specified for --url.
173 # Global variable indicating the FS type for repository creations.
176 # End of command-line-set global variables.
177 ######################################################################
179 # All temporary repositories and working copies are created underneath
180 # this dir, so there's one point at which to mount, e.g., a ramdisk.
181 work_dir
= "svn-test-work"
183 # Constant for the merge info property.
184 SVN_PROP_MERGEINFO
= "svn:mergeinfo"
186 # Where we want all the repositories and working copies to live.
187 # Each test will have its own!
188 general_repo_dir
= os
.path
.join(work_dir
, "repositories")
189 general_wc_dir
= os
.path
.join(work_dir
, "working_copies")
191 # temp directory in which we will create our 'pristine' local
192 # repository and other scratch data. This should be removed when we
193 # quit and when we startup.
194 temp_dir
= os
.path
.join(work_dir
, 'local_tmp')
196 # (derivatives of the tmp dir.)
197 pristine_dir
= os
.path
.join(temp_dir
, "repos")
198 greek_dump_dir
= os
.path
.join(temp_dir
, "greekfiles")
199 default_config_dir
= os
.path
.abspath(os
.path
.join(temp_dir
, "config"))
202 # Our pristine greek-tree state.
204 # If a test wishes to create an "expected" working-copy tree, it should
205 # call main.greek_state.copy(). That method will return a copy of this
206 # State object which can then be edited.
209 greek_state
= wc
.State('', {
210 'iota' : _item("This is the file 'iota'.\n"),
212 'A/mu' : _item("This is the file 'mu'.\n"),
214 'A/B/lambda' : _item("This is the file 'lambda'.\n"),
216 'A/B/E/alpha' : _item("This is the file 'alpha'.\n"),
217 'A/B/E/beta' : _item("This is the file 'beta'.\n"),
221 'A/D/gamma' : _item("This is the file 'gamma'.\n"),
223 'A/D/G/pi' : _item("This is the file 'pi'.\n"),
224 'A/D/G/rho' : _item("This is the file 'rho'.\n"),
225 'A/D/G/tau' : _item("This is the file 'tau'.\n"),
227 'A/D/H/chi' : _item("This is the file 'chi'.\n"),
228 'A/D/H/psi' : _item("This is the file 'psi'.\n"),
229 'A/D/H/omega' : _item("This is the file 'omega'.\n"),
233 ######################################################################
234 # Utilities shared by the tests
236 "Wrap a function, catch, print and ignore exceptions"
237 def w(*args
, **kwds
):
239 return func(*args
, **kwds
)
241 if ex
.__class
__ != Failure
or ex
.args
:
244 print 'EXCEPTION: %s: %s' % (ex
.__class
__.__name
__, ex_args
)
246 print 'EXCEPTION:', ex
.__class
__.__name
__
249 def setup_development_mode():
250 "Wraps functions in module actions"
251 l
= [ 'run_and_verify_svn',
252 'run_and_verify_svnversion',
253 'run_and_verify_load',
254 'run_and_verify_dump',
255 'run_and_verify_checkout',
256 'run_and_verify_export',
257 'run_and_verify_update',
258 'run_and_verify_merge',
259 'run_and_verify_merge2',
260 'run_and_verify_switch',
261 'run_and_verify_commit',
262 'run_and_verify_unquiet_status',
263 'run_and_verify_status',
264 'run_and_verify_diff_summarize',
265 'run_and_verify_diff_summarize_xml',
266 'run_and_validate_lock']
269 setattr(actions
, func
, wrap_ex(getattr(actions
, func
)))
271 def get_admin_name():
272 "Return name of SVN administrative subdirectory."
274 if (windows
or sys
.platform
== 'cygwin') \
275 and os
.environ
.has_key('SVN_ASP_DOT_NET_HACK'):
280 def get_start_commit_hook_path(repo_dir
):
281 "Return the path of the start-commit-hook conf file in REPO_DIR."
283 return os
.path
.join(repo_dir
, "hooks", "start-commit")
286 def get_pre_commit_hook_path(repo_dir
):
287 "Return the path of the pre-commit-hook conf file in REPO_DIR."
289 return os
.path
.join(repo_dir
, "hooks", "pre-commit")
292 def get_post_commit_hook_path(repo_dir
):
293 "Return the path of the post-commit-hook conf file in REPO_DIR."
295 return os
.path
.join(repo_dir
, "hooks", "post-commit")
297 def get_pre_revprop_change_hook_path(repo_dir
):
298 "Return the path of the pre-revprop-change hook script in REPO_DIR."
300 return os
.path
.join(repo_dir
, "hooks", "pre-revprop-change")
302 def get_svnserve_conf_file_path(repo_dir
):
303 "Return the path of the svnserve.conf file in REPO_DIR."
305 return os
.path
.join(repo_dir
, "conf", "svnserve.conf")
307 # Run any binary, logging the command line and return code
308 def run_command(command
, error_expected
, binary_mode
=0, *varargs
):
309 """Run COMMAND with VARARGS; return exit code as int; stdout, stderr
311 If ERROR_EXPECTED is None, any stderr also will be printed."""
313 return run_command_stdin(command
, error_expected
, binary_mode
,
316 # A regular expression that matches arguments that are trivially safe
317 # to pass on a command line without quoting on any supported operating
319 _safe_arg_re
= re
.compile(r
'^[A-Za-z\d\.\_\/\-\:\@]+$')
322 """Quote ARG for a command line.
324 Simply surround every argument in double-quotes unless it contains
325 only universally harmless characters.
327 WARNING: This function cannot handle arbitrary command-line
328 arguments. It can easily be confused by shell metacharacters. A
329 perfect job would be difficult and OS-dependent (see, for example,
330 http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp).
331 In other words, this function is just good enough for what we need
335 if _safe_arg_re
.match(arg
):
339 arg
= arg
.replace('$', '\$')
340 return '"%s"' % (arg
,)
342 def open_pipe(command
, mode
):
343 """Opens a popen3 pipe to COMMAND in MODE.
345 Returns (infile, outfile, errfile, waiter); waiter
346 should be passed to wait_on_pipe."""
347 if platform_with_popen3_class
:
348 kid
= Popen3(command
, True)
349 return kid
.tochild
, kid
.fromchild
, kid
.childerr
, (kid
, command
)
351 inf
, outf
, errf
= os
.popen3(command
, mode
)
352 return inf
, outf
, errf
, None
354 def wait_on_pipe(waiter
, stdout_lines
, stderr_lines
):
355 """Waits for KID (opened with open_pipe) to finish, dying
356 if it does. Uses STDOUT_LINES and STDERR_LINES for error message
357 if kid fails. Returns kid's exit code."""
361 kid
, command
= waiter
363 wait_code
= kid
.wait()
365 if os
.WIFSIGNALED(wait_code
):
366 exit_signal
= os
.WTERMSIG(wait_code
)
367 if stdout_lines
is not None:
368 sys
.stdout
.write("".join(stdout_lines
))
369 if stderr_lines
is not None:
370 sys
.stderr
.write("".join(stderr_lines
))
372 # show the whole path to make it easier to start a debugger
373 sys
.stderr
.write("CMD: %s terminated by signal %d\n"
374 % (command
, exit_signal
))
375 raise SVNProcessTerminatedBySignal
377 exit_code
= os
.WEXITSTATUS(wait_code
)
378 if exit_code
and verbose_mode
:
379 sys
.stderr
.write("CMD: %s exited with %d\n" % (command
, exit_code
))
382 # Run any binary, supplying input text, logging the command line
383 def spawn_process(command
, binary_mode
=0,stdin_lines
=None, *varargs
):
384 args
= ' '.join(map(_quote_arg
, varargs
))
386 # Log the command line
387 if verbose_mode
and not command
.endswith('.py'):
388 print 'CMD:', os
.path
.basename(command
) + ' ' + args
,
395 infile
, outfile
, errfile
, kid
= open_pipe(command
+ ' ' + args
, mode
)
398 map(infile
.write
, stdin_lines
)
402 stdout_lines
= outfile
.readlines()
403 stderr_lines
= errfile
.readlines()
408 exit_code
= wait_on_pipe(kid
, stdout_lines
, stderr_lines
)
410 return exit_code
, stdout_lines
, stderr_lines
412 def run_command_stdin(command
, error_expected
, binary_mode
=0,
413 stdin_lines
=None, *varargs
):
414 """Run COMMAND with VARARGS; input STDIN_LINES (a list of strings
415 which should include newline characters) to program via stdin - this
416 should not be very large, as if the program outputs more than the OS
417 is willing to buffer, this will deadlock, with both Python and
418 COMMAND waiting to write to each other for ever.
419 Return exit code as int; stdout, stderr as lists of lines.
420 If ERROR_EXPECTED is None, any stderr also will be printed."""
425 exit_code
, stdout_lines
, stderr_lines
= spawn_process(command
,
432 print '<TIME = %.6f>' % (stop
- start
)
434 if (not error_expected
) and (stderr_lines
):
435 map(sys
.stdout
.write
, stderr_lines
)
438 return exit_code
, stdout_lines
, stderr_lines
440 def create_config_dir(cfgdir
, config_contents
=None, server_contents
=None):
441 "Create config directories and files"
444 cfgfile_cfg
= os
.path
.join(cfgdir
, 'config')
445 cfgfile_srv
= os
.path
.join(cfgdir
, 'servers')
447 # create the directory
448 if not os
.path
.isdir(cfgdir
):
451 # define default config file contents if none provided
452 if config_contents
is None:
453 config_contents
= """
456 interactive-conflicts = false
459 # define default server file contents if none provided
460 if server_contents
is None:
462 server_contents
= """
468 server_contents
= "#\n"
470 file_write(cfgfile_cfg
, config_contents
)
471 file_write(cfgfile_srv
, server_contents
)
473 def _with_config_dir(args
):
474 if '--config-dir' in args
:
477 return args
+ ('--config-dir', default_config_dir
)
479 def _with_auth(args
):
480 assert '--password' not in args
481 args
= args
+ ('--password', wc_passwd
,
483 if '--username' in args
:
486 return args
+ ('--username', wc_author
)
488 # For running subversion and returning the output
489 def run_svn(error_expected
, *varargs
):
490 """Run svn with VARARGS; return exit code as int; stdout, stderr as
492 If ERROR_EXPECTED is None, any stderr also will be printed. If
493 you're just checking that something does/doesn't come out of
494 stdout/stderr, you might want to use actions.run_and_verify_svn()."""
495 return run_command(svn_binary
, error_expected
, 0,
496 *(_with_auth(_with_config_dir(varargs
))))
498 # For running svnadmin. Ignores the output.
499 def run_svnadmin(*varargs
):
500 """Run svnadmin with VARARGS, returns exit code as int; stdout, stderr as
502 return run_command(svnadmin_binary
, 1, 0, *varargs
)
504 # For running svnlook. Ignores the output.
505 def run_svnlook(*varargs
):
506 """Run svnlook with VARARGS, returns exit code as int; stdout, stderr as
508 return run_command(svnlook_binary
, 1, 0, *varargs
)
510 def run_svnsync(*varargs
):
511 """Run svnsync with VARARGS, returns exit code as int; stdout, stderr as
513 return run_command(svnsync_binary
, 1, 0, *(_with_config_dir(varargs
)))
515 def run_svnversion(*varargs
):
516 """Run svnversion with VARARGS, returns exit code as int; stdout, stderr
518 return run_command(svnversion_binary
, 1, 0, *varargs
)
520 # Chmod recursively on a whole subtree
521 def chmod_tree(path
, mode
, mask
):
522 def visit(arg
, dirname
, names
):
525 fullname
= os
.path
.join(dirname
, name
)
526 if not os
.path
.islink(fullname
):
527 new_mode
= (os
.stat(fullname
)[stat
.ST_MODE
] & ~mask
) | mode
528 os
.chmod(fullname
, new_mode
)
529 os
.path
.walk(path
, visit
, (mode
, mask
))
531 # For clearing away working copies
532 def safe_rmtree(dirname
, retry
=0):
533 "Remove the tree at DIRNAME, making it writable first"
535 chmod_tree(dirname
, 0666, 0666)
536 shutil
.rmtree(dirname
)
538 if not os
.path
.exists(dirname
):
542 for delay
in (0.5, 1, 2, 4):
553 # For making local mods to files
554 def file_append(path
, new_text
):
555 "Append NEW_TEXT to file at PATH"
556 file_write(path
, new_text
, 'a') # open in (a)ppend mode
558 # Append in binary mode
559 def file_append_binary(path
, new_text
):
560 "Append NEW_TEXT to file at PATH in binary mode"
561 file_write(path
, new_text
, 'ab') # open in (a)ppend mode
563 # For creating new files, and making local mods to existing files.
564 def file_write(path
, contents
, mode
= 'w'):
565 """Write the CONTENTS to the file at PATH, opening file using MODE,
566 which is (w)rite by default."""
567 fp
= open(path
, mode
)
571 # For reading the contents of a file
572 def file_read(path
, mode
= 'r'):
573 """Return the contents of the file at PATH, opening file using MODE,
574 which is (r)ead by default."""
575 fp
= open(path
, mode
)
580 # For replacing parts of contents in an existing file, with new content.
581 def file_substitute(path
, contents
, new_contents
):
582 """Replace the CONTENTS in the file at PATH using the NEW_CONTENTS"""
586 fcontent
= fcontent
.replace(contents
, new_contents
)
591 # For creating blank new repositories
592 def create_repos(path
):
593 """Create a brand-new SVN repository at PATH. If PATH does not yet
596 if not os
.path
.exists(path
):
597 os
.makedirs(path
) # this creates all the intermediate dirs, if neccessary
599 opts
= ("--bdb-txn-nosync",)
600 if server_minor_version
< 5:
601 opts
+= ("--pre-1.5-compatible",)
602 if fs_type
is not None:
603 opts
+= ("--fs-type=" + fs_type
,)
604 exit_code
, stdout
, stderr
= run_command(svnadmin_binary
, 1, 0, "create",
607 # Skip tests if we can't create the repository.
610 if line
.find('Unknown FS type') != -1:
612 # If the FS type is known, assume the repos couldn't be created
613 # (e.g. due to a missing 'svnadmin' binary).
614 raise SVNRepositoryCreateFailure("".join(stderr
).rstrip())
616 # Allow unauthenticated users to write to the repos, for ra_svn testing.
617 file_write(get_svnserve_conf_file_path(path
),
618 "[general]\nauth-access = write\n");
620 file_append(get_svnserve_conf_file_path(path
),
621 "realm = svntest\n[sasl]\nuse-sasl = true\n")
623 file_append(get_svnserve_conf_file_path(path
), "password-db = passwd\n")
624 file_append(os
.path
.join(path
, "conf", "passwd"),
625 "[users]\njrandom = rayjandom\njconstant = rayjandom\n");
626 # make the repos world-writeable, for mod_dav_svn's sake.
627 chmod_tree(path
, 0666, 0666)
629 # For copying a repository
630 def copy_repos(src_path
, dst_path
, head_revision
, ignore_uuid
= 1):
631 "Copy the repository SRC_PATH, with head revision HEAD_REVISION, to DST_PATH"
633 # Do an svnadmin dump|svnadmin load cycle. Print a fake pipe command so that
634 # the displayed CMDs can be run by hand
635 create_repos(dst_path
)
636 dump_args
= ' dump "' + src_path
+ '"'
637 load_args
= ' load "' + dst_path
+ '"'
640 load_args
= load_args
+ " --ignore-uuid"
642 print 'CMD:', os
.path
.basename(svnadmin_binary
) + dump_args
, \
643 '|', os
.path
.basename(svnadmin_binary
) + load_args
,
646 dump_in
, dump_out
, dump_err
, dump_kid
= \
647 open_pipe(svnadmin_binary
+ dump_args
, 'b')
648 load_in
, load_out
, load_err
, load_kid
= \
649 open_pipe(svnadmin_binary
+ load_args
, 'b')
652 print '<TIME = %.6f>' % (stop
- start
)
655 data
= dump_out
.read(1024*1024) # Arbitrary buffer size
659 load_in
.close() # Tell load we are done
661 dump_lines
= dump_err
.readlines()
662 load_lines
= load_out
.readlines()
668 # Wait on the pipes; ignore return code.
669 wait_on_pipe(dump_kid
, None, dump_lines
)
670 wait_on_pipe(load_kid
, load_lines
, None)
672 dump_re
= re
.compile(r
'^\* Dumped revision (\d+)\.\r?$')
674 for dump_line
in dump_lines
:
675 match
= dump_re
.match(dump_line
)
676 if not match
or match
.group(1) != str(expect_revision
):
677 print 'ERROR: dump failed:', dump_line
,
678 raise SVNRepositoryCopyFailure
680 if expect_revision
!= head_revision
+ 1:
681 print 'ERROR: dump failed; did not see revision', head_revision
682 raise SVNRepositoryCopyFailure
684 load_re
= re
.compile(r
'^------- Committed revision (\d+) >>>\r?$')
686 for load_line
in load_lines
:
687 match
= load_re
.match(load_line
)
689 if match
.group(1) != str(expect_revision
):
690 print 'ERROR: load failed:', load_line
,
691 raise SVNRepositoryCopyFailure
693 if expect_revision
!= head_revision
+ 1:
694 print 'ERROR: load failed; did not see revision', head_revision
695 raise SVNRepositoryCopyFailure
698 def canonicalize_url(input):
699 "Canonicalize the url, if the scheme is unknown, returns intact input"
701 m
= re
.match(r
"^((file://)|((svn|svn\+ssh|http|https)(://)))", input)
704 return scheme
+ re
.sub(r
'//*', '/', input[len(scheme
):])
709 def create_python_hook_script (hook_path
, hook_script_code
):
710 """Create a Python hook script at HOOK_PATH with the specified
713 if sys
.platform
== 'win32':
714 # Use an absolute path since the working directory is not guaranteed
715 hook_path
= os
.path
.abspath(hook_path
)
716 # Fill the python file.
717 file_write ("%s.py" % hook_path
, hook_script_code
)
718 # Fill the batch wrapper file.
719 file_append ("%s.bat" % hook_path
,
720 "@\"%s\" %s.py %%*\n" % (sys
.executable
, hook_path
))
722 # For all other platforms
723 file_write (hook_path
, "#!%s\n%s" % (sys
.executable
, hook_script_code
))
724 os
.chmod (hook_path
, 0755)
726 def write_restrictive_svnserve_conf(repo_dir
, anon_access
="none"):
727 "Create a restrictive authz file ( no anynomous access )."
729 fp
= open(get_svnserve_conf_file_path(repo_dir
), 'w')
730 fp
.write("[general]\nanon-access = %s\nauth-access = write\n"
731 "authz-db = authz\n" % anon_access
)
733 fp
.write("realm = svntest\n[sasl]\nuse-sasl = true\n");
735 fp
.write("password-db = passwd\n")
738 # Warning: because mod_dav_svn uses one shared authz file for all
739 # repositories, you *cannot* use write_authz_file in any test that
740 # might be run in parallel.
742 # write_authz_file can *only* be used in test suites which disable
743 # parallel execution at the bottom like so
744 # if __name__ == '__main__':
745 # svntest.main.run_tests(test_list, serial_only = True)
746 def write_authz_file(sbox
, rules
, sections
=None):
747 """Write an authz file to SBOX, appropriate for the RA method used,
748 with authorizations rules RULES mapping paths to strings containing
749 the rules. You can add sections SECTIONS (ex. groups, aliases...) with
750 an appropriate list of mappings.
752 fp
= open(sbox
.authz_file
, 'w')
754 # When the sandbox repository is read only it's name will be different from
755 # the repository name.
756 repo_name
= sbox
.repo_dir
757 while repo_name
[-1] == '/':
758 repo_name
= repo_name
[:-1]
759 repo_name
= os
.path
.basename(repo_name
)
761 if sbox
.repo_url
.startswith("http"):
762 prefix
= repo_name
+ ":"
766 for p
, r
in sections
.items():
767 fp
.write("[%s]\n%s\n" % (p
, r
))
769 for p
, r
in rules
.items():
770 fp
.write("[%s%s]\n%s\n" % (prefix
, p
, r
))
773 def use_editor(func
):
774 os
.environ
['SVN_EDITOR'] = svneditor_script
775 os
.environ
['SVN_MERGE'] = svneditor_script
776 os
.environ
['SVNTEST_EDITOR_FUNC'] = func
779 def merge_notify_line(revstart
=None, revend
=None, same_URL
=True):
780 """Return an expected output line that describes the beginning of a
781 merge operation on revisions REVSTART through REVEND. Omit both
782 REVSTART and REVEND for the case where the left and right sides of
783 the merge are from different URLs."""
785 return "--- Merging differences between repository URLs into '.+':\n"
788 # The left and right sides of the merge are from different URLs.
789 return "--- Merging differences between repository URLs into '.+':\n"
791 return "--- Reverse-merging r%ld into '.+':\n" % abs(revstart
)
793 return "--- Merging r%ld into '.+':\n" % revstart
795 if revstart
> revend
:
796 return "--- Reverse-merging r%ld through r%ld into '.+':\n" % (revstart
,
799 return "--- Merging r%ld through r%ld into '.+':\n" % (revstart
, revend
)
802 ######################################################################
803 # Functions which check the test configuration
804 # (useful for conditional XFails)
806 def _check_command_line_parsed():
807 """Raise an exception if the command line has not yet been parsed."""
808 if not command_line_parsed
:
809 raise Failure("Condition cannot be tested until command line is parsed")
811 def is_ra_type_dav():
812 _check_command_line_parsed()
813 return test_area_url
.startswith('http')
815 def is_ra_type_svn():
816 _check_command_line_parsed()
817 return test_area_url
.startswith('svn')
819 def is_ra_type_file():
820 _check_command_line_parsed()
821 return test_area_url
.startswith('file')
823 def is_fs_type_fsfs():
824 _check_command_line_parsed()
825 # This assumes that fsfs is the default fs implementation.
826 return fs_type
== 'fsfs' or fs_type
is None
829 return os
.name
== 'nt'
832 return os
.name
== 'posix'
835 return sys
.platform
== 'darwin'
837 def server_has_mergeinfo():
838 _check_command_line_parsed()
839 return server_minor_version
>= 5
841 def server_has_revprop_commit():
842 _check_command_line_parsed()
843 return server_minor_version
>= 5
845 def server_sends_copyfrom_on_update():
846 _check_command_line_parsed()
847 return server_minor_version
>= 5
849 def server_authz_has_aliases():
850 _check_command_line_parsed()
851 return server_minor_version
>= 5
853 def server_gets_client_capabilities():
854 _check_command_line_parsed()
855 return server_minor_version
>= 5
858 ######################################################################
862 """Manages a sandbox (one or more repository/working copy pairs) for
863 a test to operate within."""
867 def __init__(self
, module
, idx
):
868 self
._set
_name
("%s-%d" % (module
, idx
))
870 def _set_name(self
, name
, read_only
= False):
871 """A convenience method for renaming a sandbox, useful when
872 working with multiple repositories in the same unit test."""
875 self
.read_only
= read_only
876 self
.wc_dir
= os
.path
.join(general_wc_dir
, self
.name
)
878 self
.repo_dir
= os
.path
.join(general_repo_dir
, self
.name
)
879 self
.repo_url
= test_area_url
+ '/' + self
.repo_dir
881 self
.repo_dir
= pristine_dir
882 self
.repo_url
= pristine_url
884 ### TODO: Move this into to the build() method
885 # For dav tests we need a single authz file which must be present,
886 # so we recreate it each time a sandbox is created with some default
888 if self
.repo_url
.startswith("http"):
889 # this dir doesn't exist out of the box, so we may have to make it
890 if not os
.path
.exists(work_dir
):
891 os
.makedirs(work_dir
)
892 self
.authz_file
= os
.path
.join(work_dir
, "authz")
893 file_write(self
.authz_file
, "[/]\n* = rw\n")
895 # For svnserve tests we have a per-repository authz file, and it
896 # doesn't need to be there in order for things to work, so we don't
897 # have any default contents.
898 elif self
.repo_url
.startswith("svn"):
899 self
.authz_file
= os
.path
.join(self
.repo_dir
, "conf", "authz")
902 self
.repo_url
= self
.repo_url
.replace('\\', '/')
903 self
.test_paths
= [self
.wc_dir
, self
.repo_dir
]
905 def clone_dependent(self
, copy_wc
=False):
906 """A convenience method for creating a near-duplicate of this
907 sandbox, useful when working with multiple repositories in the
908 same unit test. If COPY_WC is true, make an exact copy of this
909 sandbox's working copy at the new sandbox's working copy
910 directory. Any necessary cleanup operations are triggered by
911 cleanup of the original sandbox."""
913 if not self
.dependents
:
915 clone
= copy
.deepcopy(self
)
916 self
.dependents
.append(clone
)
917 clone
._set
_name
("%s-%d" % (self
.name
, len(self
.dependents
)))
919 self
.add_test_path(clone
.wc_dir
)
920 shutil
.copytree(self
.wc_dir
, clone
.wc_dir
, symlinks
=True)
923 def build(self
, name
= None, create_wc
= True, read_only
= False):
924 self
._set
_name
(name
, read_only
)
925 if actions
.make_repo_and_wc(self
, create_wc
, read_only
):
926 raise Failure("Could not build repository and sandbox '%s'" % self
.name
)
928 def add_test_path(self
, path
, remove
=True):
929 self
.test_paths
.append(path
)
933 def add_repo_path(self
, suffix
, remove
=1):
934 path
= os
.path
.join(general_repo_dir
, self
.name
) + '.' + suffix
935 url
= test_area_url
+ '/' + path
937 url
= url
.replace('\\', '/')
938 self
.add_test_path(path
, remove
)
941 def add_wc_path(self
, suffix
, remove
=1):
942 path
= self
.wc_dir
+ '.' + suffix
943 self
.add_test_path(path
, remove
)
946 def cleanup_test_paths(self
):
947 "Clean up detritus from this sandbox, and any dependents."
949 # Recursively cleanup any dependent sandboxes.
950 for sbox
in self
.dependents
:
951 sbox
.cleanup_test_paths()
952 # cleanup all test specific working copies and repositories
953 for path
in self
.test_paths
:
954 if not path
is pristine_dir
:
955 _cleanup_test_path(path
)
958 _deferred_test_paths
= []
959 def _cleanup_deferred_test_paths():
960 global _deferred_test_paths
961 test_paths
= _deferred_test_paths
[:]
962 _deferred_test_paths
= []
963 for path
in test_paths
:
964 _cleanup_test_path(path
, 1)
966 def _cleanup_test_path(path
, retrying
=None):
969 print "CLEANUP: RETRY:", path
971 print "CLEANUP:", path
976 print "WARNING: cleanup failed, will try again later"
977 _deferred_test_paths
.append(path
)
979 class TestSpawningThread(threading
.Thread
):
980 """A thread that runs test cases in their own processes.
981 Receives test numbers to run from the queue, and saves results into
982 the results field."""
983 def __init__(self
, queue
):
984 threading
.Thread
.__init
__(self
)
991 next_index
= self
.queue
.get_nowait()
995 self
.run_one(next_index
)
997 def run_one(self
, index
):
998 command
= sys
.argv
[0]
1001 args
.append(str(index
))
1003 # add some startup arguments from this process
1005 args
.append('--fs-type=' + fs_type
)
1007 args
.append('--url=' + test_area_url
)
1011 args
.append('--cleanup')
1013 args
.append('--enable-sasl')
1015 args
.append('--http-library=' + http_library
)
1016 if server_minor_version
:
1017 args
.append('--server-minor-version=' + str(server_minor_version
))
1019 result
, stdout_lines
, stderr_lines
= spawn_process(command
, 1, None, *args
)
1020 # "result" will be None on platforms without Popen3 (e.g. Windows)
1021 if filter(lambda x
: x
.startswith('FAIL: ') or x
.startswith('XPASS: '),
1024 self
.results
.append((index
, result
, stdout_lines
, stderr_lines
))
1025 sys
.stdout
.write('.')
1029 """Encapsulate a single test case (predicate), including logic for
1030 runing the test and test list output."""
1032 def __init__(self
, func
, index
):
1033 self
.pred
= testcase
.create_test_case(func
)
1037 print " %2d %-5s %s" % (self
.index
,
1038 self
.pred
.list_mode(),
1039 self
.pred
.get_description())
1040 self
.pred
.check_description()
1042 def _print_name(self
):
1043 print os
.path
.basename(sys
.argv
[0]), str(self
.index
) + ":", \
1044 self
.pred
.get_description()
1045 self
.pred
.check_description()
1048 """Run self.pred and return the result. The return value is
1049 - 0 if the test was successful
1050 - 1 if it errored in a way that indicates test failure
1051 - 2 if the test skipped
1053 if self
.pred
.need_sandbox():
1054 # ooh! this function takes a sandbox argument
1055 sandbox
= Sandbox(self
.pred
.get_sandbox_name(), self
.index
)
1056 kw
= { 'sandbox' : sandbox
}
1061 # Explicitly set this so that commands that commit but don't supply a
1062 # log message will fail rather than invoke an editor.
1063 # Tests that want to use an editor should invoke svntest.main.use_editor.
1064 os
.environ
['SVN_EDITOR'] = ''
1065 os
.environ
['SVNTEST_EDITOR_FUNC'] = ''
1066 actions
.no_sleep_for_timestamps()
1068 saved_dir
= os
.getcwd()
1070 rc
= apply(self
.pred
.run
, (), kw
)
1072 print 'STYLE ERROR in',
1074 print 'Test driver returned a status code.'
1081 # We captured Failure and its subclasses. We don't want to print
1082 # anything for plain old Failure since that just indicates test
1083 # failure, rather than relevant information. However, if there
1084 # *is* information in the exception's arguments, then print it.
1085 if ex
.__class
__ != Failure
or ex
.args
:
1088 print 'EXCEPTION: %s: %s' % (ex
.__class
__.__name
__, ex_args
)
1090 print 'EXCEPTION:', ex
.__class
__.__name
__
1091 traceback
.print_exc(file=sys
.stdout
)
1092 except KeyboardInterrupt:
1095 except SystemExit, ex
:
1096 print 'EXCEPTION: SystemExit(%d), skipping cleanup' % ex
.code
1097 print ex
.code
and 'FAIL: ' or 'PASS: ',
1102 print 'UNEXPECTED EXCEPTION:'
1103 traceback
.print_exc(file=sys
.stdout
)
1106 result
= self
.pred
.convert_result(result
)
1107 (result_text
, result_benignity
) = self
.pred
.run_text(result
)
1108 if not (quiet_mode
and result_benignity
):
1112 if sandbox
is not None and result
!= 1 and cleanup_mode
:
1113 sandbox
.cleanup_test_paths()
1116 ######################################################################
1117 # Main testing functions
1119 # These two functions each take a TEST_LIST as input. The TEST_LIST
1120 # should be a list of test functions; each test function should take
1121 # no arguments and return a 0 on success, non-zero on failure.
1122 # Ideally, each test should also have a short, one-line docstring (so
1123 # it can be displayed by the 'list' command.)
1125 # Func to run one test in the list.
1126 def run_one_test(n
, test_list
, parallel
= 0, finished_tests
= None):
1127 """Run the Nth client test in TEST_LIST, return the result.
1129 If we're running the tests in parallel spawn the test in a new process.
1132 if (n
< 1) or (n
> len(test_list
) - 1):
1133 print "There is no test", `n`
+ ".\n"
1138 st
= SpawnTest(n
, finished_tests
)
1142 exit_code
= TestRunner(test_list
[n
], n
).run()
1145 def _internal_run_tests(test_list
, testnums
, parallel
):
1146 """Run the tests from TEST_LIST whose indices are listed in TESTNUMS.
1148 If we're running the tests in parallel spawn as much parallel processes
1149 as requested and gather the results in a temp. buffer when a child
1150 process is finished.
1158 for testnum
in testnums
:
1159 if run_one_test(testnum
, test_list
) == 1:
1162 number_queue
= Queue
.Queue()
1163 for num
in testnums
:
1164 number_queue
.put(num
)
1166 threads
= [ TestSpawningThread(number_queue
) for i
in range(parallel
) ]
1173 # list of (index, result, stdout, stderr)
1176 results
+= t
.results
1179 # terminate the line of dots
1182 # all tests are finished, find out the result and print the logs.
1183 for (index
, result
, stdout_lines
, stderr_lines
) in results
:
1185 for line
in stdout_lines
:
1186 sys
.stdout
.write(line
)
1188 for line
in stderr_lines
:
1189 sys
.stdout
.write(line
)
1193 _cleanup_deferred_test_paths()
1198 prog_name
= os
.path
.basename(sys
.argv
[0])
1199 print "%s [--url] [--fs-type] [--verbose|--quiet] [--parallel] \\" % \
1201 print "%s [--enable-sasl] [--cleanup] [--bin] [<test> ...]" \
1202 % (" " * len(prog_name
))
1203 print "%s " % (" " * len(prog_name
))
1204 print "%s [--list] [<test> ...]\n" % prog_name
1206 print " test The number of the test to run (multiple okay), " \
1209 print " --list Print test doc strings instead of running them"
1210 print " --fs-type Subversion file system type (fsfs or bdb)"
1211 print " --http-library DAV library to use (neon or serf)"
1212 print " --url Base url to the repos (e.g. svn://localhost)"
1213 print " --verbose Print binary command-lines (not with --quiet)"
1214 print " --quiet Print only unexpected results (not with --verbose)"
1215 print " --cleanup Whether to clean up"
1216 print " --enable-sasl Whether to enable SASL authentication"
1217 print " --parallel Run the tests in parallel"
1218 print " --bin Use the svn binaries installed in this path"
1219 print " --use-jsvn Use the jsvn (SVNKit based) binaries. Can be\n" \
1220 " combined with --bin to point to a specific path"
1221 print " --development Test development mode: provides more detailed test\n"\
1222 " output and ignores all exceptions in the \n" \
1223 " run_and_verify* functions. This option is only \n" \
1224 " useful during test development!"
1225 print " --server-minor-version Set the minor version for the server.\n" \
1226 " Supports version 4 or 5."
1227 print " --help This information"
1230 # Main func. This is the "entry point" that all the test scripts call
1231 # to run their list of tests.
1233 # This routine parses sys.argv to decide what to do.
1234 def run_tests(test_list
, serial_only
= False):
1235 """Main routine to run all tests in TEST_LIST.
1237 NOTE: this function does not return. It does a sys.exit() with the
1238 appropriate exit code.
1241 global test_area_url
1248 global is_child_process
1250 global svnadmin_binary
1251 global svnlook_binary
1252 global svnsync_binary
1253 global svnversion_binary
1254 global command_line_parsed
1256 global server_minor_version
1259 # Should the tests be listed (as opposed to executed)?
1267 opts
, args
= my_getopt(sys
.argv
[1:], 'vqhpc',
1268 ['url=', 'fs-type=', 'verbose', 'quiet', 'cleanup',
1269 'list', 'enable-sasl', 'help', 'parallel',
1270 'bin=', 'http-library=', 'server-minor-version=',
1271 'use-jsvn', 'development'])
1272 except getopt
.GetoptError
, e
:
1273 print "ERROR: %s\n" % e
1279 # This is an old deprecated variant of the "--list" option:
1281 elif arg
.startswith('BASE_URL='):
1282 test_area_url
= arg
[9:]
1285 testnums
.append(int(arg
))
1287 print "ERROR: invalid test number '%s'\n" % arg
1291 for opt
, val
in opts
:
1295 elif opt
== "--fs-type":
1298 elif opt
== "-v" or opt
== "--verbose":
1301 elif opt
== "-q" or opt
== "--quiet":
1304 elif opt
== "--cleanup":
1307 elif opt
== "--list":
1310 elif opt
== "--enable-sasl":
1313 elif opt
== "-h" or opt
== "--help":
1317 elif opt
== '-p' or opt
== "--parallel":
1318 parallel
= 5 # use 5 parallel threads.
1321 is_child_process
= True
1323 elif opt
== '--bin':
1326 elif opt
== '--http-library':
1329 elif opt
== '--server-minor-version':
1330 server_minor_version
= int(val
)
1331 if server_minor_version
< 4 or server_minor_version
> 6:
1332 print "ERROR: test harness only supports server minor version 4 or 5"
1335 elif opt
== '--use-jsvn':
1338 elif opt
== '--development':
1339 setup_development_mode()
1341 if test_area_url
[-1:] == '/': # Normalize url to have no trailing slash
1342 test_area_url
= test_area_url
[:-1]
1344 if verbose_mode
and quiet_mode
:
1345 sys
.stderr
.write("ERROR: 'verbose' and 'quiet' are incompatible\n")
1348 # Calculate pristine_url from test_area_url.
1349 pristine_url
= test_area_url
+ '/' + pristine_dir
1351 pristine_url
= pristine_url
.replace('\\', '/')
1356 svn_binary
= os
.path
.join(svn_bin
, 'jsvn' + _bat
)
1357 svnadmin_binary
= os
.path
.join(svn_bin
, 'jsvnadmin' + _bat
)
1358 svnlook_binary
= os
.path
.join(svn_bin
, 'jsvnlook' + _bat
)
1359 svnsync_binary
= os
.path
.join(svn_bin
, 'jsvnsync' + _bat
)
1360 svnversion_binary
= os
.path
.join(svn_bin
, 'jsvnversion' + _bat
)
1364 svn_binary
= os
.path
.join(svn_bin
, 'svn' + _exe
)
1365 svnadmin_binary
= os
.path
.join(svn_bin
, 'svnadmin' + _exe
)
1366 svnlook_binary
= os
.path
.join(svn_bin
, 'svnlook' + _exe
)
1367 svnsync_binary
= os
.path
.join(svn_bin
, 'svnsync' + _exe
)
1368 svnversion_binary
= os
.path
.join(svn_bin
, 'svnversion' + _exe
)
1370 command_line_parsed
= True
1372 ######################################################################
1374 # Cleanup: if a previous run crashed or interrupted the python
1375 # interpreter, then `temp_dir' was never removed. This can cause wonkiness.
1376 if not is_child_process
:
1377 safe_rmtree(temp_dir
, 1)
1380 # If no test numbers were listed explicitly, include all of them:
1381 testnums
= range(1, len(test_list
))
1384 print "Test # Mode Test Description"
1385 print "------ ----- ----------------"
1386 for testnum
in testnums
:
1387 TestRunner(test_list
[testnum
], testnum
).list()
1389 # done. just exit with success.
1392 # don't run tests in parallel when the tests don't support it or there
1393 # are only a few tests to run.
1394 if serial_only
or len(testnums
) < 2:
1397 # Setup the pristine repository
1398 actions
.setup_pristine_repository()
1400 # Build out the default configuration directory
1401 create_config_dir(default_config_dir
)
1404 exit_code
= _internal_run_tests(test_list
, testnums
, parallel
)
1406 # Remove all scratchwork: the 'pristine' repository, greek tree, etc.
1407 # This ensures that an 'import' will happen the next time we run.
1408 if not is_child_process
:
1409 safe_rmtree(temp_dir
, 1)
1411 # Cleanup after ourselves.
1412 _cleanup_deferred_test_paths()
1414 # Return the appropriate exit code from the tests.
1417 # the modules import each other, so we do this import very late, to ensure
1418 # that the definitions in "main" have been completed.