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
27 from svntest
.main
import server_has_partial_replay
30 Skip
= svntest
.testcase
.Skip
31 SkipUnless
= svntest
.testcase
.SkipUnless
32 XFail
= svntest
.testcase
.XFail
33 Item
= svntest
.wc
.StateItem
36 ######################################################################
40 def build_repos(sbox
):
41 """Avoid the use sbox.build() because we're working with a repos
42 other than the Greek tree."""
43 # Cleanup after the last run by removing any left-over repository.
44 svntest
.main
.safe_rmtree(sbox
.repo_dir
)
46 # Create an empty repository.
47 svntest
.main
.create_repos(sbox
.repo_dir
)
50 def run_sync(url
, expected_error
=None):
51 "Synchronize the mirror repository with the master"
52 exit_code
, output
, errput
= svntest
.main
.run_svnsync(
54 "--username", svntest
.main
.wc_author
,
55 "--password", svntest
.main
.wc_passwd
)
57 if expected_error
is None:
58 raise SVNUnexpectedStderr(errput
)
60 expected_error
= svntest
.verify
.RegexOutput(expected_error
,
62 svntest
.verify
.compare_and_display_lines(None, "STDERR",
63 expected_error
, errput
)
64 elif expected_error
is not None:
65 raise SVNExpectedStderr
66 if not output
and not expected_error
:
67 # should be: ['Committed revision 1.\n', 'Committed revision 2.\n']
68 raise SVNUnexpectedStdout("Missing stdout")
70 def run_copy_revprops(url
, expected_error
=None):
71 "Copy revprops to the mirror repository from the master"
72 exit_code
, output
, errput
= svntest
.main
.run_svnsync(
74 "--username", svntest
.main
.wc_author
,
75 "--password", svntest
.main
.wc_passwd
)
77 if expected_error
is None:
78 raise SVNUnexpectedStderr(errput
)
80 expected_error
= svntest
.verify
.RegexOutput(expected_error
,
82 svntest
.verify
.compare_and_display_lines(None, "STDERR",
83 expected_error
, errput
)
84 elif expected_error
is not None:
85 raise SVNExpectedStderr
86 if not output
and not expected_error
:
87 # should be: ['Copied properties for revision 1.\n',
88 # 'Copied properties for revision 2.\n']
89 raise SVNUnexpectedStdout("Missing stdout")
91 def run_init(dst_url
, src_url
):
92 "Initialize the mirror repository from the master"
93 exit_code
, output
, errput
= svntest
.main
.run_svnsync(
94 "initialize", dst_url
, src_url
,
95 "--username", svntest
.main
.wc_author
,
96 "--password", svntest
.main
.wc_passwd
)
98 raise SVNUnexpectedStderr(errput
)
99 if output
!= ['Copied properties for revision 0.\n']:
100 raise SVNUnexpectedStdout(output
)
103 def run_test(sbox
, dump_file_name
, subdir
= None, exp_dump_file_name
= None):
104 """Load a dump file, sync repositories, and compare contents with the original
105 or another dump file."""
107 # Create the empty master repository.
110 # This directory contains all the dump files
111 svnsync_tests_dir
= os
.path
.join(os
.path
.dirname(sys
.argv
[0]),
112 'svnsync_tests_data')
113 # Load the specified dump file into the master repository.
114 master_dumpfile_contents
= file(os
.path
.join(svnsync_tests_dir
,
115 dump_file_name
)).readlines()
116 svntest
.actions
.run_and_verify_load(sbox
.repo_dir
, master_dumpfile_contents
)
118 # Create the empty destination repository.
119 dest_sbox
= sbox
.clone_dependent()
120 build_repos(dest_sbox
)
122 # Setup the mirror repository. Feed it the UUID of the source repository.
123 exit_code
, output
, errput
= svntest
.main
.run_svnlook("uuid", sbox
.repo_dir
)
124 mirror_cfg
= ["SVN-fs-dump-format-version: 2\n",
125 "UUID: " + output
[0],
127 svntest
.actions
.run_and_verify_load(dest_sbox
.repo_dir
, mirror_cfg
)
129 # Create the revprop-change hook for this test
130 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
132 repo_url
= sbox
.repo_url
134 repo_url
= repo_url
+ subdir
135 run_init(dest_sbox
.repo_url
, repo_url
)
137 run_sync(dest_sbox
.repo_url
)
138 run_copy_revprops(dest_sbox
.repo_url
)
140 # Remove some SVNSync-specific housekeeping properties from the
141 # mirror repository in preparation for the comparison dump.
142 for prop_name
in ("svn:sync-from-url", "svn:sync-from-uuid",
143 "svn:sync-last-merged-rev"):
144 svntest
.actions
.run_and_verify_svn(
145 None, None, [], "propdel", "--revprop", "-r", "0",
146 prop_name
, dest_sbox
.repo_url
)
148 # Create a dump file from the mirror repository.
149 dest_dump
= svntest
.actions
.run_and_verify_dump(dest_sbox
.repo_dir
)
151 # Compare the dump produced by the mirror repository with either the original
152 # dump file (used to create the master repository) or another specified dump
154 if exp_dump_file_name
:
155 exp_master_dumpfile_contents
= file(os
.path
.join(svnsync_tests_dir
,
156 exp_dump_file_name
)).readlines()
158 exp_master_dumpfile_contents
= master_dumpfile_contents
160 svntest
.verify
.compare_and_display_lines(
161 "Dump files", "DUMP", exp_master_dumpfile_contents
, dest_dump
)
164 ######################################################################
167 #----------------------------------------------------------------------
169 def copy_and_modify(sbox
):
171 run_test(sbox
, "copy-and-modify.dump")
173 #----------------------------------------------------------------------
175 def copy_from_previous_version_and_modify(sbox
):
176 "copy from previous version and modify"
177 run_test(sbox
, "copy-from-previous-version-and-modify.dump")
179 #----------------------------------------------------------------------
181 def copy_from_previous_version(sbox
):
182 "copy from previous version"
183 run_test(sbox
, "copy-from-previous-version.dump")
185 #----------------------------------------------------------------------
187 def modified_in_place(sbox
):
189 run_test(sbox
, "modified-in-place.dump")
191 #----------------------------------------------------------------------
193 def tag_empty_trunk(sbox
):
195 run_test(sbox
, "tag-empty-trunk.dump")
197 #----------------------------------------------------------------------
199 def tag_trunk_with_dir(sbox
):
200 "tag trunk containing a sub-directory"
201 run_test(sbox
, "tag-trunk-with-dir.dump")
203 #----------------------------------------------------------------------
205 def tag_trunk_with_file(sbox
):
206 "tag trunk containing a file"
207 run_test(sbox
, "tag-trunk-with-file.dump")
209 #----------------------------------------------------------------------
211 def tag_trunk_with_file2(sbox
):
212 "tag trunk containing a file (#2)"
213 run_test(sbox
, "tag-trunk-with-file2.dump")
215 #----------------------------------------------------------------------
217 def tag_with_modified_file(sbox
):
218 "tag with a modified file"
219 run_test(sbox
, "tag-with-modified-file.dump")
221 #----------------------------------------------------------------------
223 def dir_prop_change(sbox
):
224 "directory property changes"
225 run_test(sbox
, "dir_prop_change.dump")
227 #----------------------------------------------------------------------
229 def file_dir_file(sbox
):
230 "files and dirs mixed together"
231 run_test(sbox
, "file-dir-file.dump")
233 #----------------------------------------------------------------------
235 def copy_parent_modify_prop(sbox
):
236 "copy parent and modify prop"
237 run_test(sbox
, "copy-parent-modify-prop.dump")
239 #----------------------------------------------------------------------
241 def detect_meddling(sbox
):
242 "detect non-svnsync commits in destination"
244 sbox
.build("svnsync-meddling")
246 dest_sbox
= sbox
.clone_dependent()
247 build_repos(dest_sbox
)
249 # Make our own destination checkout (have to do it ourself because
252 svntest
.main
.safe_rmtree(dest_sbox
.wc_dir
)
253 svntest
.actions
.run_and_verify_svn(None,
260 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
262 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
263 run_sync(dest_sbox
.repo_url
)
265 svntest
.actions
.run_and_verify_svn(None,
271 # Commit some change to the destination, which should be detected by svnsync
272 svntest
.main
.file_append(os
.path
.join(dest_sbox
.wc_dir
, 'A', 'B', 'lambda'),
274 svntest
.actions
.run_and_verify_svn(None,
281 run_sync(dest_sbox
.repo_url
,
282 ".*Destination HEAD \\(2\\) is not the last merged revision \\(1\\).*")
284 #----------------------------------------------------------------------
286 def basic_authz(sbox
):
287 "verify that unreadable content is not synced"
289 sbox
.build("svnsync-basic-authz")
291 write_restrictive_svnserve_conf(sbox
.repo_dir
)
293 dest_sbox
= sbox
.clone_dependent()
294 build_repos(dest_sbox
)
296 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
298 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
300 svntest
.main
.file_write(sbox
.authz_file
,
301 "[svnsync-basic-authz:/]\n"
304 "[svnsync-basic-authz:/A/B]\n"
307 "[svnsync-basic-authz-1:/]\n"
310 run_sync(dest_sbox
.repo_url
)
312 lambda_url
= dest_sbox
.repo_url
+ '/A/B/lambda'
314 # this file should have been blocked by authz
315 svntest
.actions
.run_and_verify_svn(None,
316 [], svntest
.verify
.AnyOutput
,
320 #----------------------------------------------------------------------
322 def copy_from_unreadable_dir(sbox
):
323 "verify that copies from unreadable dirs work"
325 sbox
.build("svnsync-copy-from-unreadable-dir")
327 B_url
= sbox
.repo_url
+ '/A/B'
328 P_url
= sbox
.repo_url
+ '/A/P'
330 # Set a property on the directory we're going to copy, and a file in it, to
331 # confirm that they're transmitted when we later sync the copied directory
332 svntest
.actions
.run_and_verify_svn(None,
338 sbox
.wc_dir
+ '/A/B/lambda')
340 svntest
.actions
.run_and_verify_svn(None,
346 sbox
.wc_dir
+ '/A/B')
348 svntest
.actions
.run_and_verify_svn(None,
352 sbox
.wc_dir
+ '/A/B',
355 # Now copy that directory so we'll see it in our synced copy
356 svntest
.actions
.run_and_verify_svn(None,
364 write_restrictive_svnserve_conf(sbox
.repo_dir
)
366 dest_sbox
= sbox
.clone_dependent()
367 build_repos(dest_sbox
)
369 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
371 fp
= open(sbox
.authz_file
, 'w')
373 # For mod_dav_svn's parent path setup we need per-repos permissions in
375 if sbox
.repo_url
.startswith('http'):
376 fp
.write("[svnsync-copy-from-unreadable-dir:/]\n" +
379 "[svnsync-copy-from-unreadable-dir:/A/B]\n" +
382 "[svnsync-copy-from-unreadable-dir-1:/]\n" +
385 # Otherwise we can just go with the permissions needed for the source
395 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
397 run_sync(dest_sbox
.repo_url
)
408 '\n', # log message is stripped
411 exit_code
, out
, err
= svntest
.main
.run_svn(None,
418 raise SVNUnexpectedStderr(err
)
420 svntest
.verify
.compare_and_display_lines(None,
425 svntest
.actions
.run_and_verify_svn(None,
430 dest_sbox
.repo_url
+ '/A/P/lambda')
432 svntest
.actions
.run_and_verify_svn(None,
437 dest_sbox
.repo_url
+ '/A/P')
440 def copy_with_mod_from_unreadable_dir(sbox
):
441 "verify copies with mods from unreadable dirs"
443 sbox
.build("svnsync-copy-with-mod-from-unreadable-dir")
445 # Make a copy of the B directory.
446 svntest
.actions
.run_and_verify_svn(None,
450 sbox
.wc_dir
+ '/A/B',
451 sbox
.wc_dir
+ '/A/P')
453 # Set a property inside the copied directory.
454 svntest
.actions
.run_and_verify_svn(None,
460 sbox
.wc_dir
+ '/A/P/lambda')
462 # Add a new directory and file inside the copied directory.
463 svntest
.actions
.run_and_verify_svn(None,
467 sbox
.wc_dir
+ '/A/P/NEW-DIR')
469 svntest
.main
.file_append(sbox
.wc_dir
+ '/A/P/E/new-file', "bla bla")
470 svntest
.main
.run_svn(None, 'add', sbox
.wc_dir
+ '/A/P/E/new-file')
472 # Delete a file inside the copied directory.
473 svntest
.actions
.run_and_verify_svn(None,
477 sbox
.wc_dir
+ '/A/P/E/beta')
479 # Commit the copy-with-modification.
480 svntest
.actions
.run_and_verify_svn(None,
487 # Lock down the source repository.
488 write_restrictive_svnserve_conf(sbox
.repo_dir
)
490 dest_sbox
= sbox
.clone_dependent()
491 build_repos(dest_sbox
)
493 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
495 fp
= open(sbox
.authz_file
, 'w')
497 # For mod_dav_svn's parent path setup we need per-repos permissions in
499 if sbox
.repo_url
.startswith('http'):
500 fp
.write("[svnsync-copy-with-mod-from-unreadable-dir:/]\n" +
503 "[svnsync-copy-with-mod-from-unreadable-dir:/A/B]\n" +
506 "[svnsync-copy-with-mod-from-unreadable-dir-1:/]\n" +
509 # Otherwise we can just go with the permissions needed for the source
519 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
521 run_sync(dest_sbox
.repo_url
)
528 ' A /A/P/E/new-file\n',
533 '\n', # log message is stripped
536 exit_code
, out
, err
= svntest
.main
.run_svn(None,
543 raise SVNUnexpectedStderr(err
)
545 svntest
.verify
.compare_and_display_lines(None,
550 svntest
.actions
.run_and_verify_svn(None,
555 dest_sbox
.repo_url
+ '/A/P/lambda')
558 def copy_with_mod_from_unreadable_dir_and_copy(sbox
):
559 "verify copies with mods from unreadable dirs +copy"
561 sbox
.build("svnsync-copy-with-mod-from-unreadable-dir-and-copy")
563 # Make a copy of the B directory.
564 svntest
.actions
.run_and_verify_svn(None,
568 sbox
.wc_dir
+ '/A/B',
569 sbox
.wc_dir
+ '/A/P')
572 # Copy a (readable) file into the copied directory.
573 svntest
.actions
.run_and_verify_svn(None,
577 sbox
.wc_dir
+ '/A/D/gamma',
578 sbox
.wc_dir
+ '/A/P/E')
581 # Commit the copy-with-modification.
582 svntest
.actions
.run_and_verify_svn(None,
589 # Lock down the source repository.
590 write_restrictive_svnserve_conf(sbox
.repo_dir
)
592 dest_sbox
= sbox
.clone_dependent()
593 build_repos(dest_sbox
)
595 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
597 fp
= open(sbox
.authz_file
, 'w')
599 # For mod_dav_svn's parent path setup we need per-repos permissions in
601 if sbox
.repo_url
.startswith('http'):
602 fp
.write("[svnsync-copy-with-mod-from-unreadable-dir-and-copy:/]\n" +
605 "[svnsync-copy-with-mod-from-unreadable-dir-and-copy:/A/B]\n" +
608 "[svnsync-copy-with-mod-from-unreadable-dir-and-copy-1:/]\n" +
611 # Otherwise we can just go with the permissions needed for the source
621 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
623 run_sync(dest_sbox
.repo_url
)
631 ' A /A/P/E/gamma (from /A/D/gamma:1)\n',
635 '\n', # log message is stripped
638 exit_code
, out
, err
= svntest
.main
.run_svn(None,
645 raise SVNUnexpectedStderr(err
)
647 svntest
.verify
.compare_and_display_lines(None,
652 def url_encoding(sbox
):
653 "test url encoding issues"
654 run_test(sbox
, "url-encoding-bug.dump")
657 # A test for copying revisions that lack a property that already exists
658 # on the destination rev as part of the commit (i.e. svn:author in this
659 # case, but svn:date would also work).
661 "test copying revs with no svn:author revprops"
662 run_test(sbox
, "no-author.dump")
664 def copy_revprops(sbox
):
665 "test copying revprops other than svn:*"
666 run_test(sbox
, "revprops.dump")
668 def only_trunk(sbox
):
669 "test syncing subdirectories"
670 run_test(sbox
, "svnsync-trunk-only.dump", "/trunk",
671 "svnsync-trunk-only.expected.dump")
673 def only_trunk_A_with_changes(sbox
):
674 "test syncing subdirectories with changes on root"
675 run_test(sbox
, "svnsync-trunk-A-changes.dump", "/trunk/A",
676 "svnsync-trunk-A-changes.expected.dump")
678 # test for issue #2904
679 def move_and_modify_in_the_same_revision(sbox
):
680 "test move parent and modify child file in same rev"
681 run_test(sbox
, "svnsync-move-and-modify.dump")
683 ########################################################################
687 # list all tests here, starting with None:
690 copy_from_previous_version_and_modify
,
691 copy_from_previous_version
,
695 tag_trunk_with_file2
,
697 tag_with_modified_file
,
700 copy_parent_modify_prop
,
702 Skip(basic_authz
, svntest
.main
.is_ra_type_file
),
703 Skip(copy_from_unreadable_dir
, svntest
.main
.is_ra_type_file
),
704 Skip(copy_with_mod_from_unreadable_dir
,
705 svntest
.main
.is_ra_type_file
),
706 Skip(copy_with_mod_from_unreadable_dir_and_copy
,
707 svntest
.main
.is_ra_type_file
),
711 SkipUnless(only_trunk
,
712 server_has_partial_replay
),
713 SkipUnless(only_trunk_A_with_changes
,
714 server_has_partial_replay
),
715 move_and_modify_in_the_same_revision
,
718 if __name__
== '__main__':
719 svntest
.main
.run_tests(test_list
, serial_only
= True)