3 # svnadmin_tests.py: testing the 'svnadmin' tool.
5 # Subversion is a tool for revision control.
6 # See http://subversion.tigris.org for more information.
8 # ====================================================================
9 # Copyright (c) 2000-2006 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 ######################################################################
25 from svntest
.verify
import SVNExpectedStdout
, SVNExpectedStderr
28 Skip
= svntest
.testcase
.Skip
29 SkipUnless
= svntest
.testcase
.SkipUnless
30 XFail
= svntest
.testcase
.XFail
31 Item
= svntest
.wc
.StateItem
34 #----------------------------------------------------------------------
36 # How we currently test 'svnadmin' --
38 # 'svnadmin create': Create an empty repository, test that the
39 # root node has a proper created-revision,
40 # because there was once a bug where it
43 # Note also that "svnadmin create" is tested
44 # implicitly every time we run a python test
45 # script. (An empty repository is always
46 # created and then imported into; if this
47 # subcommand failed catastrophically, every
48 # test would fail and we would know instantly.)
50 # 'svnadmin createtxn'
51 # 'svnadmin rmtxn': See below.
53 # 'svnadmin lstxns': We don't care about the contents of transactions;
54 # we only care that they exist or not.
55 # Therefore, we can simply parse transaction headers.
57 # 'svnadmin dump': A couple regression tests that ensure dump doesn't
58 # error out, and one to check that the --quiet option
59 # really does what it's meant to do. The actual
60 # contents of the dump aren't verified at all.
62 # ### TODO: someday maybe we could parse the contents of trees too.
64 ######################################################################
68 def get_txns(repo_dir
):
69 "Get the txn names using 'svnadmin lstxns'."
71 output_lines
, error_lines
= svntest
.main
.run_svnadmin('lstxns', repo_dir
)
72 txns
= map(output_lines
.strip
, output_lines
)
79 def load_and_verify_dumpstream(sbox
, expected_stdout
, expected_stderr
,
80 revs
, dump
, *varargs
):
81 """Load the array of lines passed in 'dump' into the
82 current tests' repository and verify the repository content
83 using the array of wc.States passed in revs. VARARGS are optional
84 arguments passed to the 'load' command"""
86 if type(dump
) is type(""):
90 svntest
.main
.run_command_stdin(
91 svntest
.main
.svnadmin_binary
, expected_stderr
, 1, dump
,
92 'load', '--quiet', sbox
.repo_dir
, *varargs
)
95 if expected_stdout
== svntest
.verify
.AnyOutput
:
97 raise SVNExpectedStdout
99 svntest
.verify
.compare_and_display_lines(
100 "Standard output", "STDOUT:", expected_stdout
, output
)
103 if expected_stderr
== svntest
.verify
.AnyOutput
:
105 raise SVNExpectedStderr
107 svntest
.verify
.compare_and_display_lines(
108 "Standard error output", "STDERR:", expected_stderr
, errput
)
109 # The expected error occurred, so don't try to verify the result
113 # verify revs as wc states
114 for rev
in xrange(len(revs
)):
115 svntest
.actions
.run_and_verify_svn("Updating to r%s" % (rev
+1),
116 svntest
.verify
.AnyOutput
, [],
117 "update", "-r%s" % (rev
+1),
120 wc_tree
= svntest
.tree
.build_tree_from_wc(sbox
.wc_dir
)
121 rev_tree
= revs
[rev
].old_tree()
124 svntest
.tree
.compare_trees ("rev/disk", rev_tree
, wc_tree
)
125 except svntest
.tree
.SVNTreeError
:
126 svntest
.verify
.display_trees(None, 'WC TREE', wc_tree
, rev_tree
)
130 ######################################################################
134 #----------------------------------------------------------------------
136 def test_create(sbox
):
140 repo_dir
= sbox
.repo_dir
143 svntest
.main
.safe_rmtree(repo_dir
)
144 svntest
.main
.safe_rmtree(wc_dir
)
146 svntest
.main
.create_repos(repo_dir
)
148 svntest
.actions
.run_and_verify_svn("Creating rev 0 checkout",
149 ["Checked out revision 0.\n"], [],
151 sbox
.repo_url
, wc_dir
)
154 svntest
.actions
.run_and_verify_svn(
159 svntest
.actions
.run_and_verify_svn(
160 "Running verbose status",
161 [" 0 0 ? %s\n" % wc_dir
], [],
162 "status", "--verbose", wc_dir
)
167 # dump stream tests need a dump file
169 def clean_dumpfile():
171 [ "SVN-fs-dump-format-version: 2\n\n",
172 "UUID: 668cc64a-31ed-0310-8ccb-b75d75bb44e3\n\n",
173 "Revision-number: 0\n",
174 "Prop-content-length: 56\n",
175 "Content-length: 56\n\n",
176 "K 8\nsvn:date\nV 27\n2005-01-08T21:48:13.838745Z\nPROPS-END\n\n\n",
177 "Revision-number: 1\n",
178 "Prop-content-length: 98\n",
179 "Content-length: 98\n\n",
180 "K 7\nsvn:log\nV 0\n\nK 10\nsvn:author\nV 4\nerik\n",
181 "K 8\nsvn:date\nV 27\n2005-01-08T21:51:16.313791Z\nPROPS-END\n\n\n",
184 "Node-action: add\n",
185 "Prop-content-length: 35\n",
186 "Text-content-length: 5\n",
187 "Text-content-md5: e1cbb0c3879af8347246f12c559a86b5\n",
188 "Content-length: 40\n\n",
189 "K 12\nsvn:keywords\nV 2\nId\nPROPS-END\ntext\n\n\n"]
191 dumpfile_revisions
= \
192 [ svntest
.wc
.State('', { 'A' : svntest
.wc
.StateItem(contents
="text\n") }) ]
194 #----------------------------------------------------------------------
195 def extra_headers(sbox
):
196 "loading of dumpstream with extra headers"
200 dumpfile
= clean_dumpfile()
203 [ "X-Comment-Header: Ignored header normally not in dump stream\n" ]
205 load_and_verify_dumpstream(sbox
,[],[], dumpfile_revisions
, dumpfile
)
207 #----------------------------------------------------------------------
208 # Ensure loading continues after skipping a bit of unknown extra content.
209 def extra_blockcontent(sbox
):
210 "load success on oversized Content-length"
214 dumpfile
= clean_dumpfile()
216 # Replace "Content-length" line with two lines
218 [ "Extra-content-length: 10\n",
219 "Content-length: 108\n\n" ]
220 # Insert the extra content after "PROPS-END\n"
221 dumpfile
[11] = dumpfile
[11][:-2] + "extra text\n\n\n"
223 load_and_verify_dumpstream(sbox
,[],[], dumpfile_revisions
, dumpfile
)
225 #----------------------------------------------------------------------
226 def inconsistent_headers(sbox
):
227 "load failure on undersized Content-length"
231 dumpfile
= clean_dumpfile()
233 dumpfile
[-2] = "Content-length: 30\n\n"
235 load_and_verify_dumpstream(sbox
, [], svntest
.verify
.AnyOutput
,
236 dumpfile_revisions
, dumpfile
)
238 #----------------------------------------------------------------------
239 # Test for issue #2729: Datestamp-less revisions in dump streams do
240 # not remain so after load
241 def empty_date(sbox
):
242 "preserve date-less revisions in load (issue #2729)"
246 dumpfile
= clean_dumpfile()
248 # Replace portions of the revision data to drop the svn:date revprop.
250 [ "Prop-content-length: 52\n",
251 "Content-length: 52\n\n",
252 "K 7\nsvn:log\nV 0\n\nK 10\nsvn:author\nV 4\nerik\nPROPS-END\n\n\n"
255 load_and_verify_dumpstream(sbox
,[],[], dumpfile_revisions
, dumpfile
)
257 # Verify that the revision still lacks the svn:date property.
258 svntest
.actions
.run_and_verify_svn(None, [], [], "propget",
259 "--revprop", "-r1", "svn:date",
262 #----------------------------------------------------------------------
264 def dump_copied_dir(sbox
):
265 "'svnadmin dump' on copied directory"
269 repo_dir
= sbox
.repo_dir
271 old_C_path
= os
.path
.join(wc_dir
, 'A', 'C')
272 new_C_path
= os
.path
.join(wc_dir
, 'A', 'B', 'C')
273 svntest
.main
.run_svn(None, 'cp', old_C_path
, new_C_path
)
274 svntest
.main
.run_svn(None, 'ci', wc_dir
, '--quiet',
277 output
, errput
= svntest
.main
.run_svnadmin("dump", repo_dir
)
278 if svntest
.verify
.compare_and_display_lines(
279 "Output of 'svnadmin dump' is unexpected.",
280 'STDERR', ["* Dumped revision 0.\n",
281 "* Dumped revision 1.\n",
282 "* Dumped revision 2.\n"], errput
):
283 raise svntest
.Failure
285 #----------------------------------------------------------------------
287 def dump_move_dir_modify_child(sbox
):
288 "'svnadmin dump' on modified child of copied dir"
292 repo_dir
= sbox
.repo_dir
294 B_path
= os
.path
.join(wc_dir
, 'A', 'B')
295 Q_path
= os
.path
.join(wc_dir
, 'A', 'Q')
296 svntest
.main
.run_svn(None, 'cp', B_path
, Q_path
)
297 svntest
.main
.file_append(os
.path
.join(Q_path
, 'lambda'), 'hello')
298 svntest
.main
.run_svn(None, 'ci', wc_dir
, '--quiet',
300 output
, errput
= svntest
.main
.run_svnadmin("dump", repo_dir
)
301 svntest
.verify
.compare_and_display_lines(
302 "Output of 'svnadmin dump' is unexpected.",
303 'STDERR', ["* Dumped revision 0.\n",
304 "* Dumped revision 1.\n",
305 "* Dumped revision 2.\n"], errput
)
307 output
, errput
= svntest
.main
.run_svnadmin("dump", "-r", "0:HEAD", repo_dir
)
308 svntest
.verify
.compare_and_display_lines(
309 "Output of 'svnadmin dump' is unexpected.",
310 'STDERR', ["* Dumped revision 0.\n",
311 "* Dumped revision 1.\n",
312 "* Dumped revision 2.\n"], errput
)
314 #----------------------------------------------------------------------
316 def dump_quiet(sbox
):
317 "'svnadmin dump --quiet'"
319 sbox
.build(create_wc
= False)
321 output
, errput
= svntest
.main
.run_svnadmin("dump", sbox
.repo_dir
, '--quiet')
322 svntest
.verify
.compare_and_display_lines(
323 "Output of 'svnadmin dump --quiet' is unexpected.",
324 'STDERR', [], errput
)
326 #----------------------------------------------------------------------
328 def hotcopy_dot(sbox
):
329 "'svnadmin hotcopy PATH .'"
332 backup_dir
, backup_url
= sbox
.add_repo_path('backup')
337 output
, errput
= svntest
.main
.run_svnadmin("hotcopy",
338 os
.path
.join(cwd
, sbox
.repo_dir
),
341 raise svntest
.Failure
345 origout
, origerr
= svntest
.main
.run_svnadmin("dump", sbox
.repo_dir
, '--quiet')
346 backout
, backerr
= svntest
.main
.run_svnadmin("dump", backup_dir
, '--quiet')
347 if origerr
or backerr
or origout
!= backout
:
348 raise svntest
.Failure
350 #----------------------------------------------------------------------
352 def hotcopy_format(sbox
):
353 "'svnadmin hotcopy' checking db/format file"
356 backup_dir
, backup_url
= sbox
.add_repo_path('backup')
357 output
, errput
= svntest
.main
.run_svnadmin("hotcopy", sbox
.repo_dir
,
360 print "Error: hotcopy failed"
361 raise svntest
.Failure
363 # verify that the db/format files are the same
364 fp
= open(os
.path
.join(sbox
.repo_dir
, "db", "format"))
365 contents1
= fp
.read()
368 fp2
= open(os
.path
.join(backup_dir
, "db", "format"))
369 contents2
= fp2
.read()
372 if contents1
!= contents2
:
373 print "Error: db/format file contents do not match after hotcopy"
374 raise svntest
.Failure
376 #----------------------------------------------------------------------
378 def setrevprop(sbox
):
379 "'setlog' and 'setrevprop', bypassing hooks'"
382 # Try a simple log property modification.
383 iota_path
= os
.path
.join(sbox
.wc_dir
, "iota")
384 output
, errput
= svntest
.main
.run_svnadmin("setlog", sbox
.repo_dir
,
385 "-r0", "--bypass-hooks",
388 print "Error: 'setlog' failed"
389 raise svntest
.Failure
391 # Verify that the revprop value matches what we set when retrieved
392 # through the client.
393 svntest
.actions
.run_and_verify_svn(None,
394 [ "This is the file 'iota'.\n", "\n" ],
395 [], "propget", "--revprop", "-r0",
396 "svn:log", sbox
.wc_dir
)
398 # Try an author property modification.
399 foo_path
= os
.path
.join(sbox
.wc_dir
, "foo")
400 svntest
.main
.file_write(foo_path
, "foo")
402 output
, errput
= svntest
.main
.run_svnadmin("setrevprop", sbox
.repo_dir
,
403 "-r0", "svn:author", foo_path
)
405 print "Error: 'setrevprop' failed"
406 raise svntest
.Failure
408 # Verify that the revprop value matches what we set when retrieved
409 # through the client.
410 svntest
.actions
.run_and_verify_svn(None, [ "foo\n" ], [], "propget",
411 "--revprop", "-r0", "svn:author",
414 def verify_windows_paths_in_repos(sbox
):
415 "verify a repository containing paths like 'c:hi'"
417 # setup a repo with a directory 'c:hi'
418 sbox
.build(create_wc
= False)
419 repo_url
= sbox
.repo_url
420 chi_url
= sbox
.repo_url
+ '/c:hi'
422 svntest
.actions
.run_and_verify_svn(None, None, [],
423 'mkdir', '-m', 'log_msg',
426 output
, errput
= svntest
.main
.run_svnadmin("verify", sbox
.repo_dir
)
427 svntest
.verify
.compare_and_display_lines(
428 "Error while running 'svnadmin verify'.",
429 'STDERR', ["* Verified revision 0.\n",
430 "* Verified revision 1.\n",
431 "* Verified revision 2.\n"], errput
)
433 #----------------------------------------------------------------------
435 def recover_fsfs(sbox
):
436 "recover a repository (FSFS only)"
438 # Set up a repository containing the greek tree.
439 sbox
.build(create_wc
= False)
441 # Read the current contents of the current file.
442 current_path
= os
.path
.join(sbox
.repo_dir
, 'db', 'current')
443 expected_current_contents
= svntest
.main
.file_read(current_path
)
445 # Remove the current file.
446 os
.remove(current_path
)
448 # Run 'svnadmin recover' and check that the current file is recreated.
449 output
, errput
= svntest
.main
.run_svnadmin("recover", sbox
.repo_dir
)
451 raise SVNUnexpectedStderr
453 actual_current_contents
= svntest
.main
.file_read(current_path
)
454 svntest
.verify
.compare_and_display_lines(
455 "Contents of db/current is unexpected.",
456 'db/current', expected_current_contents
, actual_current_contents
)
458 #----------------------------------------------------------------------
460 def load_with_parent_dir(sbox
):
461 "'svnadmin load --parent-dir' reparents mergeinfo"
463 ## See http://subversion.tigris.org/issues/show_bug.cgi?id=2983. ##
466 dumpfile_location
= os
.path
.join(os
.path
.dirname(sys
.argv
[0]),
467 'svnadmin_tests_data',
468 'mergeinfo_included.dump')
469 dumpfile
= svntest
.main
.file_read(dumpfile_location
)
471 # Create 'sample' dir in sbox.repo_url
472 svntest
.actions
.run_and_verify_svn(None,
473 ['\n', 'Committed revision 1.\n'],
474 [], "mkdir", sbox
.repo_url
+ "/sample",
475 "-m", "Create sample dir")
477 # Load the dump stream
478 load_and_verify_dumpstream(sbox
,[],[], None, dumpfile
, '--parent-dir',
481 # Verify the svn:mergeinfo properties for '--parent-dir'
482 svntest
.actions
.run_and_verify_svn(None,
484 "/sample/branch - /sample/trunk:5-7\n"],
485 [], 'propget', 'svn:mergeinfo', '-R',
486 sbox
.repo_url
+ '/sample/branch')
487 svntest
.actions
.run_and_verify_svn(None,
489 "/sample/branch1 - " +
490 "/sample/branch:6-9\n"],
491 [], 'propget', 'svn:mergeinfo', '-R',
492 sbox
.repo_url
+ '/sample/branch1')
494 #----------------------------------------------------------------------
497 "test 'svnadmin setuuid'"
499 sbox
.build(create_wc
=False)
501 # Squirrel away the original repository UUID.
502 output
, errput
= svntest
.main
.run_svnlook('uuid', sbox
.repo_dir
)
504 raise SVNUnexpectedStderr
505 orig_uuid
= output
[0].rstrip()
507 # Try setting a new, bogus UUID.
508 svntest
.actions
.run_and_verify_svnadmin(None, None, '^.*Malformed UUID.*$',
509 'setuuid', sbox
.repo_dir
, 'abcdef')
511 # Try generating a brand new UUID.
512 svntest
.actions
.run_and_verify_svnadmin(None, [], None,
513 'setuuid', sbox
.repo_dir
)
514 output
, errput
= svntest
.main
.run_svnlook('uuid', sbox
.repo_dir
)
516 raise SVNUnexpectedStderr
517 new_uuid
= output
[0].rstrip()
518 if new_uuid
== orig_uuid
:
519 print "Error: new UUID matches the original one"
520 raise svntest
.Failure
522 # Now, try setting the UUID back to the original value.
523 svntest
.actions
.run_and_verify_svnadmin(None, [], None,
524 'setuuid', sbox
.repo_dir
, orig_uuid
)
525 output
, errput
= svntest
.main
.run_svnlook('uuid', sbox
.repo_dir
)
527 raise SVNUnexpectedStderr
528 new_uuid
= output
[0].rstrip()
529 if new_uuid
!= orig_uuid
:
530 print "Error: new UUID doesn't match the original one"
531 raise svntest
.Failure
533 #----------------------------------------------------------------------
535 def reflect_dropped_renumbered_revs(sbox
):
536 "reflect dropped renumbered revs in svn:mergeinfo"
538 ## See http://subversion.tigris.org/issues/show_bug.cgi?id=3020. ##
542 dumpfile_location
= os
.path
.join(os
.path
.dirname(sys
.argv
[0]),
543 'svndumpfilter_tests_data',
545 dumpfile
= svntest
.main
.file_read(dumpfile_location
)
547 # Create 'toplevel' dir in sbox.repo_url
548 svntest
.actions
.run_and_verify_svn(None, ['\n', 'Committed revision 1.\n'],
549 [], "mkdir", sbox
.repo_url
+ "/toplevel",
550 "-m", "Create toplevel dir")
552 # Load the dump stream in sbox.repo_url
553 load_and_verify_dumpstream(sbox
,[],[], None, dumpfile
)
555 # Load the dump stream in toplevel dir
556 load_and_verify_dumpstream(sbox
,[],[], None, dumpfile
, '--parent-dir',
559 # Verify the svn:mergeinfo properties
560 svntest
.actions
.run_and_verify_svn(None, ["/trunk:1-4\n"],
561 [], 'propget', 'svn:mergeinfo',
562 sbox
.repo_url
+ '/branch2')
563 svntest
.actions
.run_and_verify_svn(None, ["/branch1:5-9\n"],
564 [], 'propget', 'svn:mergeinfo',
565 sbox
.repo_url
+ '/trunk')
566 svntest
.actions
.run_and_verify_svn(None, ["/toplevel/trunk:1-13\n"],
567 [], 'propget', 'svn:mergeinfo',
568 sbox
.repo_url
+ '/toplevel/branch2')
569 svntest
.actions
.run_and_verify_svn(None, ["/toplevel/branch1:14-18\n"],
570 [], 'propget', 'svn:mergeinfo',
571 sbox
.repo_url
+ '/toplevel/trunk')
572 svntest
.actions
.run_and_verify_svn(None, ["/toplevel/trunk:1-12\n"],
573 [], 'propget', 'svn:mergeinfo',
574 sbox
.repo_url
+ '/toplevel/branch1')
575 svntest
.actions
.run_and_verify_svn(None, ["/trunk:1-3\n"],
576 [], 'propget', 'svn:mergeinfo',
577 sbox
.repo_url
+ '/branch1')
580 ########################################################################
584 # list all tests here, starting with None:
588 inconsistent_headers
,
591 dump_move_dir_modify_child
,
596 verify_windows_paths_in_repos
,
597 SkipUnless(recover_fsfs
, svntest
.main
.is_fs_type_fsfs
),
598 load_with_parent_dir
,
600 reflect_dropped_renumbered_revs
,
603 if __name__
== '__main__':
604 svntest
.main
.run_tests(test_list
)