2 # actions.py: routines that actually run the svn client.
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 os
, shutil
, re
, sys
, errno
19 import difflib
, pprint
20 import xml
.parsers
.expat
21 from xml
.dom
.minidom
import parseString
23 import main
, verify
, tree
, wc
, parsers
24 from svntest
import Failure
26 def no_sleep_for_timestamps():
27 os
.environ
['SVN_SLEEP_FOR_TIMESTAMPS'] = 'no'
29 def do_sleep_for_timestamps():
30 os
.environ
['SVN_SLEEP_FOR_TIMESTAMPS'] = 'yes'
32 def setup_pristine_repository():
33 """Create the pristine repository and 'svn import' the greek tree"""
35 # these directories don't exist out of the box, so we may have to create them
36 if not os
.path
.exists(main
.general_wc_dir
):
37 os
.makedirs(main
.general_wc_dir
)
39 if not os
.path
.exists(main
.general_repo_dir
):
40 os
.makedirs(main
.general_repo_dir
) # this also creates all the intermediate dirs
42 # If there's no pristine repos, create one.
43 if not os
.path
.exists(main
.pristine_dir
):
44 main
.create_repos(main
.pristine_dir
)
46 # if this is dav, gives us access rights to import the greek tree.
47 if main
.is_ra_type_dav():
48 authz_file
= os
.path
.join(main
.work_dir
, "authz")
49 main
.file_write(authz_file
, "[/]\n* = rw\n")
51 # dump the greek tree to disk.
52 main
.greek_state
.write_to_disk(main
.greek_dump_dir
)
54 # import the greek tree, using l:foo/p:bar
55 ### todo: svn should not be prompting for auth info when using
56 ### repositories with no auth/auth requirements
57 output
, errput
= main
.run_svn(None, 'import',
58 '-m', 'Log message for revision 1.',
59 main
.greek_dump_dir
, main
.pristine_url
)
61 # check for any errors from the import
63 display_lines("Errors during initial 'svn import':",
64 'STDERR', None, errput
)
67 # verify the printed output of 'svn import'.
68 lastline
= output
.pop().strip()
69 cm
= re
.compile ("(Committed|Imported) revision [0-9]+.")
70 match
= cm
.search (lastline
)
72 print "ERROR: import did not succeed, while creating greek repos."
73 print "The final line from 'svn import' was:"
76 output_tree
= tree
.build_tree_from_commit(output
)
78 ### due to path normalization in the .old_tree() method, we cannot
79 ### prepend the necessary '.' directory. thus, let's construct an old
80 ### tree manually from the greek_state.
82 for greek_path
in main
.greek_state
.desc
.keys():
83 output_list
.append([ os
.path
.join(main
.greek_dump_dir
, greek_path
),
84 None, {}, {'verb' : 'Adding'}])
85 expected_output_tree
= tree
.build_generic_tree(output_list
)
88 tree
.compare_trees(output_tree
, expected_output_tree
)
89 except tree
.SVNTreeUnequal
:
90 verify
.display_trees("ERROR: output of import command is unexpected.",
91 "OUTPUT TREE", expected_output_tree
, output_tree
)
95 ######################################################################
96 # Used by every test, so that they can run independently of one
97 # another. Every time this routine is called, it recursively copies
98 # the `pristine repos' to a new location.
99 # Note: make sure setup_pristine_repository was called once before
100 # using this function.
102 def guarantee_greek_repository(path
):
103 """Guarantee that a local svn repository exists at PATH, containing
104 nothing but the greek-tree at revision 1."""
106 if path
== main
.pristine_dir
:
107 print "ERROR: attempt to overwrite the pristine repos! Aborting."
110 # copy the pristine repository to PATH.
111 main
.safe_rmtree(path
)
112 if main
.copy_repos(main
.pristine_dir
, path
, 1):
113 print "ERROR: copying repository failed."
116 # make the repos world-writeable, for mod_dav_svn's sake.
117 main
.chmod_tree(path
, 0666, 0666)
120 def run_and_verify_svnversion(message
, wc_dir
, repo_url
,
121 expected_stdout
, expected_stderr
):
122 "Run svnversion command and check its output"
123 out
, err
= main
.run_svnversion(wc_dir
, repo_url
)
124 verify
.verify_outputs("Unexpected output", out
, err
,
125 expected_stdout
, expected_stderr
)
129 def run_and_verify_svn(message
, expected_stdout
, expected_stderr
, *varargs
):
130 """Invokes main.run_svn() with *VARARGS, return stdout and stderr as
131 lists of lines. For both EXPECTED_STDOUT and EXPECTED_STDERR,
132 create an appropriate instance of verify.ExpectedOutput (if necessary):
134 - If it is an array of strings, create a vanilla ExpectedOutput.
136 - If it is a single string, create a RegexOutput.
138 - If it is already an instance of ExpectedOutput
139 (e.g. UnorderedOutput), leave it alone.
141 ...and invoke compare_and_display_lines() on MESSAGE, a label based
142 on the name of the stream being compared (e.g. STDOUT), the
143 ExpectedOutput instance, and the actual output.
145 If EXPECTED_STDOUT is None, do not check stdout.
146 EXPECTED_STDERR may not be None.
148 If a comparison fails, a Failure will be raised."""
150 if expected_stderr
is None:
151 raise verify
.SVNIncorrectDatatype("expected_stderr must not be None")
154 if expected_stderr
is not None and expected_stderr
is not []:
157 out
, err
= main
.run_svn(want_err
, *varargs
)
158 verify
.verify_outputs(message
, out
, err
, expected_stdout
, expected_stderr
)
162 def run_and_verify_load(repo_dir
, dump_file_content
):
163 "Runs 'svnadmin load' and reports any errors."
166 main
.run_command_stdin(main
.svnadmin_binary
,
167 expected_stderr
, 1, dump_file_content
,
168 'load', '--force-uuid', '--quiet', repo_dir
)
169 verify
.verify_outputs("Unexpected stderr output", None, errput
,
170 None, expected_stderr
)
173 def run_and_verify_dump(repo_dir
):
174 "Runs 'svnadmin dump' and reports any errors, returning the dump content."
175 output
, errput
= main
.run_svnadmin('dump', repo_dir
)
176 verify
.verify_outputs("Missing expected output(s)", output
, errput
,
177 verify
.AnyOutput
, verify
.AnyOutput
)
181 def load_repo(sbox
, dumpfile_path
= None, dump_str
= None):
182 "Loads the dumpfile into sbox"
184 dump_str
= main
.file_read(dumpfile_path
, "rb")
186 # Create a virgin repos and working copy
187 main
.safe_rmtree(sbox
.repo_dir
, 1)
188 main
.safe_rmtree(sbox
.wc_dir
, 1)
189 main
.create_repos(sbox
.repo_dir
)
191 # Load the mergetracking dumpfile into the repos, and check it out the repo
192 run_and_verify_load(sbox
.repo_dir
, dump_str
)
193 run_and_verify_svn(None, None, [], "co", sbox
.repo_url
, sbox
.wc_dir
)
198 ######################################################################
201 # These are all routines that invoke 'svn' in particular ways, and
202 # then verify the results by comparing expected trees with actual
205 # For all the functions below, the OUTPUT_TREE and DISK_TREE args need
206 # to be created by feeding carefully constructed lists to
207 # tree.build_generic_tree(). A STATUS_TREE can be built by
208 # hand, or by editing the tree returned by get_virginal_state().
211 def run_and_verify_checkout(URL
, wc_dir_name
, output_tree
, disk_tree
,
212 singleton_handler_a
= None,
214 singleton_handler_b
= None,
217 """Checkout the URL into a new directory WC_DIR_NAME. *ARGS are any
218 extra optional args to the checkout subcommand.
220 The subcommand output will be verified against OUTPUT_TREE,
221 and the working copy itself will be verified against DISK_TREE.
222 SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to
223 tree.compare_trees - see that function's doc string for more details.
224 Returns if successful and raise on failure.
226 WC_DIR_NAME is deleted if present unless the '--force' option is passed
229 if isinstance(output_tree
, wc
.State
):
230 output_tree
= output_tree
.old_tree()
231 if isinstance(disk_tree
, wc
.State
):
232 disk_tree
= disk_tree
.old_tree()
234 # Remove dir if it's already there, unless this is a forced checkout.
235 # In that case assume we want to test a forced checkout's toleration
236 # of obstructing paths.
243 main
.safe_rmtree(wc_dir_name
)
245 # Checkout and make a tree of the output, using l:foo/p:bar
246 ### todo: svn should not be prompting for auth info when using
247 ### repositories with no auth/auth requirements
248 output
, errput
= main
.run_svn (None, 'co',
249 URL
, wc_dir_name
, *args
)
250 actual
= tree
.build_tree_from_checkout (output
)
252 # Verify actual output against expected output.
253 tree
.compare_trees (actual
, output_tree
)
255 # Create a tree by scanning the working copy
256 actual
= tree
.build_tree_from_wc (wc_dir_name
)
258 # Verify expected disk against actual disk.
259 tree
.compare_trees (actual
, disk_tree
,
260 singleton_handler_a
, a_baton
,
261 singleton_handler_b
, b_baton
)
264 def run_and_verify_export(URL
, export_dir_name
, output_tree
, disk_tree
,
265 singleton_handler_a
= None,
267 singleton_handler_b
= None,
270 """Export the URL into a new directory WC_DIR_NAME.
272 The subcommand output will be verified against OUTPUT_TREE,
273 and the exported copy itself will be verified against DISK_TREE.
274 SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to
275 tree.compare_trees - see that function's doc string for more details.
276 Returns if successful and raise on failure."""
278 if isinstance(output_tree
, wc
.State
):
279 output_tree
= output_tree
.old_tree()
280 if isinstance(disk_tree
, wc
.State
):
281 disk_tree
= disk_tree
.old_tree()
283 # Export and make a tree of the output, using l:foo/p:bar
284 ### todo: svn should not be prompting for auth info when using
285 ### repositories with no auth/auth requirements
286 output
, errput
= main
.run_svn (None, 'export',
287 URL
, export_dir_name
, *args
)
288 actual
= tree
.build_tree_from_checkout (output
)
290 # Verify actual output against expected output.
291 tree
.compare_trees (actual
, output_tree
)
293 # Create a tree by scanning the working copy. Don't ignore
294 # the .svn directories so that we generate an error if they
296 actual
= tree
.build_tree_from_wc (export_dir_name
, ignore_svn
=False)
298 # Verify expected disk against actual disk.
299 tree
.compare_trees (actual
, disk_tree
,
300 singleton_handler_a
, a_baton
,
301 singleton_handler_b
, b_baton
)
304 # run_and_verify_log_xml
307 def __init__(self
, revision
, changed_paths
=None, revprops
=None):
308 self
.revision
= revision
309 if changed_paths
== None:
310 self
.changed_paths
= {}
312 self
.changed_paths
= changed_paths
316 self
.revprops
= revprops
318 def assert_changed_paths(self
, changed_paths
):
319 """Not implemented, so just raises svntest.Failure.
321 raise Failure('NOT IMPLEMENTED')
323 def assert_revprops(self
, revprops
):
324 """Assert that the dict revprops is the same as this entry's revprops.
326 Raises svntest.Failure if not.
328 if self
.revprops
!= revprops
:
329 raise Failure('\n' + '\n'.join(difflib
.ndiff(
330 pprint
.pformat(revprops
).splitlines(),
331 pprint
.pformat(self
.revprops
).splitlines())))
334 def parse(self
, data
):
335 """Return a list of LogEntrys parsed from the sequence of strings data.
337 This is the only method of interest to callers.
342 self
.parser
.Parse('', True)
343 except xml
.parsers
.expat
.ExpatError
, e
:
344 raise verify
.SVNUnexpectedStdout('%s\n%s\n' % (e
, ''.join(data
),))
349 self
.parser
= xml
.parsers
.expat
.ParserCreate()
350 self
.parser
.StartElementHandler
= self
.handle_start_element
351 self
.parser
.EndElementHandler
= self
.handle_end_element
352 self
.parser
.CharacterDataHandler
= self
.handle_character_data
353 # Ignore some things.
354 self
.ignore_elements('log', 'paths', 'path', 'revprops')
355 self
.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
362 def ignore(self
, *args
, **kwargs
):
364 def ignore_tags(self
, *args
):
366 setattr(self
, tag
, self
.ignore
)
367 def ignore_elements(self
, *args
):
369 self
.ignore_tags(element
+ '_start', element
+ '_end')
372 def handle_start_element(self
, name
, attrs
):
373 getattr(self
, name
+ '_start')(attrs
)
374 def handle_end_element(self
, name
):
375 getattr(self
, name
+ '_end')()
376 def handle_character_data(self
, data
):
377 self
.cdata
.append(data
)
379 # element handler utilities
381 result
= ''.join(self
.cdata
).strip()
384 def svn_prop(self
, name
):
385 self
.entries
[-1].revprops
['svn:' + name
] = self
.use_cdata()
388 def logentry_start(self
, attrs
):
389 self
.entries
.append(LogEntry(int(attrs
['revision'])))
390 def author_end(self
):
391 self
.svn_prop('author')
395 # svn:date could be anything, so just note its presence.
397 self
.svn_prop('date')
398 def property_start(self
, attrs
):
399 self
.property = attrs
['name']
400 def property_end(self
):
401 self
.entries
[-1].revprops
[self
.property] = self
.use_cdata()
403 def run_and_verify_log_xml(message
=None, expected_paths
=None,
404 expected_revprops
=None, expected_stdout
=None,
405 expected_stderr
=None, args
=[]):
406 """Call run_and_verify_svn with log --xml and args (optional) as command
407 arguments, and pass along message, expected_stdout, and expected_stderr.
409 If message is None, pass the svn log command as message.
411 expected_paths checking is not yet implemented.
413 expected_revprops is an optional list of dicts, compared to each
414 revision's revprops. The list must be in the same order the log entries
415 come in. Any svn:date revprops in the dicts must be '' in order to
416 match, as the actual dates could be anything.
418 expected_paths and expected_revprops are ignored if expected_stdout or
419 expected_stderr is specified.
422 message
= ' '.join(args
)
424 # We'll parse the output unless the caller specifies expected_stderr or
425 # expected_stdout for run_and_verify_svn.
427 if expected_stderr
== None:
431 if expected_stdout
!= None:
434 log_args
= list(args
)
435 if expected_paths
!= None:
436 log_args
.append('-v')
438 (stdout
, stderr
) = run_and_verify_svn(
439 message
, expected_stdout
, expected_stderr
,
440 'log', '--xml', *log_args
)
444 for (index
, entry
) in enumerate(LogParser().parse(stdout
)):
445 if expected_revprops
!= None:
446 entry
.assert_revprops(expected_revprops
[index
])
447 if expected_paths
!= None:
448 entry
.assert_changed_paths(expected_paths
[index
])
451 def verify_update(actual_output
, wc_dir_name
,
452 output_tree
, disk_tree
, status_tree
,
453 singleton_handler_a
, a_baton
,
454 singleton_handler_b
, b_baton
,
456 """Verify update of WC_DIR_NAME.
458 The subcommand output (found in ACTUAL_OUTPUT) will be verified
459 against OUTPUT_TREE (if provided), the working copy itself will be
460 verified against DISK_TREE (if provided), and the working copy's
461 'svn status' output will be verified against STATUS_TREE (if
462 provided). (This is a good way to check that revision numbers were
463 bumped.) SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed
464 to tree.compare_trees - see that function's doc string for more
465 details. If CHECK_PROPS is set, then disk comparison will examine
466 props. Returns if successful, raises on failure."""
468 # Verify actual output against expected output.
470 tree
.compare_trees (actual_output
, output_tree
)
472 # Create a tree by scanning the working copy, and verify it
474 actual_disk
= tree
.build_tree_from_wc (wc_dir_name
, check_props
)
475 tree
.compare_trees (actual_disk
, disk_tree
,
476 singleton_handler_a
, a_baton
,
477 singleton_handler_b
, b_baton
)
479 # Verify via 'status' command too, if possible.
481 run_and_verify_status(wc_dir_name
, status_tree
)
484 def verify_disk(wc_dir_name
,
486 singleton_handler_a
= None,
488 singleton_handler_b
= None,
490 check_props
= False):
492 """Verify WC_DIR_NAME against DISK_TREE. SINGLETON_HANDLER_A,
493 A_BATON, SINGLETON_HANDLER_B, and B_BATON will be passed to
494 tree.compare_trees, which see for details. If CHECK_PROPS is set,
495 the comparison will examin props. Returns if successful, raises on
497 if isinstance(disk_tree
, wc
.State
):
498 disk_tree
= disk_tree
.old_tree()
499 verify_update (None, wc_dir_name
, None, disk_tree
, None,
500 singleton_handler_a
, a_baton
,
501 singleton_handler_b
, b_baton
,
506 def run_and_verify_update(wc_dir_name
,
507 output_tree
, disk_tree
, status_tree
,
508 error_re_string
= None,
509 singleton_handler_a
= None,
511 singleton_handler_b
= None,
516 """Update WC_DIR_NAME. *ARGS are any extra optional args to the
517 update subcommand. NOTE: If *ARGS is specified at all, explicit
518 target paths must be passed in *ARGS as well (or a default `.' will
519 be chosen by the 'svn' binary). This allows the caller to update
520 many items in a single working copy dir, but still verify the entire
523 If ERROR_RE_STRING, the update must exit with error, and the error
524 message must match regular expression ERROR_RE_STRING.
526 Else if ERROR_RE_STRING is None, then:
528 The subcommand output will be verified against OUTPUT_TREE, and the
529 working copy itself will be verified against DISK_TREE. If optional
530 STATUS_TREE is given, then 'svn status' output will be compared.
531 (This is a good way to check that revision numbers were bumped.)
532 SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to
533 tree.compare_trees - see that function's doc string for more
536 If CHECK_PROPS is set, then disk comparison will examine props.
537 Returns if successful, raises on failure."""
539 if isinstance(output_tree
, wc
.State
):
540 output_tree
= output_tree
.old_tree()
541 if isinstance(disk_tree
, wc
.State
):
542 disk_tree
= disk_tree
.old_tree()
543 if isinstance(status_tree
, wc
.State
):
544 status_tree
= status_tree
.old_tree()
546 # Update and make a tree of the output.
548 output
, errput
= main
.run_svn (error_re_string
, 'up', *args
)
550 output
, errput
= main
.run_svn (error_re_string
, 'up', wc_dir_name
, *args
)
552 if (error_re_string
):
553 rm
= re
.compile(error_re_string
)
555 match
= rm
.search(line
)
558 raise main
.SVNUnmatchedError
560 actual
= tree
.build_tree_from_checkout (output
)
561 verify_update (actual
, wc_dir_name
,
562 output_tree
, disk_tree
, status_tree
,
563 singleton_handler_a
, a_baton
,
564 singleton_handler_b
, b_baton
,
568 def run_and_verify_merge(dir, rev1
, rev2
, url
,
569 output_tree
, disk_tree
, status_tree
, skip_tree
,
570 error_re_string
= None,
571 singleton_handler_a
= None,
573 singleton_handler_b
= None,
578 """Run 'svn merge -rREV1:REV2 URL DIR', leaving off the '-r'
579 argument if both REV1 and REV2 are None."""
581 run_and_verify_merge2(dir, rev1
, rev2
, url
, None, output_tree
, disk_tree
,
582 status_tree
, skip_tree
, error_re_string
,
583 singleton_handler_a
, a_baton
, singleton_handler_b
,
584 b_baton
, check_props
, dry_run
, *args
)
586 run_and_verify_merge2(dir, rev1
, rev2
, url
, None, output_tree
, disk_tree
,
587 status_tree
, skip_tree
, error_re_string
,
588 singleton_handler_a
, a_baton
, singleton_handler_b
,
589 b_baton
, check_props
, dry_run
)
592 def run_and_verify_merge2(dir, rev1
, rev2
, url1
, url2
,
593 output_tree
, disk_tree
, status_tree
, skip_tree
,
594 error_re_string
= None,
595 singleton_handler_a
= None,
597 singleton_handler_b
= None,
602 """Run 'svn merge URL1@REV1 URL2@REV2 DIR' if URL2 is not None
603 (for a three-way merge between URLs and WC).
605 If URL2 is None, run 'svn merge -rREV1:REV2 URL1 DIR'. If both REV1
606 and REV2 are None, leave off the '-r' argument.
608 If ERROR_RE_STRING, the merge must exit with error, and the error
609 message must match regular expression ERROR_RE_STRING.
611 Else if ERROR_RE_STRING is None, then:
613 The subcommand output will be verified against OUTPUT_TREE, and the
614 working copy itself will be verified against DISK_TREE. If optional
615 STATUS_TREE is given, then 'svn status' output will be compared.
616 The 'skipped' merge output will be compared to SKIP_TREE.
617 SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to
618 tree.compare_trees - see that function's doc string for more
621 If CHECK_PROPS is set, then disk comparison will examine props.
623 If DRY_RUN is set then a --dry-run merge will be carried out first and
624 the output compared with that of the full merge.
626 Returns if successful, raises on failure."""
628 if isinstance(output_tree
, wc
.State
):
629 output_tree
= output_tree
.old_tree()
630 if isinstance(disk_tree
, wc
.State
):
631 disk_tree
= disk_tree
.old_tree()
632 if isinstance(status_tree
, wc
.State
):
633 status_tree
= status_tree
.old_tree()
634 if isinstance(skip_tree
, wc
.State
):
635 skip_tree
= skip_tree
.old_tree()
637 merge_command
= [ "merge" ]
639 merge_command
.extend((url1
+ "@" + str(rev1
), url2
+ "@" + str(rev2
)))
641 if not (rev1
is None and rev2
is None):
642 merge_command
.append("-r" + str(rev1
) + ":" + str(rev2
))
643 merge_command
.append(url1
)
644 merge_command
.append(dir)
645 merge_command
= tuple(merge_command
)
648 pre_disk
= tree
.build_tree_from_wc(dir)
649 dry_run_command
= merge_command
+ ('--dry-run',)
650 dry_run_command
= dry_run_command
+ args
651 out_dry
, err_dry
= main
.run_svn(error_re_string
, *dry_run_command
)
652 post_disk
= tree
.build_tree_from_wc(dir)
654 tree
.compare_trees(post_disk
, pre_disk
)
655 except tree
.SVNTreeError
:
656 print "============================================================="
657 print "Dry-run merge altered working copy"
658 print "============================================================="
662 # Update and make a tree of the output.
663 merge_command
= merge_command
+ args
664 out
, err
= main
.run_svn (error_re_string
, *merge_command
)
667 if not error_re_string
.startswith(".*"):
668 error_re_string
= ".*(" + error_re_string
+ ")"
669 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
670 verify
.verify_outputs(None, None, err
, None, expected_err
)
673 raise verify
.SVNUnexpectedStderr(err
)
675 if dry_run
and out
!= out_dry
:
676 print "============================================================="
677 print "Merge outputs differ"
678 print "The dry-run merge output:"
679 map(sys
.stdout
.write
, out_dry
)
680 print "The full merge output:"
681 map(sys
.stdout
.write
, out
)
682 print "============================================================="
683 raise main
.SVNUnmatchedError
685 def missing_skip(a
, b
):
686 print "============================================================="
687 print "Merge failed to skip: " + a
.path
688 print "============================================================="
690 def extra_skip(a
, b
):
691 print "============================================================="
692 print "Merge unexpectedly skipped: " + a
.path
693 print "============================================================="
696 myskiptree
= tree
.build_tree_from_skipped(out
)
697 tree
.compare_trees(myskiptree
, skip_tree
,
698 extra_skip
, None, missing_skip
, None)
700 actual
= tree
.build_tree_from_checkout(out
, 0)
701 verify_update (actual
, dir,
702 output_tree
, disk_tree
, status_tree
,
703 singleton_handler_a
, a_baton
,
704 singleton_handler_b
, b_baton
,
708 def run_and_verify_mergeinfo(error_re_string
= None,
709 expected_output
= {},
711 """Run 'svn mergeinfo ARGS', and compare the result against
712 EXPECTED_OUTPUT, a dict of dict of tuples:
713 { path : { source path : (merged ranges, eligible ranges) } }
714 Raise an exception if an unexpected output is encountered."""
716 mergeinfo_command
= ["mergeinfo"]
717 mergeinfo_command
.extend(args
)
718 out
, err
= main
.run_svn(error_re_string
, *mergeinfo_command
)
721 if not error_re_string
.startswith(".*"):
722 error_re_string
= ".*(" + error_re_string
+ ")"
723 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
724 verify
.verify_outputs(None, None, err
, None, expected_err
)
727 parser
= parsers
.MergeinfoReportParser()
730 if len(expected_output
.keys()) != len(parser
.report
.keys()):
731 raise verify
.SVNUnexpectedStdout("Unexpected number of target paths")
733 for actual_path
in parser
.report
.keys():
734 actual_src_paths
= parser
.report
[actual_path
]
735 expected_src_paths
= expected_output
[actual_path
]
737 if len(actual_src_paths
.keys()) != len(expected_src_paths
.keys()):
738 raise verify
.SVNUnexpectedStdout("Unexpected number of source paths "
739 "for target path '%s'" % actual_path
)
741 for src_path
in actual_src_paths
.keys():
742 (actual_merged
, actual_eligible
) = actual_src_paths
[src_path
]
743 (expected_merged
, expected_eligible
) = expected_src_paths
[src_path
]
745 if actual_merged
!= expected_merged
:
746 raise Exception("Unexpected merged ranges for target path '%s' and "
747 "source path '%s': Expected '%s', got '%s'" %
748 (actual_path
, src_path
, expected_merged
,
750 if actual_eligible
!= expected_eligible
:
751 raise Exception("Unexpected eligible ranges for target path '%s' and "
752 "source path '%s': Expected '%s', got '%s'" %
753 (actual_path
, src_path
, expected_eligible
,
757 def run_and_verify_switch(wc_dir_name
,
760 output_tree
, disk_tree
, status_tree
,
761 error_re_string
= None,
762 singleton_handler_a
= None,
764 singleton_handler_b
= None,
769 """Switch WC_TARGET (in working copy dir WC_DIR_NAME) to SWITCH_URL.
771 If ERROR_RE_STRING, the switch must exit with error, and the error
772 message must match regular expression ERROR_RE_STRING.
774 Else if ERROR_RE_STRING is None, then:
776 The subcommand output will be verified against OUTPUT_TREE, and the
777 working copy itself will be verified against DISK_TREE. If optional
778 STATUS_TREE is given, then 'svn status' output will be
779 compared. (This is a good way to check that revision numbers were
780 bumped.) SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to
781 tree.compare_trees - see that function's doc string for more details.
782 If CHECK_PROPS is set, then disk comparison will examine props.
783 Returns if successful, raises on failure."""
785 if isinstance(output_tree
, wc
.State
):
786 output_tree
= output_tree
.old_tree()
787 if isinstance(disk_tree
, wc
.State
):
788 disk_tree
= disk_tree
.old_tree()
789 if isinstance(status_tree
, wc
.State
):
790 status_tree
= status_tree
.old_tree()
792 # Update and make a tree of the output.
793 output
, errput
= main
.run_svn (error_re_string
, 'switch',
794 switch_url
, wc_target
, *args
)
797 if not error_re_string
.startswith(".*"):
798 error_re_string
= ".*(" + error_re_string
+ ")"
799 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
800 verify
.verify_outputs(None, None, errput
, None, expected_err
)
803 raise verify
.SVNUnexpectedStderr(err
)
805 actual
= tree
.build_tree_from_checkout (output
)
807 verify_update (actual
, wc_dir_name
,
808 output_tree
, disk_tree
, status_tree
,
809 singleton_handler_a
, a_baton
,
810 singleton_handler_b
, b_baton
,
814 def run_and_verify_commit(wc_dir_name
, output_tree
, status_tree
,
815 error_re_string
= None,
816 singleton_handler_a
= None,
818 singleton_handler_b
= None,
821 """Commit and verify results within working copy WC_DIR_NAME,
822 sending ARGS to the commit subcommand.
824 The subcommand output will be verified against OUTPUT_TREE. If
825 optional STATUS_TREE is given, then 'svn status' output will
826 be compared. (This is a good way to check that revision numbers
829 If ERROR_RE_STRING is None, the commit must not exit with error. If
830 ERROR_RE_STRING is a string, the commit must exit with error, and
831 the error message must match regular expression ERROR_RE_STRING.
833 SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to
834 tree.compare_trees - see that function's doc string for more
835 details. Returns if successful, raises on failure."""
837 if isinstance(output_tree
, wc
.State
):
838 output_tree
= output_tree
.old_tree()
839 if isinstance(status_tree
, wc
.State
):
840 status_tree
= status_tree
.old_tree()
843 output
, errput
= main
.run_svn(error_re_string
, 'ci',
848 if not error_re_string
.startswith(".*"):
849 error_re_string
= ".*(" + error_re_string
+ ")"
850 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
851 verify
.verify_outputs(None, None, errput
, None, expected_err
)
854 # Else not expecting error:
856 # Remove the final output line, and verify that the commit succeeded.
859 lastline
= output
.pop().strip()
861 cm
= re
.compile("(Committed|Imported) revision [0-9]+.")
862 match
= cm
.search(lastline
)
864 print "ERROR: commit did not succeed."
865 print "The final line from 'svn ci' was:"
867 raise main
.SVNCommitFailure
869 # The new 'final' line in the output is either a regular line that
870 # mentions {Adding, Deleting, Sending, ...}, or it could be a line
871 # that says "Transmitting file data ...". If the latter case, we
872 # want to remove the line from the output; it should be ignored when
875 lastline
= output
.pop()
877 tm
= re
.compile("Transmitting file data.+")
878 match
= tm
.search(lastline
)
880 # whoops, it was important output, put it back.
881 output
.append(lastline
)
883 # Convert the output into a tree.
884 actual
= tree
.build_tree_from_commit (output
)
886 # Verify actual output against expected output.
888 tree
.compare_trees (actual
, output_tree
)
889 except tree
.SVNTreeError
:
890 verify
.display_trees("Output of commit is unexpected",
891 "OUTPUT TREE", output_tree
, actual
)
894 # Verify via 'status' command too, if possible.
896 run_and_verify_status(wc_dir_name
, status_tree
)
899 # This function always passes '-q' to the status command, which
900 # suppresses the printing of any unversioned or nonexistent items.
901 def run_and_verify_status(wc_dir_name
, output_tree
,
902 singleton_handler_a
= None,
904 singleton_handler_b
= None,
906 """Run 'status' on WC_DIR_NAME and compare it with the
907 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
908 be passed to tree.compare_trees - see that function's doc string for
910 Returns on success, raises on failure."""
912 if isinstance(output_tree
, wc
.State
):
913 output_tree
= output_tree
.old_tree()
915 output
, errput
= main
.run_svn (None, 'status', '-v', '-u', '-q',
918 actual
= tree
.build_tree_from_status (output
)
920 # Verify actual output against expected output.
922 tree
.compare_trees (actual
, output_tree
,
923 singleton_handler_a
, a_baton
,
924 singleton_handler_b
, b_baton
)
925 except tree
.SVNTreeError
:
926 verify
.display_trees(None, 'STATUS OUTPUT TREE', output_tree
, actual
)
930 # A variant of previous func, but doesn't pass '-q'. This allows us
931 # to verify unversioned or nonexistent items in the list.
932 def run_and_verify_unquiet_status(wc_dir_name
, output_tree
,
933 singleton_handler_a
= None,
935 singleton_handler_b
= None,
937 """Run 'status' on WC_DIR_NAME and compare it with the
938 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
939 be passed to tree.compare_trees - see that function's doc string for
941 Returns on success, raises on failure."""
943 if isinstance(output_tree
, wc
.State
):
944 output_tree
= output_tree
.old_tree()
946 output
, errput
= main
.run_svn (None, 'status', '-v', '-u', wc_dir_name
)
948 actual
= tree
.build_tree_from_status (output
)
950 # Verify actual output against expected output.
951 if (singleton_handler_a
or singleton_handler_b
):
952 tree
.compare_trees (actual
, output_tree
,
953 singleton_handler_a
, a_baton
,
954 singleton_handler_b
, b_baton
)
956 tree
.compare_trees (actual
, output_tree
)
958 def run_and_verify_diff_summarize_xml(error_re_string
= [],
959 expected_prefix
= None,
965 """Run 'diff --summarize --xml' with the arguments *ARGS, which should
966 contain all arguments beyond for your 'diff --summarize --xml' omitting
967 said arguments. EXPECTED_PREFIX will store a "common" path prefix
968 expected to be at the beginning of each summarized path. If
969 EXPECTED_PREFIX is None, then EXPECTED_PATHS will need to be exactly
970 as 'svn diff --summarize --xml' will output. If ERROR_RE_STRING, the
971 command must exit with error, and the error message must match regular
972 expression ERROR_RE_STRING.
974 Else if ERROR_RE_STRING is None, the subcommand output will be parsed
975 into an XML document and will then be verified by comparing the parsed
976 output to the contents in the EXPECTED_PATHS, EXPECTED_ITEMS,
977 EXPECTED_PROPS and EXPECTED_KINDS. Returns on success, raises
980 output
, errput
= run_and_verify_svn(None, None, error_re_string
, 'diff',
981 '--summarize', '--xml', *args
)
983 # Return if errors are present since they were expected
987 doc
= parseString(''.join(output
))
988 paths
= doc
.getElementsByTagName("path")
989 items
= expected_items
990 kinds
= expected_kinds
993 modified_path
= path
.childNodes
[0].data
995 if (expected_prefix
is not None
996 and modified_path
.find(expected_prefix
) == 0):
997 modified_path
= modified_path
.replace(expected_prefix
, '')[1:].strip()
999 # Workaround single-object diff
1000 if len(modified_path
) == 0:
1001 modified_path
= path
.childNodes
[0].data
.split(os
.sep
)[-1]
1003 # From here on, we use '/' as path separator.
1005 modified_path
= modified_path
.replace(os
.sep
, "/")
1007 if modified_path
not in expected_paths
:
1008 print "ERROR: %s not expected in the changed paths." % modified_path
1011 index
= expected_paths
.index(modified_path
)
1012 expected_item
= items
[index
]
1013 expected_kind
= kinds
[index
]
1014 expected_prop
= expected_props
[index
]
1015 actual_item
= path
.getAttribute('item')
1016 actual_kind
= path
.getAttribute('kind')
1017 actual_prop
= path
.getAttribute('props')
1019 if expected_item
!= actual_item
:
1020 print "ERROR: expected:", expected_item
, "actual:", actual_item
1023 if expected_kind
!= actual_kind
:
1024 print "ERROR: expected:", expected_kind
, "actual:", actual_kind
1027 if expected_prop
!= actual_prop
:
1028 print "ERROR: expected:", expected_prop
, "actual:", actual_prop
1031 def run_and_verify_diff_summarize(output_tree
, error_re_string
= None,
1032 singleton_handler_a
= None,
1034 singleton_handler_b
= None,
1037 """Run 'diff --summarize' with the arguments *ARGS.
1038 If ERROR_RE_STRING, the command must exit with error, and the error
1039 message must match regular expression ERROR_RE_STRING.
1041 Else if ERROR_RE_STRING is None, the subcommand output will be
1042 verified against OUTPUT_TREE. SINGLETON_HANDLER_A and
1043 SINGLETON_HANDLER_B will be passed to tree.compare_trees - see that
1044 function's doc string for more details. Returns on success, raises
1047 if isinstance(output_tree
, wc
.State
):
1048 output_tree
= output_tree
.old_tree()
1050 output
, errput
= main
.run_svn (None, 'diff', '--summarize',
1054 if not error_re_string
.startswith(".*"):
1055 error_re_string
= ".*(" + error_re_string
+ ")"
1056 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
1057 verify
.verify_outputs(None, None, errput
, None, expected_err
)
1060 actual
= tree
.build_tree_from_diff_summarize (output
)
1062 # Verify actual output against expected output.
1064 tree
.compare_trees (actual
, output_tree
,
1065 singleton_handler_a
, a_baton
,
1066 singleton_handler_b
, b_baton
)
1067 except tree
.SVNTreeError
:
1068 verify
.display_trees(None, 'DIFF OUTPUT TREE', output_tree
, actual
)
1071 def run_and_validate_lock(path
, username
):
1072 """`svn lock' the given path and validate the contents of the lock.
1073 Use the given username. This is important because locks are
1076 comment
= "Locking path:%s." % path
1079 run_and_verify_svn(None, ".*locked by user", [], 'lock',
1080 '--username', username
,
1081 '-m', comment
, path
)
1083 # Run info and check that we get the lock fields.
1084 output
, err
= run_and_verify_svn(None, None, [],
1088 ### TODO: Leverage RegexOuput([...], match_all=True) here.
1089 # prepare the regexs to compare against
1090 token_re
= re
.compile (".*?Lock Token: opaquelocktoken:.*?", re
.DOTALL
)
1091 author_re
= re
.compile (".*?Lock Owner: %s\n.*?" % username
, re
.DOTALL
)
1092 created_re
= re
.compile (".*?Lock Created:.*?", re
.DOTALL
)
1093 comment_re
= re
.compile (".*?%s\n.*?" % re
.escape(comment
), re
.DOTALL
)
1094 # join all output lines into one
1095 output
= "".join(output
)
1096 # Fail even if one regex does not match
1097 if ( not (token_re
.match(output
) and \
1098 author_re
.match(output
) and \
1099 created_re
.match(output
) and \
1100 comment_re
.match(output
))):
1103 ######################################################################
1104 # Other general utilities
1107 # This allows a test to *quickly* bootstrap itself.
1108 def make_repo_and_wc(sbox
, create_wc
= True):
1109 """Create a fresh repository and checkout a wc from it.
1111 The repo and wc directories will both be named TEST_NAME, and
1112 repsectively live within the global dirs 'general_repo_dir' and
1113 'general_wc_dir' (variables defined at the top of this test
1114 suite.) Returns on success, raises on failure."""
1116 # Create (or copy afresh) a new repos with a greek tree in it.
1117 guarantee_greek_repository(sbox
.repo_dir
)
1120 # Generate the expected output tree.
1121 expected_output
= main
.greek_state
.copy()
1122 expected_output
.wc_dir
= sbox
.wc_dir
1123 expected_output
.tweak(status
='A ', contents
=None)
1125 # Generate an expected wc tree.
1126 expected_wc
= main
.greek_state
1128 # Do a checkout, and verify the resulting output and disk contents.
1129 run_and_verify_checkout(sbox
.repo_url
,
1134 # just make sure the parent folder of our working copy is created
1136 os
.mkdir(main
.general_wc_dir
)
1137 except OSError, err
:
1138 if err
.errno
!= errno
.EEXIST
:
1141 # Duplicate a working copy or other dir.
1142 def duplicate_dir(wc_name
, wc_copy_name
):
1143 """Copy the working copy WC_NAME to WC_COPY_NAME. Overwrite any
1144 existing tree at that location."""
1146 main
.safe_rmtree(wc_copy_name
)
1147 shutil
.copytree(wc_name
, wc_copy_name
)
1151 def get_virginal_state(wc_dir
, rev
):
1152 "Return a virginal greek tree state for a WC and repos at revision REV."
1154 rev
= str(rev
) ### maybe switch rev to an integer?
1156 # copy the greek tree, shift it to the new wc_dir, insert a root elem,
1157 # then tweak all values
1158 state
= main
.greek_state
.copy()
1159 state
.wc_dir
= wc_dir
1160 state
.desc
[''] = wc
.StateItem()
1161 state
.tweak(contents
=None, status
=' ', wc_rev
=rev
)
1165 def remove_admin_tmp_dir(wc_dir
):
1166 "Remove the tmp directory within the administrative directory."
1168 tmp_path
= os
.path
.join(wc_dir
, main
.get_admin_name(), 'tmp')
1169 ### Any reason not to use main.safe_rmtree()?
1170 os
.rmdir(os
.path
.join(tmp_path
, 'prop-base'))
1171 os
.rmdir(os
.path
.join(tmp_path
, 'props'))
1172 os
.rmdir(os
.path
.join(tmp_path
, 'text-base'))
1175 # Cheap administrative directory locking
1176 def lock_admin_dir(wc_dir
):
1177 "Lock a SVN administrative directory"
1179 path
= os
.path
.join(wc_dir
, main
.get_admin_name(), 'lock')
1180 main
.file_append(path
, "stop looking!")
1182 def enable_revprop_changes(repo_dir
):
1183 """Enable revprop changes in a repository REPOS_DIR by creating a
1184 pre-revprop-change hook script and (if appropriate) making it executable."""
1186 hook_path
= main
.get_pre_revprop_change_hook_path (repo_dir
)
1187 main
.create_python_hook_script (hook_path
, 'import sys; sys.exit(0)')
1189 def disable_revprop_changes(repo_dir
, message
):
1190 """Disable revprop changes in a repository REPO_DIR by creating a
1191 pre-revprop-change hook script like enable_revprop_changes, except that
1192 the hook prints MESSAGE to stderr and exits non-zero. MESSAGE is printed
1193 very simply, and should have no newlines or quotes."""
1195 hook_path
= main
.get_pre_revprop_change_hook_path (repo_dir
)
1196 main
.create_python_hook_script (hook_path
,
1198 'sys.stderr.write("%s")\n'
1199 'sys.exit(1)\n' % (message
,))
1201 def create_failing_post_commit_hook(repo_dir
):
1202 """Disable commits in a repository REPOS_DIR by creating a post-commit hook
1203 script which always reports errors."""
1205 hook_path
= main
.get_post_commit_hook_path (repo_dir
)
1206 main
.create_python_hook_script (hook_path
, 'import sys; '
1207 'sys.stderr.write("Post-commit hook failed"); '
1210 # set_prop can be used for binary properties are values like '*' which are not
1211 # handled correctly when specified on the command line.
1212 def set_prop(expected_err
, name
, value
, path
, valp
):
1213 """Set a property with value from a file"""
1214 valf
= open(valp
, 'wb')
1219 main
.run_svn(expected_err
, 'propset', '-F', valp
, name
, path
)
1221 def check_prop(name
, path
, exp_out
):
1222 """Verify that property NAME on PATH has a value of EXP_OUT"""
1223 # Not using run_svn because binary_mode must be set
1224 out
, err
= main
.run_command(main
.svn_binary
, None, 1, 'pg', '--strict',
1225 name
, path
, '--config-dir',
1226 main
.default_config_dir
)
1228 print "svn pg --strict", name
, "output does not match expected."
1229 print "Expected standard output: ", exp_out
, "\n"
1230 print "Actual standard output: ", out
, "\n"
1233 def fill_file_with_lines(wc_path
, line_nbr
, line_descrip
=None,
1235 """Change the file at WC_PATH (adding some lines), and return its
1236 new contents. LINE_NBR indicates the line number at which the new
1237 contents should assume that it's being appended. LINE_DESCRIP is
1238 something like 'This is line' (the default) or 'Conflicting line'."""
1240 if line_descrip
is None:
1241 line_descrip
= "This is line"
1243 # Generate the new contents for the file.
1245 for n
in range(line_nbr
, line_nbr
+ 3):
1246 contents
= contents
+ line_descrip
+ " " + `n`
+ " in '" + \
1247 os
.path
.basename(wc_path
) + "'.\n"
1249 # Write the new contents to the file.
1251 main
.file_append(wc_path
, contents
)
1253 main
.file_write(wc_path
, contents
)
1257 def inject_conflict_into_wc(sbox
, state_path
, file_path
,
1258 expected_disk
, expected_status
, merged_rev
):
1259 """Create a conflict at FILE_PATH by replacing its contents,
1260 committing the change, backdating it to its previous revision,
1261 changing its contents again, then updating it to merge in the
1264 wc_dir
= sbox
.wc_dir
1266 # Make a change to the file.
1267 contents
= fill_file_with_lines(file_path
, 1, "This is line", append
=False)
1269 # Commit the changed file, first taking note of the current revision.
1270 prev_rev
= expected_status
.desc
[state_path
].wc_rev
1271 expected_output
= wc
.State(wc_dir
, {
1272 state_path
: wc
.StateItem(verb
='Sending'),
1275 expected_status
.tweak(state_path
, wc_rev
=merged_rev
)
1276 run_and_verify_commit(wc_dir
, expected_output
, expected_status
,
1277 None, None, None, None, None,
1280 # Backdate the file.
1281 output
, errput
= main
.run_svn(None, "up", "-r", str(prev_rev
),
1284 expected_status
.tweak(state_path
, wc_rev
=prev_rev
)
1286 # Make a conflicting change to the file, and backdate the file.
1287 conflicting_contents
= fill_file_with_lines(file_path
, 1, "Conflicting line",
1290 # Merge the previous change into the file to produce a conflict.
1292 expected_disk
.tweak(state_path
, contents
="")
1293 expected_output
= wc
.State(wc_dir
, {
1294 state_path
: wc
.StateItem(status
='C '),
1296 inject_conflict_into_expected_state(state_path
,
1297 expected_disk
, expected_status
,
1298 conflicting_contents
, contents
,
1300 output
, errput
= main
.run_svn(None, "up", "-r", str(merged_rev
),
1301 sbox
.repo_url
+ "/" + state_path
, file_path
)
1303 expected_status
.tweak(state_path
, wc_rev
=merged_rev
)
1305 def inject_conflict_into_expected_state(state_path
,
1306 expected_disk
, expected_status
,
1307 wc_text
, merged_text
, merged_rev
):
1308 """Update the EXPECTED_DISK and EXPECTED_STATUS trees for the
1309 conflict at STATE_PATH (ignored if None). WC_TEXT, MERGED_TEXT, and
1310 MERGED_REV are used to determine the contents of the conflict (the
1311 text parameters should be newline-terminated)."""
1313 conflict_marker
= make_conflict_marker_text(wc_text
, merged_text
,
1315 existing_text
= expected_disk
.desc
[state_path
].contents
or ""
1316 expected_disk
.tweak(state_path
, contents
=existing_text
+ conflict_marker
)
1319 expected_status
.tweak(state_path
, status
='C ')
1321 def make_conflict_marker_text(wc_text
, merged_text
, merged_rev
):
1322 """Return the conflict marker text described by WC_TEXT (the current
1323 text in the working copy, MERGED_TEXT (the conflicting text merged
1324 in), and MERGED_REV (the revision from whence the conflicting text
1326 return "<<<<<<< .working\n" + wc_text
+ "=======\n" + \
1327 merged_text
+ ">>>>>>> .merge-right.r" + str(merged_rev
) + "\n"