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 exit_code
, 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_copy_revprops(url
, expected_error
=None):
69 "Copy revprops to the mirror repository from the master"
70 exit_code
, output
, errput
= svntest
.main
.run_svnsync(
72 "--username", svntest
.main
.wc_author
,
73 "--password", svntest
.main
.wc_passwd
)
75 if expected_error
is None:
76 raise SVNUnexpectedStderr(errput
)
78 expected_error
= svntest
.verify
.RegexOutput(expected_error
,
80 svntest
.verify
.compare_and_display_lines(None, "STDERR",
81 expected_error
, errput
)
82 elif expected_error
is not None:
83 raise SVNExpectedStderr
84 if not output
and not expected_error
:
85 # should be: ['Copied properties for revision 1.\n',
86 # 'Copied properties for revision 2.\n']
87 raise SVNUnexpectedStdout("Missing stdout")
89 def run_init(dst_url
, src_url
):
90 "Initialize the mirror repository from the master"
91 exit_code
, output
, errput
= svntest
.main
.run_svnsync(
92 "initialize", dst_url
, src_url
,
93 "--username", svntest
.main
.wc_author
,
94 "--password", svntest
.main
.wc_passwd
)
96 raise SVNUnexpectedStderr(errput
)
97 if output
!= ['Copied properties for revision 0.\n']:
98 raise SVNUnexpectedStdout(output
)
101 def run_test(sbox
, dump_file_name
, subdir
= None, exp_dump_file_name
= None):
102 """Load a dump file, sync repositories, and compare contents with the original
103 or another dump file."""
105 # Create the empty master repository.
108 # This directory contains all the dump files
109 svnsync_tests_dir
= os
.path
.join(os
.path
.dirname(sys
.argv
[0]),
110 'svnsync_tests_data')
111 # Load the specified dump file into the master repository.
112 master_dumpfile_contents
= file(os
.path
.join(svnsync_tests_dir
,
113 dump_file_name
)).readlines()
114 svntest
.actions
.run_and_verify_load(sbox
.repo_dir
, master_dumpfile_contents
)
116 # Create the empty destination repository.
117 dest_sbox
= sbox
.clone_dependent()
118 build_repos(dest_sbox
)
120 # Setup the mirror repository. Feed it the UUID of the source repository.
121 exit_code
, output
, errput
= svntest
.main
.run_svnlook("uuid", sbox
.repo_dir
)
122 mirror_cfg
= ["SVN-fs-dump-format-version: 2\n",
123 "UUID: " + output
[0],
125 svntest
.actions
.run_and_verify_load(dest_sbox
.repo_dir
, mirror_cfg
)
127 # Create the revprop-change hook for this test
128 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
130 repo_url
= sbox
.repo_url
132 repo_url
= repo_url
+ subdir
133 run_init(dest_sbox
.repo_url
, repo_url
)
135 run_sync(dest_sbox
.repo_url
)
136 run_copy_revprops(dest_sbox
.repo_url
)
138 # Remove some SVNSync-specific housekeeping properties from the
139 # mirror repository in preparation for the comparison dump.
140 for prop_name
in ("svn:sync-from-url", "svn:sync-from-uuid",
141 "svn:sync-last-merged-rev"):
142 svntest
.actions
.run_and_verify_svn(
143 None, None, [], "propdel", "--revprop", "-r", "0",
144 prop_name
, dest_sbox
.repo_url
)
146 # Create a dump file from the mirror repository.
147 dest_dump
= svntest
.actions
.run_and_verify_dump(dest_sbox
.repo_dir
)
149 # Compare the dump produced by the mirror repository with either the original
150 # dump file (used to create the master repository) or another specified dump
152 if exp_dump_file_name
:
153 exp_master_dumpfile_contents
= file(os
.path
.join(svnsync_tests_dir
,
154 exp_dump_file_name
)).readlines()
156 exp_master_dumpfile_contents
= master_dumpfile_contents
158 svntest
.verify
.compare_and_display_lines(
159 "Dump files", "DUMP", exp_master_dumpfile_contents
, dest_dump
)
162 ######################################################################
165 #----------------------------------------------------------------------
167 def copy_and_modify(sbox
):
169 run_test(sbox
, "copy-and-modify.dump")
171 #----------------------------------------------------------------------
173 def copy_from_previous_version_and_modify(sbox
):
174 "copy from previous version and modify"
175 run_test(sbox
, "copy-from-previous-version-and-modify.dump")
177 #----------------------------------------------------------------------
179 def copy_from_previous_version(sbox
):
180 "copy from previous version"
181 run_test(sbox
, "copy-from-previous-version.dump")
183 #----------------------------------------------------------------------
185 def modified_in_place(sbox
):
187 run_test(sbox
, "modified-in-place.dump")
189 #----------------------------------------------------------------------
191 def tag_empty_trunk(sbox
):
193 run_test(sbox
, "tag-empty-trunk.dump")
195 #----------------------------------------------------------------------
197 def tag_trunk_with_dir(sbox
):
198 "tag trunk containing a sub-directory"
199 run_test(sbox
, "tag-trunk-with-dir.dump")
201 #----------------------------------------------------------------------
203 def tag_trunk_with_file(sbox
):
204 "tag trunk containing a file"
205 run_test(sbox
, "tag-trunk-with-file.dump")
207 #----------------------------------------------------------------------
209 def tag_trunk_with_file2(sbox
):
210 "tag trunk containing a file (#2)"
211 run_test(sbox
, "tag-trunk-with-file2.dump")
213 #----------------------------------------------------------------------
215 def tag_with_modified_file(sbox
):
216 "tag with a modified file"
217 run_test(sbox
, "tag-with-modified-file.dump")
219 #----------------------------------------------------------------------
221 def dir_prop_change(sbox
):
222 "directory property changes"
223 run_test(sbox
, "dir_prop_change.dump")
225 #----------------------------------------------------------------------
227 def file_dir_file(sbox
):
228 "files and dirs mixed together"
229 run_test(sbox
, "file-dir-file.dump")
231 #----------------------------------------------------------------------
233 def copy_parent_modify_prop(sbox
):
234 "copy parent and modify prop"
235 run_test(sbox
, "copy-parent-modify-prop.dump")
237 #----------------------------------------------------------------------
239 def detect_meddling(sbox
):
240 "detect non-svnsync commits in destination"
242 sbox
.build("svnsync-meddling")
244 dest_sbox
= sbox
.clone_dependent()
245 build_repos(dest_sbox
)
247 # Make our own destination checkout (have to do it ourself because
250 svntest
.main
.safe_rmtree(dest_sbox
.wc_dir
)
251 svntest
.actions
.run_and_verify_svn(None,
258 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
260 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
261 run_sync(dest_sbox
.repo_url
)
263 svntest
.actions
.run_and_verify_svn(None,
269 # Commit some change to the destination, which should be detected by svnsync
270 svntest
.main
.file_append(os
.path
.join(dest_sbox
.wc_dir
, 'A', 'B', 'lambda'),
272 svntest
.actions
.run_and_verify_svn(None,
279 run_sync(dest_sbox
.repo_url
,
280 ".*Destination HEAD \\(2\\) is not the last merged revision \\(1\\).*")
282 #----------------------------------------------------------------------
284 def basic_authz(sbox
):
285 "verify that unreadable content is not synced"
287 sbox
.build("svnsync-basic-authz")
289 write_restrictive_svnserve_conf(sbox
.repo_dir
)
291 dest_sbox
= sbox
.clone_dependent()
292 build_repos(dest_sbox
)
294 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
296 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
298 svntest
.main
.file_write(sbox
.authz_file
,
299 "[svnsync-basic-authz:/]\n"
302 "[svnsync-basic-authz:/A/B]\n"
305 "[svnsync-basic-authz-1:/]\n"
308 run_sync(dest_sbox
.repo_url
)
310 lambda_url
= dest_sbox
.repo_url
+ '/A/B/lambda'
312 # this file should have been blocked by authz
313 svntest
.actions
.run_and_verify_svn(None,
314 [], svntest
.verify
.AnyOutput
,
318 #----------------------------------------------------------------------
320 def copy_from_unreadable_dir(sbox
):
321 "verify that copies from unreadable dirs work"
323 sbox
.build("svnsync-copy-from-unreadable-dir")
325 B_url
= sbox
.repo_url
+ '/A/B'
326 P_url
= sbox
.repo_url
+ '/A/P'
328 # Set a property on the directory we're going to copy, and a file in it, to
329 # confirm that they're transmitted when we later sync the copied directory
330 svntest
.actions
.run_and_verify_svn(None,
336 sbox
.wc_dir
+ '/A/B/lambda')
338 svntest
.actions
.run_and_verify_svn(None,
344 sbox
.wc_dir
+ '/A/B')
346 svntest
.actions
.run_and_verify_svn(None,
350 sbox
.wc_dir
+ '/A/B',
353 # Now copy that directory so we'll see it in our synced copy
354 svntest
.actions
.run_and_verify_svn(None,
362 write_restrictive_svnserve_conf(sbox
.repo_dir
)
364 dest_sbox
= sbox
.clone_dependent()
365 build_repos(dest_sbox
)
367 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
369 fp
= open(sbox
.authz_file
, 'w')
371 # For mod_dav_svn's parent path setup we need per-repos permissions in
373 if sbox
.repo_url
.startswith('http'):
374 fp
.write("[svnsync-copy-from-unreadable-dir:/]\n" +
377 "[svnsync-copy-from-unreadable-dir:/A/B]\n" +
380 "[svnsync-copy-from-unreadable-dir-1:/]\n" +
383 # Otherwise we can just go with the permissions needed for the source
393 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
395 run_sync(dest_sbox
.repo_url
)
406 '\n', # log message is stripped
409 exit_code
, out
, err
= svntest
.main
.run_svn(None,
416 raise SVNUnexpectedStderr(err
)
418 svntest
.verify
.compare_and_display_lines(None,
423 svntest
.actions
.run_and_verify_svn(None,
428 dest_sbox
.repo_url
+ '/A/P/lambda')
430 svntest
.actions
.run_and_verify_svn(None,
435 dest_sbox
.repo_url
+ '/A/P')
438 def copy_with_mod_from_unreadable_dir(sbox
):
439 "verify copies with mods from unreadable dirs"
441 sbox
.build("svnsync-copy-with-mod-from-unreadable-dir")
443 # Make a copy of the B directory.
444 svntest
.actions
.run_and_verify_svn(None,
448 sbox
.wc_dir
+ '/A/B',
449 sbox
.wc_dir
+ '/A/P')
451 # Set a property inside the copied directory.
452 svntest
.actions
.run_and_verify_svn(None,
458 sbox
.wc_dir
+ '/A/P/lambda')
460 # Add a new directory and file inside the copied directory.
461 svntest
.actions
.run_and_verify_svn(None,
465 sbox
.wc_dir
+ '/A/P/NEW-DIR')
467 svntest
.main
.file_append(sbox
.wc_dir
+ '/A/P/E/new-file', "bla bla")
468 svntest
.main
.run_svn(None, 'add', sbox
.wc_dir
+ '/A/P/E/new-file')
470 # Delete a file inside the copied directory.
471 svntest
.actions
.run_and_verify_svn(None,
475 sbox
.wc_dir
+ '/A/P/E/beta')
477 # Commit the copy-with-modification.
478 svntest
.actions
.run_and_verify_svn(None,
485 # Lock down the source repository.
486 write_restrictive_svnserve_conf(sbox
.repo_dir
)
488 dest_sbox
= sbox
.clone_dependent()
489 build_repos(dest_sbox
)
491 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
493 fp
= open(sbox
.authz_file
, 'w')
495 # For mod_dav_svn's parent path setup we need per-repos permissions in
497 if sbox
.repo_url
.startswith('http'):
498 fp
.write("[svnsync-copy-with-mod-from-unreadable-dir:/]\n" +
501 "[svnsync-copy-with-mod-from-unreadable-dir:/A/B]\n" +
504 "[svnsync-copy-with-mod-from-unreadable-dir-1:/]\n" +
507 # Otherwise we can just go with the permissions needed for the source
517 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
519 run_sync(dest_sbox
.repo_url
)
526 ' A /A/P/E/new-file\n',
531 '\n', # log message is stripped
534 exit_code
, out
, err
= svntest
.main
.run_svn(None,
541 raise SVNUnexpectedStderr(err
)
543 svntest
.verify
.compare_and_display_lines(None,
548 svntest
.actions
.run_and_verify_svn(None,
553 dest_sbox
.repo_url
+ '/A/P/lambda')
556 def copy_with_mod_from_unreadable_dir_and_copy(sbox
):
557 "verify copies with mods from unreadable dirs +copy"
559 sbox
.build("svnsync-copy-with-mod-from-unreadable-dir-and-copy")
561 # Make a copy of the B directory.
562 svntest
.actions
.run_and_verify_svn(None,
566 sbox
.wc_dir
+ '/A/B',
567 sbox
.wc_dir
+ '/A/P')
570 # Copy a (readable) file into the copied directory.
571 svntest
.actions
.run_and_verify_svn(None,
575 sbox
.wc_dir
+ '/A/D/gamma',
576 sbox
.wc_dir
+ '/A/P/E')
579 # Commit the copy-with-modification.
580 svntest
.actions
.run_and_verify_svn(None,
587 # Lock down the source repository.
588 write_restrictive_svnserve_conf(sbox
.repo_dir
)
590 dest_sbox
= sbox
.clone_dependent()
591 build_repos(dest_sbox
)
593 svntest
.actions
.enable_revprop_changes(dest_sbox
.repo_dir
)
595 fp
= open(sbox
.authz_file
, 'w')
597 # For mod_dav_svn's parent path setup we need per-repos permissions in
599 if sbox
.repo_url
.startswith('http'):
600 fp
.write("[svnsync-copy-with-mod-from-unreadable-dir-and-copy:/]\n" +
603 "[svnsync-copy-with-mod-from-unreadable-dir-and-copy:/A/B]\n" +
606 "[svnsync-copy-with-mod-from-unreadable-dir-and-copy-1:/]\n" +
609 # Otherwise we can just go with the permissions needed for the source
619 run_init(dest_sbox
.repo_url
, sbox
.repo_url
)
621 run_sync(dest_sbox
.repo_url
)
629 ' A /A/P/E/gamma (from /A/D/gamma:1)\n',
633 '\n', # log message is stripped
636 exit_code
, out
, err
= svntest
.main
.run_svn(None,
643 raise SVNUnexpectedStderr(err
)
645 svntest
.verify
.compare_and_display_lines(None,
650 def url_encoding(sbox
):
651 "test url encoding issues"
652 run_test(sbox
, "url-encoding-bug.dump")
655 # A test for copying revisions that lack a property that already exists
656 # on the destination rev as part of the commit (i.e. svn:author in this
657 # case, but svn:date would also work).
659 "test copying revs with no svn:author revprops"
660 run_test(sbox
, "no-author.dump")
662 def copy_revprops(sbox
):
663 "test copying revprops other than svn:*"
664 run_test(sbox
, "revprops.dump")
666 def only_trunk(sbox
):
667 "test syncing subdirectories"
668 run_test(sbox
, "svnsync-trunk-only.dump", "/trunk",
669 "svnsync-trunk-only.expected.dump")
671 def only_trunk_A_with_changes(sbox
):
672 "test syncing subdirectories with changes on root"
673 run_test(sbox
, "svnsync-trunk-A-changes.dump", "/trunk/A",
674 "svnsync-trunk-A-changes.expected.dump")
676 # test for issue #2904
677 def move_and_modify_in_the_same_revision(sbox
):
678 "test move parent and modify child file in same rev"
679 run_test(sbox
, "svnsync-move-and-modify.dump")
681 ########################################################################
685 # list all tests here, starting with None:
688 copy_from_previous_version_and_modify
,
689 copy_from_previous_version
,
693 tag_trunk_with_file2
,
695 tag_with_modified_file
,
698 copy_parent_modify_prop
,
700 Skip(basic_authz
, svntest
.main
.is_ra_type_file
),
701 Skip(copy_from_unreadable_dir
, svntest
.main
.is_ra_type_file
),
702 Skip(copy_with_mod_from_unreadable_dir
,
703 svntest
.main
.is_ra_type_file
),
704 Skip(copy_with_mod_from_unreadable_dir_and_copy
,
705 svntest
.main
.is_ra_type_file
),
710 only_trunk_A_with_changes
,
711 move_and_modify_in_the_same_revision
,
714 if __name__
== '__main__':
715 svntest
.main
.run_tests(test_list
, serial_only
= True)