3 # svnsync_tests.py: Tests SVNSync's repository mirroring capabilities.
5 # Subversion is a tool for revision control.
6 # See http://subversion.tigris.org for more information.
8 # ====================================================================
9 # Copyright (c) 2005-2007 CollabNet. All rights reserved.
11 # This software is licensed as described in the file COPYING, which
12 # you should have received as part of this distribution. The terms
13 # are also available at http://subversion.tigris.org/license-1.html.
14 # If newer versions of this license are posted there, you may use a
15 # newer version instead, at your option.
17 ######################################################################
24 from svntest
.verify
import SVNUnexpectedStdout
, SVNUnexpectedStderr
25 from svntest
.verify
import SVNExpectedStderr
26 from svntest
.main
import write_restrictive_svnserve_conf
29 Skip
= svntest
.testcase
.Skip
30 XFail
= svntest
.testcase
.XFail
31 Item
= svntest
.wc
.StateItem
34 ######################################################################
38 def build_repos(sbox
):
39 """Avoid the use sbox.build() because we're working with a repos
40 other than the Greek tree."""
41 # Cleanup after the last run by removing any left-over repository.
42 svntest
.main
.safe_rmtree(sbox
.repo_dir
)
44 # Create an empty repository.
45 svntest
.main
.create_repos(sbox
.repo_dir
)
48 def run_sync(url
, expected_error
=None):
49 "Synchronize the mirror repository with the master"
50 output
, errput
= svntest
.main
.run_svnsync(
52 "--username", svntest
.main
.wc_author
,
53 "--password", svntest
.main
.wc_passwd
)
55 if expected_error
is None:
56 raise SVNUnexpectedStderr(errput
)
58 expected_error
= svntest
.verify
.RegexOutput(expected_error
,
60 svntest
.verify
.compare_and_display_lines(None, "STDERR",
61 expected_error
, errput
)
62 elif expected_error
is not None:
63 raise SVNExpectedStderr
64 if not output
and not expected_error
:
65 # should be: ['Committed revision 1.\n', 'Committed revision 2.\n']
66 raise SVNUnexpectedStdout("Missing stdout")
68 def run_init(dst_url
, src_url
):
69 "Initialize the mirror repository from the master"
70 output
, errput
= svntest
.main
.run_svnsync(
71 "initialize", dst_url
, src_url
,
72 "--username", svntest
.main
.wc_author
,
73 "--password", svntest
.main
.wc_passwd
)
75 raise SVNUnexpectedStderr(errput
)
76 if output
!= ['Copied properties for revision 0.\n']:
77 raise SVNUnexpectedStdout(output
)
80 def run_test(sbox
, dump_file_name
, subdir
= None, exp_dump_file_name
= None):
81 """Load a dump file, sync repositories, and compare contents with the original
82 or another dump file."""
84 # Create the empty master repository.
87 # This directory contains all the dump files
88 svnsync_tests_dir
= os
.path
.join(os
.path
.dirname(sys
.argv
[0]),
90 # Load the specified dump file into the master repository.
91 master_dumpfile_contents
= file(os
.path
.join(svnsync_tests_dir
,
92 dump_file_name
)).readlines()
93 svntest
.actions
.run_and_verify_load(sbox
.repo_dir
, master_dumpfile_contents
)
95 # Create the empty destination repository.
96 dest_sbox
= sbox
.clone_dependent()
97 build_repos(dest_sbox
)
99 # Setup the mirror repository. Feed it the UUID of the source repository.
100 output
, errput
= svntest
.main
.run_svnlook("uuid", sbox
.repo_dir
)
101 mirror_cfg
= ["SVN-fs-dump-format-version: 2\n",
102 "UUID: " + output
[0],
104 svntest
.actions
.run_and_verify_load(dest_sbox
.repo_dir
, mirror_cfg
)
106 # Create the revprop-change hook for this test
107 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
109 repo_url
= sbox
.repo_url
111 repo_url
= repo_url
+ subdir
112 run_init(dest_sbox
.repo_url
, repo_url
)
114 run_sync(dest_sbox
.repo_url
)
116 # Remove some SVNSync-specific housekeeping properties from the
117 # mirror repository in preparation for the comparison dump.
118 for prop_name
in ("svn:sync-from-url", "svn:sync-from-uuid",
119 "svn:sync-last-merged-rev"):
120 svntest
.actions
.run_and_verify_svn(
121 None, None, [], "propdel", "--revprop", "-r", "0",
122 prop_name
, dest_sbox
.repo_url
)
124 # Create a dump file from the mirror repository.
125 dest_dump
= svntest
.actions
.run_and_verify_dump(dest_sbox
.repo_dir
)
127 # Compare the dump produced by the mirror repository with either the original
128 # dump file (used to create the master repository) or another specified dump
130 if exp_dump_file_name
:
131 exp_master_dumpfile_contents
= file(os
.path
.join(svnsync_tests_dir
,
132 exp_dump_file_name
)).readlines()
134 exp_master_dumpfile_contents
= master_dumpfile_contents
136 svntest
.verify
.compare_and_display_lines(
137 "Dump files", "DUMP", exp_master_dumpfile_contents
, dest_dump
)
140 ######################################################################
143 #----------------------------------------------------------------------
145 def copy_and_modify(sbox
):
147 run_test(sbox
, "copy-and-modify.dump")
149 #----------------------------------------------------------------------
151 def copy_from_previous_version_and_modify(sbox
):
152 "copy from previous version and modify"
153 run_test(sbox
, "copy-from-previous-version-and-modify.dump")
155 #----------------------------------------------------------------------
157 def copy_from_previous_version(sbox
):
158 "copy from previous version"
159 run_test(sbox
, "copy-from-previous-version.dump")
161 #----------------------------------------------------------------------
163 def modified_in_place(sbox
):
165 run_test(sbox
, "modified-in-place.dump")
167 #----------------------------------------------------------------------
169 def tag_empty_trunk(sbox
):
171 run_test(sbox
, "tag-empty-trunk.dump")
173 #----------------------------------------------------------------------
175 def tag_trunk_with_dir(sbox
):
176 "tag trunk containing a sub-directory"
177 run_test(sbox
, "tag-trunk-with-dir.dump")
179 #----------------------------------------------------------------------
181 def tag_trunk_with_file(sbox
):
182 "tag trunk containing a file"
183 run_test(sbox
, "tag-trunk-with-file.dump")
185 #----------------------------------------------------------------------
187 def tag_trunk_with_file2(sbox
):
188 "tag trunk containing a file (#2)"
189 run_test(sbox
, "tag-trunk-with-file2.dump")
191 #----------------------------------------------------------------------
193 def tag_with_modified_file(sbox
):
194 "tag with a modified file"
195 run_test(sbox
, "tag-with-modified-file.dump")
197 #----------------------------------------------------------------------
199 def dir_prop_change(sbox
):
200 "directory property changes"
201 run_test(sbox
, "dir_prop_change.dump")
203 #----------------------------------------------------------------------
205 def file_dir_file(sbox
):
206 "files and dirs mixed together"
207 run_test(sbox
, "file-dir-file.dump")
209 #----------------------------------------------------------------------
211 def copy_parent_modify_prop(sbox
):
212 "copy parent and modify prop"
213 run_test(sbox
, "copy-parent-modify-prop.dump")
215 #----------------------------------------------------------------------
217 def detect_meddling(sbox
):
218 "detect non-svnsync commits in destination"
220 sbox
.build("svnsync-meddling")
222 dest_sbox
= sbox
.clone_dependent()
223 build_repos(dest_sbox
)
225 # Make our own destination checkout (have to do it ourself because
228 svntest
.main
.safe_rmtree(dest_sbox
.wc_dir
)
229 svntest
.actions
.run_and_verify_svn(None,
236 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
238 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
239 run_sync(dest_sbox
.repo_url
)
241 svntest
.actions
.run_and_verify_svn(None,
247 # Commit some change to the destination, which should be detected by svnsync
248 svntest
.main
.file_append(os
.path
.join(dest_sbox
.wc_dir
, 'A', 'B', 'lambda'),
250 svntest
.actions
.run_and_verify_svn(None,
257 run_sync(dest_sbox
.repo_url
,
258 ".*Destination HEAD \\(2\\) is not the last merged revision \\(1\\).*")
260 #----------------------------------------------------------------------
262 def basic_authz(sbox
):
263 "verify that unreadable content is not synced"
265 sbox
.build("svnsync-basic-authz")
267 write_restrictive_svnserve_conf(sbox
.repo_dir
)
269 dest_sbox
= sbox
.clone_dependent()
270 build_repos(dest_sbox
)
272 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
274 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
276 svntest
.main
.file_write(sbox
.authz_file
,
277 "[svnsync-basic-authz:/]\n"
280 "[svnsync-basic-authz:/A/B]\n"
283 "[svnsync-basic-authz-1:/]\n"
286 run_sync(dest_sbox
.repo_url
)
288 lambda_url
= dest_sbox
.repo_url
+ '/A/B/lambda'
290 # this file should have been blocked by authz
291 svntest
.actions
.run_and_verify_svn(None,
292 [], svntest
.verify
.AnyOutput
,
296 #----------------------------------------------------------------------
298 def copy_from_unreadable_dir(sbox
):
299 "verify that copies from unreadable dirs work"
301 sbox
.build("svnsync-copy-from-unreadable-dir")
303 B_url
= sbox
.repo_url
+ '/A/B'
304 P_url
= sbox
.repo_url
+ '/A/P'
306 # Set a property on the directory we're going to copy, and a file in it, to
307 # confirm that they're transmitted when we later sync the copied directory
308 svntest
.actions
.run_and_verify_svn(None,
314 sbox
.wc_dir
+ '/A/B/lambda')
316 svntest
.actions
.run_and_verify_svn(None,
322 sbox
.wc_dir
+ '/A/B')
324 svntest
.actions
.run_and_verify_svn(None,
328 sbox
.wc_dir
+ '/A/B',
331 # Now copy that directory so we'll see it in our synced copy
332 svntest
.actions
.run_and_verify_svn(None,
340 write_restrictive_svnserve_conf(sbox
.repo_dir
)
342 dest_sbox
= sbox
.clone_dependent()
343 build_repos(dest_sbox
)
345 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
347 fp
= open(sbox
.authz_file
, 'w')
349 # For mod_dav_svn's parent path setup we need per-repos permissions in
351 if sbox
.repo_url
.startswith('http'):
352 fp
.write("[svnsync-copy-from-unreadable-dir:/]\n" +
355 "[svnsync-copy-from-unreadable-dir:/A/B]\n" +
358 "[svnsync-copy-from-unreadable-dir-1:/]\n" +
361 # Otherwise we can just go with the permissions needed for the source
371 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
373 run_sync(dest_sbox
.repo_url
)
384 '\n', # log message is stripped
387 out
, err
= svntest
.main
.run_svn(None,
394 raise SVNUnexpectedStderr(err
)
396 svntest
.verify
.compare_and_display_lines(None,
401 svntest
.actions
.run_and_verify_svn(None,
406 dest_sbox
.repo_url
+ '/A/P/lambda')
408 svntest
.actions
.run_and_verify_svn(None,
413 dest_sbox
.repo_url
+ '/A/P')
416 def copy_with_mod_from_unreadable_dir(sbox
):
417 "verify copies with mods from unreadable dirs"
419 sbox
.build("svnsync-copy-with-mod-from-unreadable-dir")
421 # Make a copy of the B directory.
422 svntest
.actions
.run_and_verify_svn(None,
426 sbox
.wc_dir
+ '/A/B',
427 sbox
.wc_dir
+ '/A/P')
429 # Set a property inside the copied directory.
430 svntest
.actions
.run_and_verify_svn(None,
436 sbox
.wc_dir
+ '/A/P/lambda')
438 # Add a new directory and file inside the copied directory.
439 svntest
.actions
.run_and_verify_svn(None,
443 sbox
.wc_dir
+ '/A/P/NEW-DIR')
445 svntest
.main
.file_append(sbox
.wc_dir
+ '/A/P/E/new-file', "bla bla")
446 svntest
.main
.run_svn(None, 'add', sbox
.wc_dir
+ '/A/P/E/new-file')
448 # Delete a file inside the copied directory.
449 svntest
.actions
.run_and_verify_svn(None,
453 sbox
.wc_dir
+ '/A/P/E/beta')
455 # Commit the copy-with-modification.
456 svntest
.actions
.run_and_verify_svn(None,
463 # Lock down the source repository.
464 write_restrictive_svnserve_conf(sbox
.repo_dir
)
466 dest_sbox
= sbox
.clone_dependent()
467 build_repos(dest_sbox
)
469 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
471 fp
= open(sbox
.authz_file
, 'w')
473 # For mod_dav_svn's parent path setup we need per-repos permissions in
475 if sbox
.repo_url
.startswith('http'):
476 fp
.write("[svnsync-copy-with-mod-from-unreadable-dir:/]\n" +
479 "[svnsync-copy-with-mod-from-unreadable-dir:/A/B]\n" +
482 "[svnsync-copy-with-mod-from-unreadable-dir-1:/]\n" +
485 # Otherwise we can just go with the permissions needed for the source
495 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
497 run_sync(dest_sbox
.repo_url
)
504 ' A /A/P/E/new-file\n',
509 '\n', # log message is stripped
512 out
, err
= svntest
.main
.run_svn(None,
519 raise SVNUnexpectedStderr(err
)
521 svntest
.verify
.compare_and_display_lines(None,
526 svntest
.actions
.run_and_verify_svn(None,
531 dest_sbox
.repo_url
+ '/A/P/lambda')
534 def copy_with_mod_from_unreadable_dir_and_copy(sbox
):
535 "verify copies with mods from unreadable dirs +copy"
537 sbox
.build("svnsync-copy-with-mod-from-unreadable-dir-and-copy")
539 # Make a copy of the B directory.
540 svntest
.actions
.run_and_verify_svn(None,
544 sbox
.wc_dir
+ '/A/B',
545 sbox
.wc_dir
+ '/A/P')
548 # Copy a (readable) file into the copied directory.
549 svntest
.actions
.run_and_verify_svn(None,
553 sbox
.wc_dir
+ '/A/D/gamma',
554 sbox
.wc_dir
+ '/A/P/E')
557 # Commit the copy-with-modification.
558 svntest
.actions
.run_and_verify_svn(None,
565 # Lock down the source repository.
566 write_restrictive_svnserve_conf(sbox
.repo_dir
)
568 dest_sbox
= sbox
.clone_dependent()
569 build_repos(dest_sbox
)
571 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
573 fp
= open(sbox
.authz_file
, 'w')
575 # For mod_dav_svn's parent path setup we need per-repos permissions in
577 if sbox
.repo_url
.startswith('http'):
578 fp
.write("[svnsync-copy-with-mod-from-unreadable-dir-and-copy:/]\n" +
581 "[svnsync-copy-with-mod-from-unreadable-dir-and-copy:/A/B]\n" +
584 "[svnsync-copy-with-mod-from-unreadable-dir-and-copy-1:/]\n" +
587 # Otherwise we can just go with the permissions needed for the source
597 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
599 run_sync(dest_sbox
.repo_url
)
607 ' A /A/P/E/gamma (from /A/D/gamma:1)\n',
611 '\n', # log message is stripped
614 out
, err
= svntest
.main
.run_svn(None,
621 raise SVNUnexpectedStderr(err
)
623 svntest
.verify
.compare_and_display_lines(None,
628 def url_encoding(sbox
):
629 "test url encoding issues"
630 run_test(sbox
, "url-encoding-bug.dump")
633 # A test for copying revisions that lack a property that already exists
634 # on the destination rev as part of the commit (i.e. svn:author in this
635 # case, but svn:date would also work).
637 "test copying revs with no svn:author revprops"
638 run_test(sbox
, "no-author.dump")
640 def copy_revprops(sbox
):
641 "test copying revprops other than svn:*"
642 run_test(sbox
, "revprops.dump")
644 def only_trunk(sbox
):
645 "test syncing subdirectories"
646 run_test(sbox
, "svnsync-trunk-only.dump", "/trunk",
647 "svnsync-trunk-only.expected.dump")
649 def only_trunk_A_with_changes(sbox
):
650 "test syncing subdirectories with changes on root"
651 run_test(sbox
, "svnsync-trunk-A-changes.dump", "/trunk/A",
652 "svnsync-trunk-A-changes.expected.dump")
654 # test for issue #2904
655 def move_and_modify_in_the_same_revision(sbox
):
656 "test move parent and modify child file in same rev"
657 run_test(sbox
, "svnsync-move-and-modify.dump")
659 ########################################################################
663 # list all tests here, starting with None:
666 copy_from_previous_version_and_modify
,
667 copy_from_previous_version
,
671 tag_trunk_with_file2
,
673 tag_with_modified_file
,
676 copy_parent_modify_prop
,
678 Skip(basic_authz
, svntest
.main
.is_ra_type_file
),
679 Skip(copy_from_unreadable_dir
, svntest
.main
.is_ra_type_file
),
680 Skip(copy_with_mod_from_unreadable_dir
,
681 svntest
.main
.is_ra_type_file
),
682 Skip(copy_with_mod_from_unreadable_dir_and_copy
,
683 svntest
.main
.is_ra_type_file
),
688 only_trunk_A_with_changes
,
689 move_and_modify_in_the_same_revision
,
692 if __name__
== '__main__':
693 svntest
.main
.run_tests(test_list
, serial_only
= True)