Make the cmdline tests print out the complete actual tree and meta-
[svnrdump.git] / svntest / actions.py
blob49779ab7d1c0f7c50583165b8fed8db03eec9a99
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
24 from svntest import Failure
26 def no_sleep_for_timestamps():
27 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'yes'
29 def do_sleep_for_timestamps():
30 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'no'
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 exit_code, output, errput = main.run_svn(None, 'import', '-m',
58 'Log message for revision 1.',
59 main.greek_dump_dir,
60 main.pristine_url)
62 # check for any errors from the import
63 if len(errput):
64 display_lines("Errors during initial 'svn import':",
65 'STDERR', None, errput)
66 sys.exit(1)
68 # verify the printed output of 'svn import'.
69 lastline = output.pop().strip()
70 cm = re.compile ("(Committed|Imported) revision [0-9]+.")
71 match = cm.search (lastline)
72 if not match:
73 print "ERROR: import did not succeed, while creating greek repos."
74 print "The final line from 'svn import' was:"
75 print lastline
76 sys.exit(1)
77 output_tree = tree.build_tree_from_commit(output)
79 ### due to path normalization in the .old_tree() method, we cannot
80 ### prepend the necessary '.' directory. thus, let's construct an old
81 ### tree manually from the greek_state.
82 output_list = []
83 for greek_path in main.greek_state.desc.keys():
84 output_list.append([ os.path.join(main.greek_dump_dir, greek_path),
85 None, {}, {'verb' : 'Adding'}])
86 expected_output_tree = tree.build_generic_tree(output_list)
88 try:
89 tree.compare_trees("output", output_tree, expected_output_tree)
90 except tree.SVNTreeUnequal:
91 verify.display_trees("ERROR: output of import command is unexpected.",
92 "OUTPUT TREE", expected_output_tree, output_tree)
93 sys.exit(1)
95 # Finally, disallow any changes to the "pristine" repos.
96 error_msg = "Don't modify the pristine repository"
97 create_failing_hook(main.pristine_dir, 'start-commit', error_msg)
98 create_failing_hook(main.pristine_dir, 'pre-lock', error_msg)
99 create_failing_hook(main.pristine_dir, 'pre-revprop-change', error_msg)
102 ######################################################################
103 # Used by every test, so that they can run independently of one
104 # another. Every time this routine is called, it recursively copies
105 # the `pristine repos' to a new location.
106 # Note: make sure setup_pristine_repository was called once before
107 # using this function.
109 def guarantee_greek_repository(path):
110 """Guarantee that a local svn repository exists at PATH, containing
111 nothing but the greek-tree at revision 1."""
113 if path == main.pristine_dir:
114 print "ERROR: attempt to overwrite the pristine repos! Aborting."
115 sys.exit(1)
117 # copy the pristine repository to PATH.
118 main.safe_rmtree(path)
119 if main.copy_repos(main.pristine_dir, path, 1):
120 print "ERROR: copying repository failed."
121 sys.exit(1)
123 # make the repos world-writeable, for mod_dav_svn's sake.
124 main.chmod_tree(path, 0666, 0666)
127 def run_and_verify_svnlook(message, expected_stdout,
128 expected_stderr, *varargs):
129 """Like run_and_verify_svnlook2, but the expected exit code is
130 assumed to be 0 if no output is expected on stderr, and 1 otherwise.
132 Exit code is not checked on platforms without Popen3 - see note in
133 run_and_verify_svn2."""
134 expected_exit = 0
135 if expected_stderr is not None and expected_stderr != []:
136 expected_exit = 1
137 return run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
138 expected_exit, *varargs)
140 def run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
141 expected_exit, *varargs):
142 """Run svnlook command and check its output and exit code.
144 Exit code is not checked on platforms without Popen3 - see note in
145 run_and_verify_svn2."""
146 exit_code, out, err = main.run_svnlook(*varargs)
147 verify.verify_outputs("Unexpected output", out, err,
148 expected_stdout, expected_stderr)
149 verify.verify_exit_code(message, exit_code, expected_exit)
150 return exit_code, out, err
153 def run_and_verify_svnadmin(message, expected_stdout,
154 expected_stderr, *varargs):
155 """Like run_and_verify_svnadmin2, but the expected exit code is
156 assumed to be 0 if no output is expected on stderr, and 1 otherwise.
158 Exit code is not checked on platforms without Popen3 - see note in
159 run_and_verify_svn2."""
160 expected_exit = 0
161 if expected_stderr is not None and expected_stderr != []:
162 expected_exit = 1
163 return run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
164 expected_exit, *varargs)
166 def run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
167 expected_exit, *varargs):
168 """Run svnadmin command and check its output and exit code.
170 Exit code is not checked on platforms without Popen3 - see note in
171 run_and_verify_svn2."""
172 exit_code, out, err = main.run_svnadmin(*varargs)
173 verify.verify_outputs("Unexpected output", out, err,
174 expected_stdout, expected_stderr)
175 verify.verify_exit_code(message, exit_code, expected_exit)
176 return exit_code, out, err
179 def run_and_verify_svnversion(message, wc_dir, repo_url,
180 expected_stdout, expected_stderr):
181 """like run_and_verify_svnversion2, but the expected exit code is
182 assumed to be 0 if no output is expected on stderr, and 1 otherwise.
184 Exit code is not checked on platforms without Popen3 - see note in
185 run_and_verify_svn2."""
186 expected_exit = 0
187 if expected_stderr is not None and expected_stderr != []:
188 expected_exit = 1
189 return run_and_verify_svnversion2(message, wc_dir, repo_url,
190 expected_stdout, expected_stderr,
191 expected_exit)
193 def run_and_verify_svnversion2(message, wc_dir, repo_url,
194 expected_stdout, expected_stderr,
195 expected_exit):
196 """Run svnversion command and check its output and exit code.
198 Exit code is not checked on platforms without Popen3 - see note in
199 run_and_verify_svn2."""
200 exit_code, out, err = main.run_svnversion(wc_dir, repo_url)
201 verify.verify_outputs("Unexpected output", out, err,
202 expected_stdout, expected_stderr)
203 verify.verify_exit_code(message, exit_code, expected_exit)
204 return exit_code, out, err
206 def run_and_verify_svn(message, expected_stdout, expected_stderr, *varargs):
207 """like run_and_verify_svn2, but the expected exit code is assumed to
208 be 0 if no output is expected on stderr, and 1 otherwise.
210 Exit code is not checked on platforms without Popen3 - see note in
211 run_and_verify_svn2."""
213 expected_exit = 0
214 if expected_stderr is not None and expected_stderr != []:
215 expected_exit = 1
216 return run_and_verify_svn2(message, expected_stdout, expected_stderr,
217 expected_exit, *varargs)
219 def run_and_verify_svn2(message, expected_stdout, expected_stderr,
220 expected_exit, *varargs):
221 """Invokes main.run_svn() with *VARARGS, returns exit code as int, stdout
222 and stderr as lists of lines. For both EXPECTED_STDOUT and EXPECTED_STDERR,
223 create an appropriate instance of verify.ExpectedOutput (if necessary):
225 - If it is an array of strings, create a vanilla ExpectedOutput.
227 - If it is a single string, create a RegexOutput.
229 - If it is already an instance of ExpectedOutput
230 (e.g. UnorderedOutput), leave it alone.
232 ...and invoke compare_and_display_lines() on MESSAGE, a label based
233 on the name of the stream being compared (e.g. STDOUT), the
234 ExpectedOutput instance, and the actual output.
236 If EXPECTED_STDOUT is None, do not check stdout.
237 EXPECTED_STDERR may not be None.
239 If output checks pass, on supported platforms (namely those with the Popen3
240 class), the expected and actual codes are compared. On platforms lacking
241 Popen3, the actual exit code is unavailable and a value of None is returned
242 as the exit code from this and all other run_...() functions.
244 If a comparison fails, a Failure will be raised."""
246 if expected_stderr is None:
247 raise verify.SVNIncorrectDatatype("expected_stderr must not be None")
249 want_err = None
250 if expected_stderr is not None and expected_stderr != []:
251 want_err = True
253 exit_code, out, err = main.run_svn(want_err, *varargs)
254 verify.verify_outputs(message, out, err, expected_stdout, expected_stderr)
255 verify.verify_exit_code(message, exit_code, expected_exit)
256 return exit_code, out, err
258 def run_and_verify_svn_match_any(message, expected_stdout, expected_stderr,
259 *varargs):
260 """Like run_and_verify_svn_match_any2, but the expected exit code is
261 assumed to be 0 if no output is expected on stderr, and 1 otherwise.
263 Exit code is not checked on platforms without Popen3 - see note in
264 run_and_verify_svn2."""
265 expected_exit = 0
266 if expected_stderr is not None and expected_stderr != []:
267 expected_exit = 1
268 return run_and_verify_svn_match_any2(message, expected_stdout,
269 expected_stderr, expected_exit,
270 *varargs)
273 def run_and_verify_svn_match_any2(message, expected_stdout, expected_stderr,
274 expected_exit, *varargs):
275 """Like run_and_verify_svn2, except that only one stdout line must match
276 EXPECTED_STDOUT.
278 Exit code is not checked on platforms without Popen3 - see note in
279 run_and_verify_svn2."""
281 if expected_stderr is None:
282 raise verify.SVNIncorrectDatatype("expected_stderr must not be None")
284 want_err = None
285 if expected_stderr is not None and expected_stderr != []:
286 want_err = True
288 exit_code, out, err = main.run_svn(want_err, *varargs)
289 verify.verify_outputs(message, out, err, expected_stdout, expected_stderr,
290 False)
291 verify.verify_exit_code(message, exit_code, expected_exit)
292 return exit_code, out, err
295 def run_and_verify_load(repo_dir, dump_file_content):
296 "Runs 'svnadmin load' and reports any errors."
297 expected_stderr = []
298 exit_code, output, errput = main.run_command_stdin(
299 main.svnadmin_binary, expected_stderr, 1, dump_file_content,
300 'load', '--force-uuid', '--quiet', repo_dir)
302 verify.verify_outputs("Unexpected stderr output", None, errput,
303 None, expected_stderr)
306 def run_and_verify_dump(repo_dir):
307 "Runs 'svnadmin dump' and reports any errors, returning the dump content."
308 exit_code, output, errput = main.run_svnadmin('dump', repo_dir)
309 verify.verify_outputs("Missing expected output(s)", output, errput,
310 verify.AnyOutput, verify.AnyOutput)
311 return output
314 def load_repo(sbox, dumpfile_path = None, dump_str = None):
315 "Loads the dumpfile into sbox"
316 if not dump_str:
317 dump_str = main.file_read(dumpfile_path, "rb")
319 # Create a virgin repos and working copy
320 main.safe_rmtree(sbox.repo_dir, 1)
321 main.safe_rmtree(sbox.wc_dir, 1)
322 main.create_repos(sbox.repo_dir)
324 # Load the mergetracking dumpfile into the repos, and check it out the repo
325 run_and_verify_load(sbox.repo_dir, dump_str)
326 run_and_verify_svn(None, None, [], "co", sbox.repo_url, sbox.wc_dir)
328 return dump_str
331 ######################################################################
332 # Subversion Actions
334 # These are all routines that invoke 'svn' in particular ways, and
335 # then verify the results by comparing expected trees with actual
336 # trees.
338 # For all the functions below, the OUTPUT_TREE and DISK_TREE args need
339 # to be created by feeding carefully constructed lists to
340 # tree.build_generic_tree(). A STATUS_TREE can be built by
341 # hand, or by editing the tree returned by get_virginal_state().
344 def run_and_verify_checkout(URL, wc_dir_name, output_tree, disk_tree,
345 singleton_handler_a = None,
346 a_baton = None,
347 singleton_handler_b = None,
348 b_baton = None,
349 *args):
350 """Checkout the URL into a new directory WC_DIR_NAME. *ARGS are any
351 extra optional args to the checkout subcommand.
353 The subcommand output will be verified against OUTPUT_TREE,
354 and the working copy itself will be verified against DISK_TREE.
355 For the latter comparison, SINGLETON_HANDLER_A and
356 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
357 function's doc string for more details. Return if successful, raise
358 on failure.
360 WC_DIR_NAME is deleted if present unless the '--force' option is passed
361 in *ARGS."""
363 if isinstance(output_tree, wc.State):
364 output_tree = output_tree.old_tree()
365 if isinstance(disk_tree, wc.State):
366 disk_tree = disk_tree.old_tree()
368 # Remove dir if it's already there, unless this is a forced checkout.
369 # In that case assume we want to test a forced checkout's toleration
370 # of obstructing paths.
371 remove_wc = True
372 for arg in args:
373 if arg == '--force':
374 remove_wc = False
375 break
376 if remove_wc:
377 main.safe_rmtree(wc_dir_name)
379 # Checkout and make a tree of the output, using l:foo/p:bar
380 ### todo: svn should not be prompting for auth info when using
381 ### repositories with no auth/auth requirements
382 exit_code, output, errput = main.run_svn(None, 'co',
383 URL, wc_dir_name, *args)
384 actual = tree.build_tree_from_checkout (output)
386 # Verify actual output against expected output.
387 try:
388 tree.compare_trees ("output", actual, output_tree)
389 except tree.SVNTreeUnequal:
390 print "ACTUAL OUTPUT TREE:"
391 tree.dump_tree_script(actual, wc_dir_name + os.sep)
392 raise
394 # Create a tree by scanning the working copy
395 actual = tree.build_tree_from_wc (wc_dir_name)
397 # Verify expected disk against actual disk.
398 try:
399 tree.compare_trees ("disk", actual, disk_tree,
400 singleton_handler_a, a_baton,
401 singleton_handler_b, b_baton)
402 except tree.SVNTreeUnequal:
403 print "ACTUAL DISK TREE:"
404 tree.dump_tree_script(actual, wc_dir_name + os.sep)
405 raise
408 def run_and_verify_export(URL, export_dir_name, output_tree, disk_tree,
409 singleton_handler_a = None,
410 a_baton = None,
411 singleton_handler_b = None,
412 b_baton = None,
413 *args):
414 """Export the URL into a new directory WC_DIR_NAME.
416 The subcommand output will be verified against OUTPUT_TREE,
417 and the exported copy itself will be verified against DISK_TREE.
418 For the latter comparison, SINGLETON_HANDLER_A and
419 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
420 function's doc string for more details. Return if successful, raise
421 on failure."""
423 if isinstance(output_tree, wc.State):
424 output_tree = output_tree.old_tree()
425 if isinstance(disk_tree, wc.State):
426 disk_tree = disk_tree.old_tree()
428 # Export and make a tree of the output, using l:foo/p:bar
429 ### todo: svn should not be prompting for auth info when using
430 ### repositories with no auth/auth requirements
431 exit_code, output, errput = main.run_svn(None, 'export',
432 URL, export_dir_name, *args)
433 actual = tree.build_tree_from_checkout (output)
435 # Verify actual output against expected output.
436 try:
437 tree.compare_trees ("output", actual, output_tree)
438 except tree.SVNTreeUnequal:
439 print "ACTUAL OUTPUT TREE:"
440 tree.dump_tree_script(actual, export_dir_name + os.sep)
441 raise
443 # Create a tree by scanning the working copy. Don't ignore
444 # the .svn directories so that we generate an error if they
445 # happen to show up.
446 actual = tree.build_tree_from_wc (export_dir_name, ignore_svn=False)
448 # Verify expected disk against actual disk.
449 try:
450 tree.compare_trees ("disk", actual, disk_tree,
451 singleton_handler_a, a_baton,
452 singleton_handler_b, b_baton)
453 except tree.SVNTreeUnequal:
454 print "ACTUAL DISK TREE:"
455 tree.dump_tree_script(actual, export_dir_name + os.sep)
456 raise
459 # run_and_verify_log_xml
461 class LogEntry:
462 def __init__(self, revision, changed_paths=None, revprops=None):
463 self.revision = revision
464 if changed_paths == None:
465 self.changed_paths = {}
466 else:
467 self.changed_paths = changed_paths
468 if revprops == None:
469 self.revprops = {}
470 else:
471 self.revprops = revprops
473 def assert_changed_paths(self, changed_paths):
474 """Not implemented, so just raises svntest.Failure.
476 raise Failure('NOT IMPLEMENTED')
478 def assert_revprops(self, revprops):
479 """Assert that the dict revprops is the same as this entry's revprops.
481 Raises svntest.Failure if not.
483 if self.revprops != revprops:
484 raise Failure('\n' + '\n'.join(difflib.ndiff(
485 pprint.pformat(revprops).splitlines(),
486 pprint.pformat(self.revprops).splitlines())))
488 class LogParser:
489 def parse(self, data):
490 """Return a list of LogEntrys parsed from the sequence of strings data.
492 This is the only method of interest to callers.
494 try:
495 for i in data:
496 self.parser.Parse(i)
497 self.parser.Parse('', True)
498 except xml.parsers.expat.ExpatError, e:
499 raise verify.SVNUnexpectedStdout('%s\n%s\n' % (e, ''.join(data),))
500 return self.entries
502 def __init__(self):
503 # for expat
504 self.parser = xml.parsers.expat.ParserCreate()
505 self.parser.StartElementHandler = self.handle_start_element
506 self.parser.EndElementHandler = self.handle_end_element
507 self.parser.CharacterDataHandler = self.handle_character_data
508 # Ignore some things.
509 self.ignore_elements('log', 'paths', 'path', 'revprops')
510 self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
511 # internal state
512 self.cdata = []
513 self.property = None
514 # the result
515 self.entries = []
517 def ignore(self, *args, **kwargs):
518 del self.cdata[:]
519 def ignore_tags(self, *args):
520 for tag in args:
521 setattr(self, tag, self.ignore)
522 def ignore_elements(self, *args):
523 for element in args:
524 self.ignore_tags(element + '_start', element + '_end')
526 # expat handlers
527 def handle_start_element(self, name, attrs):
528 getattr(self, name + '_start')(attrs)
529 def handle_end_element(self, name):
530 getattr(self, name + '_end')()
531 def handle_character_data(self, data):
532 self.cdata.append(data)
534 # element handler utilities
535 def use_cdata(self):
536 result = ''.join(self.cdata).strip()
537 del self.cdata[:]
538 return result
539 def svn_prop(self, name):
540 self.entries[-1].revprops['svn:' + name] = self.use_cdata()
542 # element handlers
543 def logentry_start(self, attrs):
544 self.entries.append(LogEntry(int(attrs['revision'])))
545 def author_end(self):
546 self.svn_prop('author')
547 def msg_end(self):
548 self.svn_prop('log')
549 def date_end(self):
550 # svn:date could be anything, so just note its presence.
551 self.cdata[:] = ['']
552 self.svn_prop('date')
553 def property_start(self, attrs):
554 self.property = attrs['name']
555 def property_end(self):
556 self.entries[-1].revprops[self.property] = self.use_cdata()
558 def run_and_verify_log_xml(message=None, expected_paths=None,
559 expected_revprops=None, expected_stdout=None,
560 expected_stderr=None, args=[]):
561 """Call run_and_verify_svn with log --xml and args (optional) as command
562 arguments, and pass along message, expected_stdout, and expected_stderr.
564 If message is None, pass the svn log command as message.
566 expected_paths checking is not yet implemented.
568 expected_revprops is an optional list of dicts, compared to each
569 revision's revprops. The list must be in the same order the log entries
570 come in. Any svn:date revprops in the dicts must be '' in order to
571 match, as the actual dates could be anything.
573 expected_paths and expected_revprops are ignored if expected_stdout or
574 expected_stderr is specified.
576 if message == None:
577 message = ' '.join(args)
579 # We'll parse the output unless the caller specifies expected_stderr or
580 # expected_stdout for run_and_verify_svn.
581 parse = True
582 if expected_stderr == None:
583 expected_stderr = []
584 else:
585 parse = False
586 if expected_stdout != None:
587 parse = False
589 log_args = list(args)
590 if expected_paths != None:
591 log_args.append('-v')
593 (exit_code, stdout, stderr) = run_and_verify_svn(
594 message, expected_stdout, expected_stderr,
595 'log', '--xml', *log_args)
596 if not parse:
597 return
599 entries = LogParser().parse(stdout)
600 for index in xrange(len(entries)):
601 entry = entries[index]
602 if expected_revprops != None:
603 entry.assert_revprops(expected_revprops[index])
604 if expected_paths != None:
605 entry.assert_changed_paths(expected_paths[index])
608 def verify_update(actual_output, wc_dir_name,
609 output_tree, disk_tree, status_tree,
610 singleton_handler_a, a_baton,
611 singleton_handler_b, b_baton,
612 check_props):
613 """Verify update of WC_DIR_NAME.
615 The subcommand output (found in ACTUAL_OUTPUT) will be verified
616 against OUTPUT_TREE (if provided), the working copy itself will be
617 verified against DISK_TREE (if provided), and the working copy's
618 'svn status' output will be verified against STATUS_TREE (if
619 provided). (This is a good way to check that revision numbers were
620 bumped.)
622 Return if successful, raise on failure.
624 For the comparison with DISK_TREE, pass SINGLETON_HANDLER_A and
625 SINGLETON_HANDLER_B to tree.compare_trees -- see that function's doc
626 string for more details. If CHECK_PROPS is set, then disk
627 comparison will examine props."""
629 # Verify actual output against expected output.
630 if output_tree:
631 try:
632 tree.compare_trees ("output", actual_output, output_tree)
633 except tree.SVNTreeUnequal:
634 print "ACTUAL OUTPUT TREE:"
635 tree.dump_tree_script(actual_output, wc_dir_name + os.sep)
636 raise
638 # Create a tree by scanning the working copy, and verify it
639 if disk_tree:
640 actual_disk = tree.build_tree_from_wc (wc_dir_name, check_props)
641 try:
642 tree.compare_trees ("disk", actual_disk, disk_tree,
643 singleton_handler_a, a_baton,
644 singleton_handler_b, b_baton)
645 except tree.SVNTreeUnequal:
646 print "ACTUAL DISK TREE:"
647 tree.dump_tree_script(actual_disk)
648 raise
650 # Verify via 'status' command too, if possible.
651 if status_tree:
652 run_and_verify_status(wc_dir_name, status_tree)
655 def verify_disk(wc_dir_name,
656 disk_tree,
657 singleton_handler_a = None,
658 a_baton = None,
659 singleton_handler_b = None,
660 b_baton = None,
661 check_props = False):
663 """Verify WC_DIR_NAME against DISK_TREE. SINGLETON_HANDLER_A,
664 A_BATON, SINGLETON_HANDLER_B, and B_BATON will be passed to
665 tree.compare_trees, which see for details. If CHECK_PROPS is set,
666 the comparison will examin props. Returns if successful, raises on
667 failure."""
668 if isinstance(disk_tree, wc.State):
669 disk_tree = disk_tree.old_tree()
670 verify_update (None, wc_dir_name, None, disk_tree, None,
671 singleton_handler_a, a_baton,
672 singleton_handler_b, b_baton,
673 check_props)
677 def run_and_verify_update(wc_dir_name,
678 output_tree, disk_tree, status_tree,
679 error_re_string = None,
680 singleton_handler_a = None,
681 a_baton = None,
682 singleton_handler_b = None,
683 b_baton = None,
684 check_props = False,
685 *args):
687 """Update WC_DIR_NAME. *ARGS are any extra optional args to the
688 update subcommand. NOTE: If *ARGS is specified at all, explicit
689 target paths must be passed in *ARGS as well (or a default `.' will
690 be chosen by the 'svn' binary). This allows the caller to update
691 many items in a single working copy dir, but still verify the entire
692 working copy dir.
694 If ERROR_RE_STRING, the update must exit with error, and the error
695 message must match regular expression ERROR_RE_STRING.
697 Else if ERROR_RE_STRING is None, then:
699 The subcommand output will be verified against OUTPUT_TREE, and the
700 working copy itself will be verified against DISK_TREE. If optional
701 STATUS_TREE is given, then 'svn status' output will be compared.
702 (This is a good way to check that revision numbers were bumped.)
704 For the DISK_TREE verification, SINGLETON_HANDLER_A and
705 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
706 function's doc string for more details.
708 If CHECK_PROPS is set, then disk comparison will examine props.
710 Return if successful, raise on failure."""
712 if isinstance(output_tree, wc.State):
713 output_tree = output_tree.old_tree()
714 if isinstance(disk_tree, wc.State):
715 disk_tree = disk_tree.old_tree()
716 if isinstance(status_tree, wc.State):
717 status_tree = status_tree.old_tree()
719 # Update and make a tree of the output.
720 if len(args):
721 exit_code, output, errput = main.run_svn(error_re_string, 'up', *args)
722 else:
723 exit_code, output, errput = main.run_svn(error_re_string,
724 'up', wc_dir_name,
725 *args)
727 if (error_re_string):
728 rm = re.compile(error_re_string)
729 for line in errput:
730 match = rm.search(line)
731 if match:
732 return
733 raise main.SVNUnmatchedError
735 actual = tree.build_tree_from_checkout (output)
736 verify_update (actual, wc_dir_name,
737 output_tree, disk_tree, status_tree,
738 singleton_handler_a, a_baton,
739 singleton_handler_b, b_baton,
740 check_props)
743 def run_and_verify_merge(dir, rev1, rev2, url,
744 output_tree, disk_tree, status_tree, skip_tree,
745 error_re_string = None,
746 singleton_handler_a = None,
747 a_baton = None,
748 singleton_handler_b = None,
749 b_baton = None,
750 check_props = False,
751 dry_run = True,
752 *args):
753 """Run 'svn merge -rREV1:REV2 URL DIR', leaving off the '-r'
754 argument if both REV1 and REV2 are None."""
755 if args:
756 run_and_verify_merge2(dir, rev1, rev2, url, None, output_tree, disk_tree,
757 status_tree, skip_tree, error_re_string,
758 singleton_handler_a, a_baton, singleton_handler_b,
759 b_baton, check_props, dry_run, *args)
760 else:
761 run_and_verify_merge2(dir, rev1, rev2, url, None, output_tree, disk_tree,
762 status_tree, skip_tree, error_re_string,
763 singleton_handler_a, a_baton, singleton_handler_b,
764 b_baton, check_props, dry_run)
767 def run_and_verify_merge2(dir, rev1, rev2, url1, url2,
768 output_tree, disk_tree, status_tree, skip_tree,
769 error_re_string = None,
770 singleton_handler_a = None,
771 a_baton = None,
772 singleton_handler_b = None,
773 b_baton = None,
774 check_props = False,
775 dry_run = True,
776 *args):
777 """Run 'svn merge URL1@REV1 URL2@REV2 DIR' if URL2 is not None
778 (for a three-way merge between URLs and WC).
780 If URL2 is None, run 'svn merge -rREV1:REV2 URL1 DIR'. If both REV1
781 and REV2 are None, leave off the '-r' argument.
783 If ERROR_RE_STRING, the merge must exit with error, and the error
784 message must match regular expression ERROR_RE_STRING.
786 Else if ERROR_RE_STRING is None, then:
788 The subcommand output will be verified against OUTPUT_TREE, and the
789 working copy itself will be verified against DISK_TREE. If optional
790 STATUS_TREE is given, then 'svn status' output will be compared.
791 The 'skipped' merge output will be compared to SKIP_TREE.
793 For the DISK_TREE verification, SINGLETON_HANDLER_A and
794 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
795 function's doc string for more details.
797 If CHECK_PROPS is set, then disk comparison will examine props.
799 If DRY_RUN is set then a --dry-run merge will be carried out first and
800 the output compared with that of the full merge.
802 Return if successful, raise on failure."""
804 if isinstance(output_tree, wc.State):
805 output_tree = output_tree.old_tree()
806 if isinstance(disk_tree, wc.State):
807 disk_tree = disk_tree.old_tree()
808 if isinstance(status_tree, wc.State):
809 status_tree = status_tree.old_tree()
810 if isinstance(skip_tree, wc.State):
811 skip_tree = skip_tree.old_tree()
813 merge_command = [ "merge" ]
814 if url2:
815 merge_command.extend((url1 + "@" + str(rev1), url2 + "@" + str(rev2)))
816 else:
817 if not (rev1 is None and rev2 is None):
818 merge_command.append("-r" + str(rev1) + ":" + str(rev2))
819 merge_command.append(url1)
820 merge_command.append(dir)
821 merge_command = tuple(merge_command)
823 if dry_run:
824 pre_disk = tree.build_tree_from_wc(dir)
825 dry_run_command = merge_command + ('--dry-run',)
826 dry_run_command = dry_run_command + args
827 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
828 *dry_run_command)
829 post_disk = tree.build_tree_from_wc(dir)
830 try:
831 tree.compare_trees("disk", post_disk, pre_disk)
832 except tree.SVNTreeError:
833 print "============================================================="
834 print "Dry-run merge altered working copy"
835 print "============================================================="
836 raise
839 # Update and make a tree of the output.
840 merge_command = merge_command + args
841 exit_code, out, err = main.run_svn(error_re_string, *merge_command)
843 if error_re_string:
844 if not error_re_string.startswith(".*"):
845 error_re_string = ".*(" + error_re_string + ")"
846 expected_err = verify.RegexOutput(error_re_string, match_all=False)
847 verify.verify_outputs(None, None, err, None, expected_err)
848 return
849 elif err:
850 raise verify.SVNUnexpectedStderr(err)
852 if dry_run and out != out_dry:
853 print "============================================================="
854 print "Merge outputs differ"
855 print "The dry-run merge output:"
856 map(sys.stdout.write, out_dry)
857 print "The full merge output:"
858 map(sys.stdout.write, out)
859 print "============================================================="
860 raise main.SVNUnmatchedError
862 def missing_skip(a, b):
863 print "============================================================="
864 print "Merge failed to skip: " + a.path
865 print "============================================================="
866 raise Failure
867 def extra_skip(a, b):
868 print "============================================================="
869 print "Merge unexpectedly skipped: " + a.path
870 print "============================================================="
871 raise Failure
873 myskiptree = tree.build_tree_from_skipped(out)
874 try:
875 tree.compare_trees("skip", myskiptree, skip_tree,
876 extra_skip, None, missing_skip, None)
877 except tree.SVNTreeUnequal:
878 print "ACTUAL SKIP TREE:"
879 tree.dump_tree_script(myskiptree, dir + os.sep)
880 raise
882 actual = tree.build_tree_from_checkout(out, 0)
883 verify_update (actual, dir,
884 output_tree, disk_tree, status_tree,
885 singleton_handler_a, a_baton,
886 singleton_handler_b, b_baton,
887 check_props)
890 def run_and_verify_mergeinfo(error_re_string = None,
891 expected_output = [],
892 *args):
893 """Run 'svn mergeinfo ARGS', and compare the result against
894 EXPECTED_OUTPUT, a list of revisions expected in the output.
895 Raise an exception if an unexpected output is encountered."""
897 mergeinfo_command = ["mergeinfo"]
898 mergeinfo_command.extend(args)
899 exit_code, out, err = main.run_svn(error_re_string, *mergeinfo_command)
901 if error_re_string:
902 if not error_re_string.startswith(".*"):
903 error_re_string = ".*(" + error_re_string + ")"
904 expected_err = verify.RegexOutput(error_re_string, match_all=False)
905 verify.verify_outputs(None, None, err, None, expected_err)
906 return
908 out = filter(None, map(lambda x: int(x.rstrip()[1:]), out))
909 out.sort()
910 expected_output.sort()
912 extra_out = []
913 if out != expected_output:
914 exp_hash = dict.fromkeys(expected_output)
915 for rev in out:
916 if exp_hash.has_key(rev):
917 del(exp_hash[rev])
918 else:
919 extra_out.append(rev)
920 extra_exp = exp_hash.keys()
921 raise Exception("Unexpected 'svn mergeinfo' output:\n"
922 " expected but not found: %s\n"
923 " found but not expected: %s"
924 % (', '.join(map(lambda x: str(x), extra_exp)),
925 ', '.join(map(lambda x: str(x), extra_out))))
928 def run_and_verify_switch(wc_dir_name,
929 wc_target,
930 switch_url,
931 output_tree, disk_tree, status_tree,
932 error_re_string = None,
933 singleton_handler_a = None,
934 a_baton = None,
935 singleton_handler_b = None,
936 b_baton = None,
937 check_props = False,
938 *args):
940 """Switch WC_TARGET (in working copy dir WC_DIR_NAME) to SWITCH_URL.
942 If ERROR_RE_STRING, the switch must exit with error, and the error
943 message must match regular expression ERROR_RE_STRING.
945 Else if ERROR_RE_STRING is None, then:
947 The subcommand output will be verified against OUTPUT_TREE, and the
948 working copy itself will be verified against DISK_TREE. If optional
949 STATUS_TREE is given, then 'svn status' output will be
950 compared. (This is a good way to check that revision numbers were
951 bumped.)
953 For the DISK_TREE verification, SINGLETON_HANDLER_A and
954 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
955 function's doc string for more details.
957 If CHECK_PROPS is set, then disk comparison will examine props.
959 Return if successful, raise on failure."""
961 if isinstance(output_tree, wc.State):
962 output_tree = output_tree.old_tree()
963 if isinstance(disk_tree, wc.State):
964 disk_tree = disk_tree.old_tree()
965 if isinstance(status_tree, wc.State):
966 status_tree = status_tree.old_tree()
968 # Update and make a tree of the output.
969 exit_code, output, errput = main.run_svn(error_re_string, 'switch',
970 switch_url, wc_target, *args)
972 if error_re_string:
973 if not error_re_string.startswith(".*"):
974 error_re_string = ".*(" + error_re_string + ")"
975 expected_err = verify.RegexOutput(error_re_string, match_all=False)
976 verify.verify_outputs(None, None, errput, None, expected_err)
977 return
978 elif errput:
979 raise verify.SVNUnexpectedStderr(err)
981 actual = tree.build_tree_from_checkout (output)
983 verify_update (actual, wc_dir_name,
984 output_tree, disk_tree, status_tree,
985 singleton_handler_a, a_baton,
986 singleton_handler_b, b_baton,
987 check_props)
990 def run_and_verify_commit(wc_dir_name, output_tree, status_tree,
991 error_re_string = None,
992 *args):
993 """Commit and verify results within working copy WC_DIR_NAME,
994 sending ARGS to the commit subcommand.
996 The subcommand output will be verified against OUTPUT_TREE. If
997 optional STATUS_TREE is given, then 'svn status' output will
998 be compared. (This is a good way to check that revision numbers
999 were bumped.)
1001 If ERROR_RE_STRING is None, the commit must not exit with error. If
1002 ERROR_RE_STRING is a string, the commit must exit with error, and
1003 the error message must match regular expression ERROR_RE_STRING.
1005 Return if successful, raise on failure."""
1007 if isinstance(output_tree, wc.State):
1008 output_tree = output_tree.old_tree()
1009 if isinstance(status_tree, wc.State):
1010 status_tree = status_tree.old_tree()
1012 # Commit.
1013 exit_code, output, errput = main.run_svn(error_re_string, 'ci',
1014 '-m', 'log msg',
1015 *args)
1017 if error_re_string:
1018 if not error_re_string.startswith(".*"):
1019 error_re_string = ".*(" + error_re_string + ")"
1020 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1021 verify.verify_outputs(None, None, errput, None, expected_err)
1022 return
1024 # Else not expecting error:
1026 # Remove the final output line, and verify that the commit succeeded.
1027 lastline = ""
1028 if len(output):
1029 lastline = output.pop().strip()
1031 cm = re.compile("(Committed|Imported) revision [0-9]+.")
1032 match = cm.search(lastline)
1033 if not match:
1034 print "ERROR: commit did not succeed."
1035 print "The final line from 'svn ci' was:"
1036 print lastline
1037 raise main.SVNCommitFailure
1039 # The new 'final' line in the output is either a regular line that
1040 # mentions {Adding, Deleting, Sending, ...}, or it could be a line
1041 # that says "Transmitting file data ...". If the latter case, we
1042 # want to remove the line from the output; it should be ignored when
1043 # building a tree.
1044 if len(output):
1045 lastline = output.pop()
1047 tm = re.compile("Transmitting file data.+")
1048 match = tm.search(lastline)
1049 if not match:
1050 # whoops, it was important output, put it back.
1051 output.append(lastline)
1053 # Convert the output into a tree.
1054 actual = tree.build_tree_from_commit (output)
1056 # Verify actual output against expected output.
1057 try:
1058 tree.compare_trees ("output", actual, output_tree)
1059 except tree.SVNTreeError:
1060 verify.display_trees("Output of commit is unexpected",
1061 "OUTPUT TREE", output_tree, actual)
1062 print "ACTUAL OUTPUT TREE:"
1063 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1064 raise
1066 # Verify via 'status' command too, if possible.
1067 if status_tree:
1068 run_and_verify_status(wc_dir_name, status_tree)
1071 # This function always passes '-q' to the status command, which
1072 # suppresses the printing of any unversioned or nonexistent items.
1073 def run_and_verify_status(wc_dir_name, output_tree,
1074 singleton_handler_a = None,
1075 a_baton = None,
1076 singleton_handler_b = None,
1077 b_baton = None):
1078 """Run 'status' on WC_DIR_NAME and compare it with the
1079 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1080 be passed to tree.compare_trees - see that function's doc string for
1081 more details.
1082 Returns on success, raises on failure."""
1084 if isinstance(output_tree, wc.State):
1085 output_tree = output_tree.old_tree()
1087 exit_code, output, errput = main.run_svn(None, 'status', '-v', '-u', '-q',
1088 wc_dir_name)
1090 actual = tree.build_tree_from_status (output)
1092 # Verify actual output against expected output.
1093 try:
1094 tree.compare_trees ("status", actual, output_tree,
1095 singleton_handler_a, a_baton,
1096 singleton_handler_b, b_baton)
1097 except tree.SVNTreeError:
1098 verify.display_trees(None, 'STATUS OUTPUT TREE', output_tree, actual)
1099 print "ACTUAL STATUS TREE:"
1100 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1101 raise
1104 # A variant of previous func, but doesn't pass '-q'. This allows us
1105 # to verify unversioned or nonexistent items in the list.
1106 def run_and_verify_unquiet_status(wc_dir_name, output_tree,
1107 singleton_handler_a = None,
1108 a_baton = None,
1109 singleton_handler_b = None,
1110 b_baton = None):
1111 """Run 'status' on WC_DIR_NAME and compare it with the
1112 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1113 be passed to tree.compare_trees - see that function's doc string for
1114 more details.
1115 Returns on success, raises on failure."""
1117 if isinstance(output_tree, wc.State):
1118 output_tree = output_tree.old_tree()
1120 exit_code, output, errput = main.run_svn(None, 'status', '-v',
1121 '-u', wc_dir_name)
1123 actual = tree.build_tree_from_status (output)
1125 # Verify actual output against expected output.
1126 try:
1127 tree.compare_trees ("output", actual, output_tree,
1128 singleton_handler_a, a_baton,
1129 singleton_handler_b, b_baton)
1130 except tree.SVNTreeError:
1131 print "ACTUAL OUTPUT TREE:"
1132 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1133 raise
1135 def run_and_verify_diff_summarize_xml(error_re_string = [],
1136 expected_prefix = None,
1137 expected_paths = [],
1138 expected_items = [],
1139 expected_props = [],
1140 expected_kinds = [],
1141 *args):
1142 """Run 'diff --summarize --xml' with the arguments *ARGS, which should
1143 contain all arguments beyond for your 'diff --summarize --xml' omitting
1144 said arguments. EXPECTED_PREFIX will store a "common" path prefix
1145 expected to be at the beginning of each summarized path. If
1146 EXPECTED_PREFIX is None, then EXPECTED_PATHS will need to be exactly
1147 as 'svn diff --summarize --xml' will output. If ERROR_RE_STRING, the
1148 command must exit with error, and the error message must match regular
1149 expression ERROR_RE_STRING.
1151 Else if ERROR_RE_STRING is None, the subcommand output will be parsed
1152 into an XML document and will then be verified by comparing the parsed
1153 output to the contents in the EXPECTED_PATHS, EXPECTED_ITEMS,
1154 EXPECTED_PROPS and EXPECTED_KINDS. Returns on success, raises
1155 on failure."""
1157 exit_code, output, errput = run_and_verify_svn(None, None, error_re_string,
1158 'diff', '--summarize',
1159 '--xml', *args)
1162 # Return if errors are present since they were expected
1163 if len(errput) > 0:
1164 return
1166 doc = parseString(''.join(output))
1167 paths = doc.getElementsByTagName("path")
1168 items = expected_items
1169 kinds = expected_kinds
1171 for path in paths:
1172 modified_path = path.childNodes[0].data
1174 if (expected_prefix is not None
1175 and modified_path.find(expected_prefix) == 0):
1176 modified_path = modified_path.replace(expected_prefix, '')[1:].strip()
1178 # Workaround single-object diff
1179 if len(modified_path) == 0:
1180 modified_path = path.childNodes[0].data.split(os.sep)[-1]
1182 # From here on, we use '/' as path separator.
1183 if os.sep != "/":
1184 modified_path = modified_path.replace(os.sep, "/")
1186 if modified_path not in expected_paths:
1187 print "ERROR: %s not expected in the changed paths." % modified_path
1188 raise Failure
1190 index = expected_paths.index(modified_path)
1191 expected_item = items[index]
1192 expected_kind = kinds[index]
1193 expected_prop = expected_props[index]
1194 actual_item = path.getAttribute('item')
1195 actual_kind = path.getAttribute('kind')
1196 actual_prop = path.getAttribute('props')
1198 if expected_item != actual_item:
1199 print "ERROR: expected:", expected_item, "actual:", actual_item
1200 raise Failure
1202 if expected_kind != actual_kind:
1203 print "ERROR: expected:", expected_kind, "actual:", actual_kind
1204 raise Failure
1206 if expected_prop != actual_prop:
1207 print "ERROR: expected:", expected_prop, "actual:", actual_prop
1208 raise Failure
1210 def run_and_verify_diff_summarize(output_tree, error_re_string = None,
1211 singleton_handler_a = None,
1212 a_baton = None,
1213 singleton_handler_b = None,
1214 b_baton = None,
1215 *args):
1216 """Run 'diff --summarize' with the arguments *ARGS.
1217 If ERROR_RE_STRING, the command must exit with error, and the error
1218 message must match regular expression ERROR_RE_STRING.
1220 Else if ERROR_RE_STRING is None, the subcommand output will be
1221 verified against OUTPUT_TREE. SINGLETON_HANDLER_A and
1222 SINGLETON_HANDLER_B will be passed to tree.compare_trees - see that
1223 function's doc string for more details. Returns on success, raises
1224 on failure."""
1226 if isinstance(output_tree, wc.State):
1227 output_tree = output_tree.old_tree()
1229 exit_code, output, errput = main.run_svn(None, 'diff', '--summarize',
1230 *args)
1232 if error_re_string:
1233 if not error_re_string.startswith(".*"):
1234 error_re_string = ".*(" + error_re_string + ")"
1235 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1236 verify.verify_outputs(None, None, errput, None, expected_err)
1237 return
1239 actual = tree.build_tree_from_diff_summarize (output)
1241 # Verify actual output against expected output.
1242 try:
1243 tree.compare_trees ("output", actual, output_tree,
1244 singleton_handler_a, a_baton,
1245 singleton_handler_b, b_baton)
1246 except tree.SVNTreeError:
1247 verify.display_trees(None, 'DIFF OUTPUT TREE', output_tree, actual)
1248 print "ACTUAL DIFF OUTPUT TREE:"
1249 tree.dump_tree_script(actual)
1250 raise
1252 def run_and_validate_lock(path, username):
1253 """`svn lock' the given path and validate the contents of the lock.
1254 Use the given username. This is important because locks are
1255 user specific."""
1257 comment = "Locking path:%s." % path
1259 # lock the path
1260 run_and_verify_svn(None, ".*locked by user", [], 'lock',
1261 '--username', username,
1262 '-m', comment, path)
1264 # Run info and check that we get the lock fields.
1265 exit_code, output, err = run_and_verify_svn(None, None, [],
1266 'info','-R',
1267 path)
1269 ### TODO: Leverage RegexOuput([...], match_all=True) here.
1270 # prepare the regexs to compare against
1271 token_re = re.compile (".*?Lock Token: opaquelocktoken:.*?", re.DOTALL)
1272 author_re = re.compile (".*?Lock Owner: %s\n.*?" % username, re.DOTALL)
1273 created_re = re.compile (".*?Lock Created:.*?", re.DOTALL)
1274 comment_re = re.compile (".*?%s\n.*?" % re.escape(comment), re.DOTALL)
1275 # join all output lines into one
1276 output = "".join(output)
1277 # Fail even if one regex does not match
1278 if ( not (token_re.match(output) and \
1279 author_re.match(output) and \
1280 created_re.match(output) and \
1281 comment_re.match(output))):
1282 raise Failure
1284 ######################################################################
1285 # Other general utilities
1288 # This allows a test to *quickly* bootstrap itself.
1289 def make_repo_and_wc(sbox, create_wc = True, read_only = False):
1290 """Create a fresh repository and checkout a wc from it.
1292 If read_only is False, a dedicated repository will be created, named
1293 TEST_NAME. The repository will live in the global dir 'general_repo_dir'.
1294 If read_only is True the pristine repository will be used.
1296 If create_wc is True, a dedicated working copy will be checked out from
1297 the repository, named TEST_NAME. The wc directory will live in the global
1298 dir 'general_wc_dir'.
1300 Both variables 'general_repo_dir' and 'general_wc_dir' are defined at the
1301 top of this test suite.) Returns on success, raises on failure."""
1303 # Create (or copy afresh) a new repos with a greek tree in it.
1304 if not read_only:
1305 guarantee_greek_repository(sbox.repo_dir)
1307 if create_wc:
1308 # Generate the expected output tree.
1309 expected_output = main.greek_state.copy()
1310 expected_output.wc_dir = sbox.wc_dir
1311 expected_output.tweak(status='A ', contents=None)
1313 # Generate an expected wc tree.
1314 expected_wc = main.greek_state
1316 # Do a checkout, and verify the resulting output and disk contents.
1317 run_and_verify_checkout(sbox.repo_url,
1318 sbox.wc_dir,
1319 expected_output,
1320 expected_wc)
1321 else:
1322 # just make sure the parent folder of our working copy is created
1323 try:
1324 os.mkdir(main.general_wc_dir)
1325 except OSError, err:
1326 if err.errno != errno.EEXIST:
1327 raise
1329 # Duplicate a working copy or other dir.
1330 def duplicate_dir(wc_name, wc_copy_name):
1331 """Copy the working copy WC_NAME to WC_COPY_NAME. Overwrite any
1332 existing tree at that location."""
1334 main.safe_rmtree(wc_copy_name)
1335 shutil.copytree(wc_name, wc_copy_name)
1339 def get_virginal_state(wc_dir, rev):
1340 "Return a virginal greek tree state for a WC and repos at revision REV."
1342 rev = str(rev) ### maybe switch rev to an integer?
1344 # copy the greek tree, shift it to the new wc_dir, insert a root elem,
1345 # then tweak all values
1346 state = main.greek_state.copy()
1347 state.wc_dir = wc_dir
1348 state.desc[''] = wc.StateItem()
1349 state.tweak(contents=None, status=' ', wc_rev=rev)
1351 return state
1353 def remove_admin_tmp_dir(wc_dir):
1354 "Remove the tmp directory within the administrative directory."
1356 tmp_path = os.path.join(wc_dir, main.get_admin_name(), 'tmp')
1357 ### Any reason not to use main.safe_rmtree()?
1358 os.rmdir(os.path.join(tmp_path, 'prop-base'))
1359 os.rmdir(os.path.join(tmp_path, 'props'))
1360 os.rmdir(os.path.join(tmp_path, 'text-base'))
1361 os.rmdir(tmp_path)
1363 # Cheap administrative directory locking
1364 def lock_admin_dir(wc_dir):
1365 "Lock a SVN administrative directory"
1367 path = os.path.join(wc_dir, main.get_admin_name(), 'lock')
1368 main.file_append(path, "stop looking!")
1370 def create_failing_hook(repo_dir, hook_name, text):
1371 """Create a HOOK_NAME hook in REPO_DIR that prints TEXT to stderr and exits
1372 with an error."""
1374 hook_path = os.path.join(repo_dir, 'hooks', hook_name)
1375 main.create_python_hook_script(hook_path, 'import sys;\n'
1376 'sys.stderr.write("""%%s hook failed: %%s""" %% (%s, %s));\n'
1377 'sys.exit(1);\n' % (repr(hook_name), repr(text)))
1379 def enable_revprop_changes(repo_dir):
1380 """Enable revprop changes in a repository REPOS_DIR by creating a
1381 pre-revprop-change hook script and (if appropriate) making it executable."""
1383 hook_path = main.get_pre_revprop_change_hook_path (repo_dir)
1384 main.create_python_hook_script (hook_path, 'import sys; sys.exit(0)')
1386 def disable_revprop_changes(repo_dir):
1387 """Disable revprop changes in a repository REPO_DIR by creating a
1388 pre-revprop-change hook script like enable_revprop_changes, except that
1389 the hook prints "pre-revprop-change" followed by sys.argv"""
1390 hook_path = main.get_pre_revprop_change_hook_path (repo_dir)
1391 main.create_python_hook_script (hook_path,
1392 'import sys\n'
1393 'sys.stderr.write("pre-revprop-change %s %s %s %s %s" % (sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]))\n'
1394 'sys.exit(1)\n')
1396 def create_failing_post_commit_hook(repo_dir):
1397 """Disable commits in a repository REPOS_DIR by creating a post-commit hook
1398 script which always reports errors."""
1400 hook_path = main.get_post_commit_hook_path (repo_dir)
1401 main.create_python_hook_script (hook_path, 'import sys; '
1402 'sys.stderr.write("Post-commit hook failed"); '
1403 'sys.exit(1)')
1405 # set_prop can be used for binary properties are values like '*' which are not
1406 # handled correctly when specified on the command line.
1407 def set_prop(expected_err, name, value, path, valp):
1408 """Set a property with value from a file"""
1409 valf = open(valp, 'wb')
1410 valf.seek(0)
1411 valf.truncate(0)
1412 valf.write(value)
1413 valf.flush()
1414 main.run_svn(expected_err, 'propset', '-F', valp, name, path)
1416 def check_prop(name, path, exp_out):
1417 """Verify that property NAME on PATH has a value of EXP_OUT"""
1418 # Not using run_svn because binary_mode must be set
1419 exit_code, out, err = main.run_command(main.svn_binary, None, 1, 'pg',
1420 '--strict', name, path,
1421 '--config-dir',
1422 main.default_config_dir)
1423 if out != exp_out:
1424 print "svn pg --strict", name, "output does not match expected."
1425 print "Expected standard output: ", exp_out, "\n"
1426 print "Actual standard output: ", out, "\n"
1427 raise Failure
1429 def fill_file_with_lines(wc_path, line_nbr, line_descrip=None,
1430 append=True):
1431 """Change the file at WC_PATH (adding some lines), and return its
1432 new contents. LINE_NBR indicates the line number at which the new
1433 contents should assume that it's being appended. LINE_DESCRIP is
1434 something like 'This is line' (the default) or 'Conflicting line'."""
1436 if line_descrip is None:
1437 line_descrip = "This is line"
1439 # Generate the new contents for the file.
1440 contents = ""
1441 for n in range(line_nbr, line_nbr + 3):
1442 contents = contents + line_descrip + " " + `n` + " in '" + \
1443 os.path.basename(wc_path) + "'.\n"
1445 # Write the new contents to the file.
1446 if append:
1447 main.file_append(wc_path, contents)
1448 else:
1449 main.file_write(wc_path, contents)
1451 return contents
1453 def inject_conflict_into_wc(sbox, state_path, file_path,
1454 expected_disk, expected_status, merged_rev):
1455 """Create a conflict at FILE_PATH by replacing its contents,
1456 committing the change, backdating it to its previous revision,
1457 changing its contents again, then updating it to merge in the
1458 previous change."""
1460 wc_dir = sbox.wc_dir
1462 # Make a change to the file.
1463 contents = fill_file_with_lines(file_path, 1, "This is line", append=False)
1465 # Commit the changed file, first taking note of the current revision.
1466 prev_rev = expected_status.desc[state_path].wc_rev
1467 expected_output = wc.State(wc_dir, {
1468 state_path : wc.StateItem(verb='Sending'),
1470 if expected_status:
1471 expected_status.tweak(state_path, wc_rev=merged_rev)
1472 run_and_verify_commit(wc_dir, expected_output, expected_status,
1473 None, file_path)
1475 # Backdate the file.
1476 exit_code, output, errput = main.run_svn(None, "up", "-r", str(prev_rev),
1477 file_path)
1478 if expected_status:
1479 expected_status.tweak(state_path, wc_rev=prev_rev)
1481 # Make a conflicting change to the file, and backdate the file.
1482 conflicting_contents = fill_file_with_lines(file_path, 1, "Conflicting line",
1483 append=False)
1485 # Merge the previous change into the file to produce a conflict.
1486 if expected_disk:
1487 expected_disk.tweak(state_path, contents="")
1488 expected_output = wc.State(wc_dir, {
1489 state_path : wc.StateItem(status='C '),
1491 inject_conflict_into_expected_state(state_path,
1492 expected_disk, expected_status,
1493 conflicting_contents, contents,
1494 merged_rev)
1495 exit_code, output, errput = main.run_svn(None, "up", "-r", str(merged_rev),
1496 sbox.repo_url + "/" + state_path,
1497 file_path)
1498 if expected_status:
1499 expected_status.tweak(state_path, wc_rev=merged_rev)
1501 def inject_conflict_into_expected_state(state_path,
1502 expected_disk, expected_status,
1503 wc_text, merged_text, merged_rev):
1504 """Update the EXPECTED_DISK and EXPECTED_STATUS trees for the
1505 conflict at STATE_PATH (ignored if None). WC_TEXT, MERGED_TEXT, and
1506 MERGED_REV are used to determine the contents of the conflict (the
1507 text parameters should be newline-terminated)."""
1508 if expected_disk:
1509 conflict_marker = make_conflict_marker_text(wc_text, merged_text,
1510 merged_rev)
1511 existing_text = expected_disk.desc[state_path].contents or ""
1512 expected_disk.tweak(state_path, contents=existing_text + conflict_marker)
1514 if expected_status:
1515 expected_status.tweak(state_path, status='C ')
1517 def make_conflict_marker_text(wc_text, merged_text, merged_rev):
1518 """Return the conflict marker text described by WC_TEXT (the current
1519 text in the working copy, MERGED_TEXT (the conflicting text merged
1520 in), and MERGED_REV (the revision from whence the conflicting text
1521 came)."""
1522 return "<<<<<<< .working\n" + wc_text + "=======\n" + \
1523 merged_text + ">>>>>>> .merge-right.r" + str(merged_rev) + "\n"