Include property changes in the deep trees conflict tests. This doesn't
[svnrdump.git] / svntest / actions.py
blobb99c2693e4cf7c5cf9997aaef5b0ee33bdf6762b
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-2008 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 range(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 If OUTPUT_TREE is not None, the subcommand output will be verified
700 against OUTPUT_TREE. If DISK_TREE is not None, the working copy
701 itself will be verified against DISK_TREE. If STATUS_TREE is not
702 None, the 'svn status' output will be verified against STATUS_TREE.
703 (This is a good way to check that revision numbers were bumped.)
705 For the DISK_TREE verification, SINGLETON_HANDLER_A and
706 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
707 function's doc string for more details.
709 If CHECK_PROPS is set, then disk comparison will examine props.
711 Return if successful, raise on failure."""
713 if isinstance(output_tree, wc.State):
714 output_tree = output_tree.old_tree()
715 if isinstance(disk_tree, wc.State):
716 disk_tree = disk_tree.old_tree()
717 if isinstance(status_tree, wc.State):
718 status_tree = status_tree.old_tree()
720 # Update and make a tree of the output.
721 if len(args):
722 exit_code, output, errput = main.run_svn(error_re_string, 'up', *args)
723 else:
724 exit_code, output, errput = main.run_svn(error_re_string,
725 'up', wc_dir_name,
726 *args)
728 if (error_re_string):
729 rm = re.compile(error_re_string)
730 for line in errput:
731 match = rm.search(line)
732 if match:
733 return
734 raise main.SVNUnmatchedError
736 actual = tree.build_tree_from_checkout (output)
737 verify_update (actual, wc_dir_name,
738 output_tree, disk_tree, status_tree,
739 singleton_handler_a, a_baton,
740 singleton_handler_b, b_baton,
741 check_props)
744 def run_and_parse_info(*args):
745 """Run 'svn info' and parse its output into a list of dicts,
746 one dict per target."""
748 # the returned array
749 all_infos = []
751 # per-target variables
752 iter_info = {}
753 prev_key = None
755 exit_code, output, errput = main.run_svn(None, 'info', *args)
757 for line in output:
758 line = line[:-1] # trim '\n'
759 if len(line) == 0:
760 # separator line between items
761 all_infos.append(iter_info)
762 iter_info = {}
763 prev_key = None
764 elif line[0].isspace():
765 # continuation line (for tree conflicts)
766 iter_info[prev_key] += line[1:]
767 else:
768 # normal line
769 key, value = line.split(':', 1)
770 if len(value) > 1:
771 iter_info[key] = value[1:]
772 else:
773 # it's a "Tree conflict:\n" line; value is in continuation lines
774 iter_info[key] = ''
775 prev_key = key
777 return all_infos
779 def run_and_verify_info(expected_infos, *args):
780 """Run 'svn info' with the arguments in *ARGS and verify the results
781 against expected_infos. The latter should be a list of dicts (in the
782 same order as the targets).
784 In the dicts, each key is the before-the-colon part of the 'svn info' output,
785 and each value is either None (meaning that the key should *not* appear in
786 the 'svn info' output) or a regex matching the output value. Output lines
787 not matching a key in the dict are ignored.
789 Return if successful, raise on failure."""
791 actual_infos = run_and_parse_info(*args)
793 try:
794 for actual, expected in zip(actual_infos, expected_infos):
795 # compare dicts
796 for key, value in expected.items():
797 if value is None and key in actual:
798 raise main.SVNLineUnequal("Found unexpected key '%s' with value '%s'"
799 % (key, actual[key]))
800 if value is not None and key not in actual:
801 raise main.SVNLineUnequal("Expected key '%s' (with value '%s') "
802 "not found" % (key, value))
803 if value is not None and not re.search(value, actual[key]):
804 raise verify.SVNUnexpectedStdout("Values of key '%s' don't match:\n"
805 " Expected: '%s'\n"
806 " Found: '%s'\n"
807 % (key, value, actual[key]))
809 except:
810 sys.stderr.write("Bad 'svn info' output:\n"
811 " Received: %s\n"
812 " Expected: %s\n"
813 % (actual_infos, expected_infos))
814 raise
816 def run_and_verify_merge(dir, rev1, rev2, url,
817 output_tree, disk_tree, status_tree, skip_tree,
818 error_re_string = None,
819 singleton_handler_a = None,
820 a_baton = None,
821 singleton_handler_b = None,
822 b_baton = None,
823 check_props = False,
824 dry_run = True,
825 *args):
826 """Run 'svn merge -rREV1:REV2 URL DIR', leaving off the '-r'
827 argument if both REV1 and REV2 are None."""
828 if args:
829 run_and_verify_merge2(dir, rev1, rev2, url, None, output_tree, disk_tree,
830 status_tree, skip_tree, error_re_string,
831 singleton_handler_a, a_baton, singleton_handler_b,
832 b_baton, check_props, dry_run, *args)
833 else:
834 run_and_verify_merge2(dir, rev1, rev2, url, None, output_tree, disk_tree,
835 status_tree, skip_tree, error_re_string,
836 singleton_handler_a, a_baton, singleton_handler_b,
837 b_baton, check_props, dry_run)
840 def run_and_verify_merge2(dir, rev1, rev2, url1, url2,
841 output_tree, disk_tree, status_tree, skip_tree,
842 error_re_string = None,
843 singleton_handler_a = None,
844 a_baton = None,
845 singleton_handler_b = None,
846 b_baton = None,
847 check_props = False,
848 dry_run = True,
849 *args):
850 """Run 'svn merge URL1@REV1 URL2@REV2 DIR' if URL2 is not None
851 (for a three-way merge between URLs and WC).
853 If URL2 is None, run 'svn merge -rREV1:REV2 URL1 DIR'. If both REV1
854 and REV2 are None, leave off the '-r' argument.
856 If ERROR_RE_STRING, the merge must exit with error, and the error
857 message must match regular expression ERROR_RE_STRING.
859 Else if ERROR_RE_STRING is None, then:
861 The subcommand output will be verified against OUTPUT_TREE, and the
862 working copy itself will be verified against DISK_TREE. If optional
863 STATUS_TREE is given, then 'svn status' output will be compared.
864 The 'skipped' merge output will be compared to SKIP_TREE.
866 For the DISK_TREE verification, SINGLETON_HANDLER_A and
867 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
868 function's doc string for more details.
870 If CHECK_PROPS is set, then disk comparison will examine props.
872 If DRY_RUN is set then a --dry-run merge will be carried out first and
873 the output compared with that of the full merge.
875 Return if successful, raise on failure."""
877 if isinstance(output_tree, wc.State):
878 output_tree = output_tree.old_tree()
879 if isinstance(disk_tree, wc.State):
880 disk_tree = disk_tree.old_tree()
881 if isinstance(status_tree, wc.State):
882 status_tree = status_tree.old_tree()
883 if isinstance(skip_tree, wc.State):
884 skip_tree = skip_tree.old_tree()
886 merge_command = [ "merge" ]
887 if url2:
888 merge_command.extend((url1 + "@" + str(rev1), url2 + "@" + str(rev2)))
889 else:
890 if not (rev1 is None and rev2 is None):
891 merge_command.append("-r" + str(rev1) + ":" + str(rev2))
892 merge_command.append(url1)
893 merge_command.append(dir)
894 merge_command = tuple(merge_command)
896 if dry_run:
897 pre_disk = tree.build_tree_from_wc(dir)
898 dry_run_command = merge_command + ('--dry-run',)
899 dry_run_command = dry_run_command + args
900 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
901 *dry_run_command)
902 post_disk = tree.build_tree_from_wc(dir)
903 try:
904 tree.compare_trees("disk", post_disk, pre_disk)
905 except tree.SVNTreeError:
906 print("=============================================================")
907 print("Dry-run merge altered working copy")
908 print("=============================================================")
909 raise
912 # Update and make a tree of the output.
913 merge_command = merge_command + args
914 exit_code, out, err = main.run_svn(error_re_string, *merge_command)
916 if error_re_string:
917 if not error_re_string.startswith(".*"):
918 error_re_string = ".*(" + error_re_string + ")"
919 expected_err = verify.RegexOutput(error_re_string, match_all=False)
920 verify.verify_outputs(None, None, err, None, expected_err)
921 return
922 elif err:
923 raise verify.SVNUnexpectedStderr(err)
925 if dry_run and out != out_dry:
926 print("=============================================================")
927 print("Merge outputs differ")
928 print("The dry-run merge output:")
929 for x in out_dry:
930 sys.stdout.write(x)
931 print("The full merge output:")
932 for x in out:
933 sys.stdout.write(x)
934 print("=============================================================")
935 raise main.SVNUnmatchedError
937 def missing_skip(a, b):
938 print("=============================================================")
939 print("Merge failed to skip: " + a.path)
940 print("=============================================================")
941 raise Failure
942 def extra_skip(a, b):
943 print("=============================================================")
944 print("Merge unexpectedly skipped: " + a.path)
945 print("=============================================================")
946 raise Failure
948 myskiptree = tree.build_tree_from_skipped(out)
949 try:
950 tree.compare_trees("skip", myskiptree, skip_tree,
951 extra_skip, None, missing_skip, None)
952 except tree.SVNTreeUnequal:
953 print("ACTUAL SKIP TREE:")
954 tree.dump_tree_script(myskiptree, dir + os.sep)
955 raise
957 actual = tree.build_tree_from_checkout(out, 0)
958 verify_update (actual, dir,
959 output_tree, disk_tree, status_tree,
960 singleton_handler_a, a_baton,
961 singleton_handler_b, b_baton,
962 check_props)
965 def run_and_verify_mergeinfo(error_re_string = None,
966 expected_output = [],
967 *args):
968 """Run 'svn mergeinfo ARGS', and compare the result against
969 EXPECTED_OUTPUT, a list of revisions expected in the output.
970 Raise an exception if an unexpected output is encountered."""
972 mergeinfo_command = ["mergeinfo"]
973 mergeinfo_command.extend(args)
974 exit_code, out, err = main.run_svn(error_re_string, *mergeinfo_command)
976 if error_re_string:
977 if not error_re_string.startswith(".*"):
978 error_re_string = ".*(" + error_re_string + ")"
979 expected_err = verify.RegexOutput(error_re_string, match_all=False)
980 verify.verify_outputs(None, None, err, None, expected_err)
981 return
983 out = [_f for _f in [int(x.rstrip()[1:]) for x in out] if _f]
984 out.sort()
985 expected_output.sort()
987 extra_out = []
988 if out != expected_output:
989 exp_hash = dict.fromkeys(expected_output)
990 for rev in out:
991 if rev in exp_hash:
992 del(exp_hash[rev])
993 else:
994 extra_out.append(rev)
995 extra_exp = list(exp_hash.keys())
996 raise Exception("Unexpected 'svn mergeinfo' output:\n"
997 " expected but not found: %s\n"
998 " found but not expected: %s"
999 % (', '.join([str(x) for x in extra_exp]),
1000 ', '.join([str(x) for x in extra_out])))
1003 def run_and_verify_switch(wc_dir_name,
1004 wc_target,
1005 switch_url,
1006 output_tree, disk_tree, status_tree,
1007 error_re_string = None,
1008 singleton_handler_a = None,
1009 a_baton = None,
1010 singleton_handler_b = None,
1011 b_baton = None,
1012 check_props = False,
1013 *args):
1015 """Switch WC_TARGET (in working copy dir WC_DIR_NAME) to SWITCH_URL.
1017 If ERROR_RE_STRING, the switch must exit with error, and the error
1018 message must match regular expression ERROR_RE_STRING.
1020 Else if ERROR_RE_STRING is None, then:
1022 The subcommand output will be verified against OUTPUT_TREE, and the
1023 working copy itself will be verified against DISK_TREE. If optional
1024 STATUS_TREE is given, then 'svn status' output will be
1025 compared. (This is a good way to check that revision numbers were
1026 bumped.)
1028 For the DISK_TREE verification, SINGLETON_HANDLER_A and
1029 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
1030 function's doc string for more details.
1032 If CHECK_PROPS is set, then disk comparison will examine props.
1034 Return if successful, raise on failure."""
1036 if isinstance(output_tree, wc.State):
1037 output_tree = output_tree.old_tree()
1038 if isinstance(disk_tree, wc.State):
1039 disk_tree = disk_tree.old_tree()
1040 if isinstance(status_tree, wc.State):
1041 status_tree = status_tree.old_tree()
1043 # Update and make a tree of the output.
1044 exit_code, output, errput = main.run_svn(error_re_string, 'switch',
1045 switch_url, wc_target, *args)
1047 if error_re_string:
1048 if not error_re_string.startswith(".*"):
1049 error_re_string = ".*(" + error_re_string + ")"
1050 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1051 verify.verify_outputs(None, None, errput, None, expected_err)
1052 return
1053 elif errput:
1054 raise verify.SVNUnexpectedStderr(err)
1056 actual = tree.build_tree_from_checkout (output)
1058 verify_update (actual, wc_dir_name,
1059 output_tree, disk_tree, status_tree,
1060 singleton_handler_a, a_baton,
1061 singleton_handler_b, b_baton,
1062 check_props)
1065 def run_and_verify_commit(wc_dir_name, output_tree, status_tree,
1066 error_re_string = None,
1067 *args):
1068 """Commit and verify results within working copy WC_DIR_NAME,
1069 sending ARGS to the commit subcommand.
1071 The subcommand output will be verified against OUTPUT_TREE. If
1072 optional STATUS_TREE is given, then 'svn status' output will
1073 be compared. (This is a good way to check that revision numbers
1074 were bumped.)
1076 If ERROR_RE_STRING is None, the commit must not exit with error. If
1077 ERROR_RE_STRING is a string, the commit must exit with error, and
1078 the error message must match regular expression ERROR_RE_STRING.
1080 Return if successful, raise on failure."""
1082 if isinstance(output_tree, wc.State):
1083 output_tree = output_tree.old_tree()
1084 if isinstance(status_tree, wc.State):
1085 status_tree = status_tree.old_tree()
1087 # Commit.
1088 exit_code, output, errput = main.run_svn(error_re_string, 'ci',
1089 '-m', 'log msg',
1090 *args)
1092 if error_re_string:
1093 if not error_re_string.startswith(".*"):
1094 error_re_string = ".*(" + error_re_string + ")"
1095 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1096 verify.verify_outputs(None, None, errput, None, expected_err)
1097 return
1099 # Else not expecting error:
1101 # Remove the final output line, and verify that the commit succeeded.
1102 lastline = ""
1103 if len(output):
1104 lastline = output.pop().strip()
1106 cm = re.compile("(Committed|Imported) revision [0-9]+.")
1107 match = cm.search(lastline)
1108 if not match:
1109 print("ERROR: commit did not succeed.")
1110 print("The final line from 'svn ci' was:")
1111 print(lastline)
1112 raise main.SVNCommitFailure
1114 # The new 'final' line in the output is either a regular line that
1115 # mentions {Adding, Deleting, Sending, ...}, or it could be a line
1116 # that says "Transmitting file data ...". If the latter case, we
1117 # want to remove the line from the output; it should be ignored when
1118 # building a tree.
1119 if len(output):
1120 lastline = output.pop()
1122 tm = re.compile("Transmitting file data.+")
1123 match = tm.search(lastline)
1124 if not match:
1125 # whoops, it was important output, put it back.
1126 output.append(lastline)
1128 # Convert the output into a tree.
1129 actual = tree.build_tree_from_commit (output)
1131 # Verify actual output against expected output.
1132 try:
1133 tree.compare_trees ("output", actual, output_tree)
1134 except tree.SVNTreeError:
1135 verify.display_trees("Output of commit is unexpected",
1136 "OUTPUT TREE", output_tree, actual)
1137 print("ACTUAL OUTPUT TREE:")
1138 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1139 raise
1141 # Verify via 'status' command too, if possible.
1142 if status_tree:
1143 run_and_verify_status(wc_dir_name, status_tree)
1146 # This function always passes '-q' to the status command, which
1147 # suppresses the printing of any unversioned or nonexistent items.
1148 def run_and_verify_status(wc_dir_name, output_tree,
1149 singleton_handler_a = None,
1150 a_baton = None,
1151 singleton_handler_b = None,
1152 b_baton = None):
1153 """Run 'status' on WC_DIR_NAME and compare it with the
1154 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1155 be passed to tree.compare_trees - see that function's doc string for
1156 more details.
1157 Returns on success, raises on failure."""
1159 if isinstance(output_tree, wc.State):
1160 output_tree = output_tree.old_tree()
1162 exit_code, output, errput = main.run_svn(None, 'status', '-v', '-u', '-q',
1163 wc_dir_name)
1165 actual = tree.build_tree_from_status (output)
1167 # Verify actual output against expected output.
1168 try:
1169 tree.compare_trees ("status", actual, output_tree,
1170 singleton_handler_a, a_baton,
1171 singleton_handler_b, b_baton)
1172 except tree.SVNTreeError:
1173 verify.display_trees(None, 'STATUS OUTPUT TREE', output_tree, actual)
1174 print("ACTUAL STATUS TREE:")
1175 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1176 raise
1179 # A variant of previous func, but doesn't pass '-q'. This allows us
1180 # to verify unversioned or nonexistent items in the list.
1181 def run_and_verify_unquiet_status(wc_dir_name, status_tree,
1182 singleton_handler_a = None,
1183 a_baton = None,
1184 singleton_handler_b = None,
1185 b_baton = None):
1186 """Run 'status' on WC_DIR_NAME and compare it with the
1187 expected STATUS_TREE SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1188 be passed to tree.compare_trees - see that function's doc string for
1189 more details.
1190 Returns on success, raises on failure."""
1192 if isinstance(status_tree, wc.State):
1193 status_tree = status_tree.old_tree()
1195 exit_code, output, errput = main.run_svn(None, 'status', '-v',
1196 '-u', wc_dir_name)
1198 actual = tree.build_tree_from_status (output)
1200 # Verify actual output against expected output.
1201 try:
1202 tree.compare_trees ("UNQUIET STATUS", actual, status_tree,
1203 singleton_handler_a, a_baton,
1204 singleton_handler_b, b_baton)
1205 except tree.SVNTreeError:
1206 print("ACTUAL UNQUIET STATUS TREE:")
1207 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1208 raise
1210 def run_and_verify_diff_summarize_xml(error_re_string = [],
1211 expected_prefix = None,
1212 expected_paths = [],
1213 expected_items = [],
1214 expected_props = [],
1215 expected_kinds = [],
1216 *args):
1217 """Run 'diff --summarize --xml' with the arguments *ARGS, which should
1218 contain all arguments beyond for your 'diff --summarize --xml' omitting
1219 said arguments. EXPECTED_PREFIX will store a "common" path prefix
1220 expected to be at the beginning of each summarized path. If
1221 EXPECTED_PREFIX is None, then EXPECTED_PATHS will need to be exactly
1222 as 'svn diff --summarize --xml' will output. If ERROR_RE_STRING, the
1223 command must exit with error, and the error message must match regular
1224 expression ERROR_RE_STRING.
1226 Else if ERROR_RE_STRING is None, the subcommand output will be parsed
1227 into an XML document and will then be verified by comparing the parsed
1228 output to the contents in the EXPECTED_PATHS, EXPECTED_ITEMS,
1229 EXPECTED_PROPS and EXPECTED_KINDS. Returns on success, raises
1230 on failure."""
1232 exit_code, output, errput = run_and_verify_svn(None, None, error_re_string,
1233 'diff', '--summarize',
1234 '--xml', *args)
1237 # Return if errors are present since they were expected
1238 if len(errput) > 0:
1239 return
1241 doc = parseString(''.join(output))
1242 paths = doc.getElementsByTagName("path")
1243 items = expected_items
1244 kinds = expected_kinds
1246 for path in paths:
1247 modified_path = path.childNodes[0].data
1249 if (expected_prefix is not None
1250 and modified_path.find(expected_prefix) == 0):
1251 modified_path = modified_path.replace(expected_prefix, '')[1:].strip()
1253 # Workaround single-object diff
1254 if len(modified_path) == 0:
1255 modified_path = path.childNodes[0].data.split(os.sep)[-1]
1257 # From here on, we use '/' as path separator.
1258 if os.sep != "/":
1259 modified_path = modified_path.replace(os.sep, "/")
1261 if modified_path not in expected_paths:
1262 print("ERROR: %s not expected in the changed paths." % modified_path)
1263 raise Failure
1265 index = expected_paths.index(modified_path)
1266 expected_item = items[index]
1267 expected_kind = kinds[index]
1268 expected_prop = expected_props[index]
1269 actual_item = path.getAttribute('item')
1270 actual_kind = path.getAttribute('kind')
1271 actual_prop = path.getAttribute('props')
1273 if expected_item != actual_item:
1274 print("ERROR: expected: %s actual: %s" % (expected_item, actual_item))
1275 raise Failure
1277 if expected_kind != actual_kind:
1278 print("ERROR: expected: %s actual: %s" % (expected_kind, actual_kind))
1279 raise Failure
1281 if expected_prop != actual_prop:
1282 print("ERROR: expected: %s actual: %s" % (expected_prop, actual_prop))
1283 raise Failure
1285 def run_and_verify_diff_summarize(output_tree, error_re_string = None,
1286 singleton_handler_a = None,
1287 a_baton = None,
1288 singleton_handler_b = None,
1289 b_baton = None,
1290 *args):
1291 """Run 'diff --summarize' with the arguments *ARGS.
1292 If ERROR_RE_STRING, the command must exit with error, and the error
1293 message must match regular expression ERROR_RE_STRING.
1295 Else if ERROR_RE_STRING is None, the subcommand output will be
1296 verified against OUTPUT_TREE. SINGLETON_HANDLER_A and
1297 SINGLETON_HANDLER_B will be passed to tree.compare_trees - see that
1298 function's doc string for more details. Returns on success, raises
1299 on failure."""
1301 if isinstance(output_tree, wc.State):
1302 output_tree = output_tree.old_tree()
1304 exit_code, output, errput = main.run_svn(None, 'diff', '--summarize',
1305 *args)
1307 if error_re_string:
1308 if not error_re_string.startswith(".*"):
1309 error_re_string = ".*(" + error_re_string + ")"
1310 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1311 verify.verify_outputs(None, None, errput, None, expected_err)
1312 return
1314 actual = tree.build_tree_from_diff_summarize (output)
1316 # Verify actual output against expected output.
1317 try:
1318 tree.compare_trees ("output", actual, output_tree,
1319 singleton_handler_a, a_baton,
1320 singleton_handler_b, b_baton)
1321 except tree.SVNTreeError:
1322 verify.display_trees(None, 'DIFF OUTPUT TREE', output_tree, actual)
1323 print("ACTUAL DIFF OUTPUT TREE:")
1324 tree.dump_tree_script(actual)
1325 raise
1327 def run_and_validate_lock(path, username):
1328 """`svn lock' the given path and validate the contents of the lock.
1329 Use the given username. This is important because locks are
1330 user specific."""
1332 comment = "Locking path:%s." % path
1334 # lock the path
1335 run_and_verify_svn(None, ".*locked by user", [], 'lock',
1336 '--username', username,
1337 '-m', comment, path)
1339 # Run info and check that we get the lock fields.
1340 exit_code, output, err = run_and_verify_svn(None, None, [],
1341 'info','-R',
1342 path)
1344 ### TODO: Leverage RegexOuput([...], match_all=True) here.
1345 # prepare the regexs to compare against
1346 token_re = re.compile (".*?Lock Token: opaquelocktoken:.*?", re.DOTALL)
1347 author_re = re.compile (".*?Lock Owner: %s\n.*?" % username, re.DOTALL)
1348 created_re = re.compile (".*?Lock Created:.*?", re.DOTALL)
1349 comment_re = re.compile (".*?%s\n.*?" % re.escape(comment), re.DOTALL)
1350 # join all output lines into one
1351 output = "".join(output)
1352 # Fail even if one regex does not match
1353 if ( not (token_re.match(output) and \
1354 author_re.match(output) and \
1355 created_re.match(output) and \
1356 comment_re.match(output))):
1357 raise Failure
1359 ######################################################################
1360 # Other general utilities
1363 # This allows a test to *quickly* bootstrap itself.
1364 def make_repo_and_wc(sbox, create_wc = True, read_only = False):
1365 """Create a fresh repository and checkout a wc from it.
1367 If read_only is False, a dedicated repository will be created, named
1368 TEST_NAME. The repository will live in the global dir 'general_repo_dir'.
1369 If read_only is True the pristine repository will be used.
1371 If create_wc is True, a dedicated working copy will be checked out from
1372 the repository, named TEST_NAME. The wc directory will live in the global
1373 dir 'general_wc_dir'.
1375 Both variables 'general_repo_dir' and 'general_wc_dir' are defined at the
1376 top of this test suite.) Returns on success, raises on failure."""
1378 # Create (or copy afresh) a new repos with a greek tree in it.
1379 if not read_only:
1380 guarantee_greek_repository(sbox.repo_dir)
1382 if create_wc:
1383 # Generate the expected output tree.
1384 expected_output = main.greek_state.copy()
1385 expected_output.wc_dir = sbox.wc_dir
1386 expected_output.tweak(status='A ', contents=None)
1388 # Generate an expected wc tree.
1389 expected_wc = main.greek_state
1391 # Do a checkout, and verify the resulting output and disk contents.
1392 run_and_verify_checkout(sbox.repo_url,
1393 sbox.wc_dir,
1394 expected_output,
1395 expected_wc)
1396 else:
1397 # just make sure the parent folder of our working copy is created
1398 try:
1399 os.mkdir(main.general_wc_dir)
1400 except OSError, err:
1401 if err.errno != errno.EEXIST:
1402 raise
1404 # Duplicate a working copy or other dir.
1405 def duplicate_dir(wc_name, wc_copy_name):
1406 """Copy the working copy WC_NAME to WC_COPY_NAME. Overwrite any
1407 existing tree at that location."""
1409 main.safe_rmtree(wc_copy_name)
1410 shutil.copytree(wc_name, wc_copy_name)
1414 def get_virginal_state(wc_dir, rev):
1415 "Return a virginal greek tree state for a WC and repos at revision REV."
1417 rev = str(rev) ### maybe switch rev to an integer?
1419 # copy the greek tree, shift it to the new wc_dir, insert a root elem,
1420 # then tweak all values
1421 state = main.greek_state.copy()
1422 state.wc_dir = wc_dir
1423 state.desc[''] = wc.StateItem()
1424 state.tweak(contents=None, status=' ', wc_rev=rev)
1426 return state
1428 def remove_admin_tmp_dir(wc_dir):
1429 "Remove the tmp directory within the administrative directory."
1431 tmp_path = os.path.join(wc_dir, main.get_admin_name(), 'tmp')
1432 ### Any reason not to use main.safe_rmtree()?
1433 os.rmdir(os.path.join(tmp_path, 'prop-base'))
1434 os.rmdir(os.path.join(tmp_path, 'props'))
1435 os.rmdir(os.path.join(tmp_path, 'text-base'))
1436 os.rmdir(tmp_path)
1438 # Cheap administrative directory locking
1439 def lock_admin_dir(wc_dir):
1440 "Lock a SVN administrative directory"
1442 path = os.path.join(wc_dir, main.get_admin_name(), 'lock')
1443 main.file_append(path, "stop looking!")
1445 def get_wc_uuid(wc_dir):
1446 "Return the UUID of the working copy at WC_DIR."
1447 return run_and_parse_info(wc_dir)[0]['Repository UUID']
1449 def create_failing_hook(repo_dir, hook_name, text):
1450 """Create a HOOK_NAME hook in REPO_DIR that prints TEXT to stderr and exits
1451 with an error."""
1453 hook_path = os.path.join(repo_dir, 'hooks', hook_name)
1454 main.create_python_hook_script(hook_path, 'import sys;\n'
1455 'sys.stderr.write("""%%s hook failed: %%s""" %% (%s, %s));\n'
1456 'sys.exit(1);\n' % (repr(hook_name), repr(text)))
1458 def enable_revprop_changes(repo_dir):
1459 """Enable revprop changes in a repository REPOS_DIR by creating a
1460 pre-revprop-change hook script and (if appropriate) making it executable."""
1462 hook_path = main.get_pre_revprop_change_hook_path (repo_dir)
1463 main.create_python_hook_script (hook_path, 'import sys; sys.exit(0)')
1465 def disable_revprop_changes(repo_dir):
1466 """Disable revprop changes in a repository REPO_DIR by creating a
1467 pre-revprop-change hook script like enable_revprop_changes, except that
1468 the hook prints "pre-revprop-change" followed by sys.argv"""
1469 hook_path = main.get_pre_revprop_change_hook_path (repo_dir)
1470 main.create_python_hook_script (hook_path,
1471 'import sys\n'
1472 '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'
1473 'sys.exit(1)\n')
1475 def create_failing_post_commit_hook(repo_dir):
1476 """Disable commits in a repository REPOS_DIR by creating a post-commit hook
1477 script which always reports errors."""
1479 hook_path = main.get_post_commit_hook_path (repo_dir)
1480 main.create_python_hook_script (hook_path, 'import sys; '
1481 'sys.stderr.write("Post-commit hook failed"); '
1482 'sys.exit(1)')
1484 # set_prop can be used for binary properties are values like '*' which are not
1485 # handled correctly when specified on the command line.
1486 def set_prop(expected_err, name, value, path, valp):
1487 """Set a property with value from a file"""
1488 valf = open(valp, 'wb')
1489 valf.seek(0)
1490 valf.truncate(0)
1491 valf.write(value)
1492 valf.flush()
1493 main.run_svn(expected_err, 'propset', '-F', valp, name, path)
1495 def check_prop(name, path, exp_out):
1496 """Verify that property NAME on PATH has a value of EXP_OUT"""
1497 # Not using run_svn because binary_mode must be set
1498 exit_code, out, err = main.run_command(main.svn_binary, None, 1, 'pg',
1499 '--strict', name, path,
1500 '--config-dir',
1501 main.default_config_dir,
1502 '--username', main.wc_author,
1503 '--password', main.wc_passwd)
1504 if out != exp_out:
1505 print("svn pg --strict %s output does not match expected." % name)
1506 print("Expected standard output: %s\n" % exp_out)
1507 print("Actual standard output: %s\n" % out)
1508 raise Failure
1510 def fill_file_with_lines(wc_path, line_nbr, line_descrip=None,
1511 append=True):
1512 """Change the file at WC_PATH (adding some lines), and return its
1513 new contents. LINE_NBR indicates the line number at which the new
1514 contents should assume that it's being appended. LINE_DESCRIP is
1515 something like 'This is line' (the default) or 'Conflicting line'."""
1517 if line_descrip is None:
1518 line_descrip = "This is line"
1520 # Generate the new contents for the file.
1521 contents = ""
1522 for n in range(line_nbr, line_nbr + 3):
1523 contents = contents + line_descrip + " " + repr(n) + " in '" + \
1524 os.path.basename(wc_path) + "'.\n"
1526 # Write the new contents to the file.
1527 if append:
1528 main.file_append(wc_path, contents)
1529 else:
1530 main.file_write(wc_path, contents)
1532 return contents
1534 def inject_conflict_into_wc(sbox, state_path, file_path,
1535 expected_disk, expected_status, merged_rev):
1536 """Create a conflict at FILE_PATH by replacing its contents,
1537 committing the change, backdating it to its previous revision,
1538 changing its contents again, then updating it to merge in the
1539 previous change."""
1541 wc_dir = sbox.wc_dir
1543 # Make a change to the file.
1544 contents = fill_file_with_lines(file_path, 1, "This is line", append=False)
1546 # Commit the changed file, first taking note of the current revision.
1547 prev_rev = expected_status.desc[state_path].wc_rev
1548 expected_output = wc.State(wc_dir, {
1549 state_path : wc.StateItem(verb='Sending'),
1551 if expected_status:
1552 expected_status.tweak(state_path, wc_rev=merged_rev)
1553 run_and_verify_commit(wc_dir, expected_output, expected_status,
1554 None, file_path)
1556 # Backdate the file.
1557 exit_code, output, errput = main.run_svn(None, "up", "-r", str(prev_rev),
1558 file_path)
1559 if expected_status:
1560 expected_status.tweak(state_path, wc_rev=prev_rev)
1562 # Make a conflicting change to the file, and backdate the file.
1563 conflicting_contents = fill_file_with_lines(file_path, 1, "Conflicting line",
1564 append=False)
1566 # Merge the previous change into the file to produce a conflict.
1567 if expected_disk:
1568 expected_disk.tweak(state_path, contents="")
1569 expected_output = wc.State(wc_dir, {
1570 state_path : wc.StateItem(status='C '),
1572 inject_conflict_into_expected_state(state_path,
1573 expected_disk, expected_status,
1574 conflicting_contents, contents,
1575 merged_rev)
1576 exit_code, output, errput = main.run_svn(None, "up", "-r", str(merged_rev),
1577 sbox.repo_url + "/" + state_path,
1578 file_path)
1579 if expected_status:
1580 expected_status.tweak(state_path, wc_rev=merged_rev)
1582 def inject_conflict_into_expected_state(state_path,
1583 expected_disk, expected_status,
1584 wc_text, merged_text, merged_rev):
1585 """Update the EXPECTED_DISK and EXPECTED_STATUS trees for the
1586 conflict at STATE_PATH (ignored if None). WC_TEXT, MERGED_TEXT, and
1587 MERGED_REV are used to determine the contents of the conflict (the
1588 text parameters should be newline-terminated)."""
1589 if expected_disk:
1590 conflict_marker = make_conflict_marker_text(wc_text, merged_text,
1591 merged_rev)
1592 existing_text = expected_disk.desc[state_path].contents or ""
1593 expected_disk.tweak(state_path, contents=existing_text + conflict_marker)
1595 if expected_status:
1596 expected_status.tweak(state_path, status='C ')
1598 def make_conflict_marker_text(wc_text, merged_text, merged_rev):
1599 """Return the conflict marker text described by WC_TEXT (the current
1600 text in the working copy, MERGED_TEXT (the conflicting text merged
1601 in), and MERGED_REV (the revision from whence the conflicting text
1602 came)."""
1603 return "<<<<<<< .working\n" + wc_text + "=======\n" + \
1604 merged_text + ">>>>>>> .merge-right.r" + str(merged_rev) + "\n"
1607 def build_greek_tree_conflicts(sbox):
1608 """Create a working copy that has tree-conflict markings.
1609 After this function has been called, sbox.wc_dir is a working
1610 copy that has specific tree-conflict markings.
1612 In particular, this does two conflicting sets of edits and performs an
1613 update so that tree conflicts appear.
1615 Note that this function calls sbox.build() because it needs a clean sbox.
1616 So, there is no need to call sbox.build() before this.
1618 The conflicts are the result of an 'update' on the following changes:
1620 Incoming Local
1622 A/D/G/pi text-mod del
1623 A/D/G/rho del text-mod
1624 A/D/G/tau del del
1626 This function is useful for testing that tree-conflicts are handled
1627 properly once they have appeared, e.g. that commits are blocked, that the
1628 info output is correct, etc.
1630 See also the tree-conflicts tests using deep_trees in various other
1631 .py files, and tree_conflict_tests.py.
1634 sbox.build()
1635 wc_dir = sbox.wc_dir
1636 j = os.path.join
1637 G = j(wc_dir, 'A', 'D', 'G')
1638 pi = j(G, 'pi')
1639 rho = j(G, 'rho')
1640 tau = j(G, 'tau')
1642 # Make incoming changes and "store them away" with a commit.
1643 main.file_append(pi, "Incoming edit.\n")
1644 main.run_svn(None, 'del', rho)
1645 main.run_svn(None, 'del', tau)
1647 expected_output = wc.State(wc_dir, {
1648 'A/D/G/pi' : Item(verb='Sending'),
1649 'A/D/G/rho' : Item(verb='Deleting'),
1650 'A/D/G/tau' : Item(verb='Deleting'),
1652 expected_status = get_virginal_state(wc_dir, 1)
1653 expected_status.tweak('A/D/G/pi', wc_rev='2')
1654 expected_status.remove('A/D/G/rho', 'A/D/G/tau')
1655 run_and_verify_commit(wc_dir, expected_output, expected_status, None,
1656 '-m', 'Incoming changes.', wc_dir )
1658 # Update back to the pristine state ("time-warp").
1659 expected_output = wc.State(wc_dir, {
1660 'A/D/G/pi' : Item(status='U '),
1661 'A/D/G/rho' : Item(status='A '),
1662 'A/D/G/tau' : Item(status='A '),
1664 expected_disk = main.greek_state
1665 expected_status = get_virginal_state(wc_dir, 1)
1666 run_and_verify_update(wc_dir, expected_output, expected_disk,
1667 expected_status, None, None, None, None, None, False,
1668 '-r', '1', wc_dir)
1670 # Make local changes
1671 main.run_svn(None, 'del', pi)
1672 main.file_append(rho, "Local edit.\n")
1673 main.run_svn(None, 'del', tau)
1675 # Update, receiving the incoming changes on top of the local changes,
1676 # causing tree conflicts. Don't check for any particular result: that is
1677 # the job of other tests.
1678 run_and_verify_svn(None, verify.AnyOutput, [], 'update', wc_dir)
1681 def make_deep_trees(base):
1682 """Helper function for deep trees conflicts. Create a set of trees,
1683 each in its own "container" dir. Any conflicts can be tested separately
1684 in each container.
1686 j = os.path.join
1687 # Create the container dirs.
1688 F = j(base, 'F')
1689 D = j(base, 'D')
1690 DF = j(base, 'DF')
1691 DD = j(base, 'DD')
1692 DDF = j(base, 'DDF')
1693 DDD = j(base, 'DDD')
1694 os.makedirs(F)
1695 os.makedirs(j(D, 'D1'))
1696 os.makedirs(j(DF, 'D1'))
1697 os.makedirs(j(DD, 'D1', 'D2'))
1698 os.makedirs(j(DDF, 'D1', 'D2'))
1699 os.makedirs(j(DDD, 'D1', 'D2', 'D3'))
1701 # Create their files.
1702 alpha = j(F, 'alpha')
1703 beta = j(DF, 'D1', 'beta')
1704 gamma = j(DDF, 'D1', 'D2', 'gamma')
1705 main.file_append(alpha, "This is the file 'alpha'.\n")
1706 main.file_append(beta, "This is the file 'beta'.\n")
1707 main.file_append(gamma, "This is the file 'gamma'.\n")
1710 def add_deep_trees(sbox, base_dir_name):
1711 """Prepare a "deep_trees" within a given directory.
1713 The directory <sbox.wc_dir>/<base_dir_name> is created and a deep_tree
1714 is created within. The items are only added, a commit has to be
1715 called separately, if needed.
1717 <base_dir_name> will thus be a container for the set of containers
1718 mentioned in make_deep_trees().
1720 j = os.path.join
1721 base = j(sbox.wc_dir, base_dir_name)
1722 make_deep_trees(base)
1723 main.run_svn(None, 'add', base)
1726 Item = wc.StateItem
1728 # initial deep trees state
1729 deep_trees_virginal_state = wc.State('', {
1730 'F' : Item(),
1731 'F/alpha' : Item("This is the file 'alpha'.\n"),
1732 'D' : Item(),
1733 'D/D1' : Item(),
1734 'DF' : Item(),
1735 'DF/D1' : Item(),
1736 'DF/D1/beta' : Item("This is the file 'beta'.\n"),
1737 'DD' : Item(),
1738 'DD/D1' : Item(),
1739 'DD/D1/D2' : Item(),
1740 'DDF' : Item(),
1741 'DDF/D1' : Item(),
1742 'DDF/D1/D2' : Item(),
1743 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\n"),
1744 'DDD' : Item(),
1745 'DDD/D1' : Item(),
1746 'DDD/D1/D2' : Item(),
1747 'DDD/D1/D2/D3' : Item(),
1751 # Many actions on deep trees and their resulting states...
1753 def deep_trees_leaf_edit(base):
1754 """Helper function for deep trees test cases. Append text to files,
1755 create new files in empty directories, and change leaf node properties."""
1756 j = os.path.join
1757 F = j(base, 'F', 'alpha')
1758 DF = j(base, 'DF', 'D1', 'beta')
1759 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
1760 main.file_append(F, "More text for file alpha.\n")
1761 main.file_append(DF, "More text for file beta.\n")
1762 main.file_append(DDF, "More text for file gamma.\n")
1763 run_and_verify_svn(None, verify.AnyOutput, [],
1764 'propset', 'prop1', '1', F, DF, DDF)
1766 D = j(base, 'D', 'D1')
1767 DD = j(base, 'DD', 'D1', 'D2')
1768 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
1769 run_and_verify_svn(None, verify.AnyOutput, [],
1770 'propset', 'prop1', '1', D, DD, DDD)
1771 D = j(base, 'D', 'D1', 'delta')
1772 DD = j(base, 'DD', 'D1', 'D2', 'epsilon')
1773 DDD = j(base, 'DDD', 'D1', 'D2', 'D3', 'zeta')
1774 main.file_append(D, "This is the file 'delta'.\n")
1775 main.file_append(DD, "This is the file 'epsilon'.\n")
1776 main.file_append(DDD, "This is the file 'zeta'.\n")
1777 run_and_verify_svn(None, verify.AnyOutput, [],
1778 'add', D, DD, DDD)
1780 # deep trees state after a call to deep_trees_leaf_edit
1781 deep_trees_after_leaf_edit = wc.State('', {
1782 'F' : Item(),
1783 'F/alpha' : Item("This is the file 'alpha'.\nMore text for file alpha.\n"),
1784 'D' : Item(),
1785 'D/D1' : Item(),
1786 'D/D1/delta' : Item("This is the file 'delta'.\n"),
1787 'DF' : Item(),
1788 'DF/D1' : Item(),
1789 'DF/D1/beta' : Item("This is the file 'beta'.\nMore text for file beta.\n"),
1790 'DD' : Item(),
1791 'DD/D1' : Item(),
1792 'DD/D1/D2' : Item(),
1793 'DD/D1/D2/epsilon' : Item("This is the file 'epsilon'.\n"),
1794 'DDF' : Item(),
1795 'DDF/D1' : Item(),
1796 'DDF/D1/D2' : Item(),
1797 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\nMore text for file gamma.\n"),
1798 'DDD' : Item(),
1799 'DDD/D1' : Item(),
1800 'DDD/D1/D2' : Item(),
1801 'DDD/D1/D2/D3' : Item(),
1802 'DDD/D1/D2/D3/zeta' : Item("This is the file 'zeta'.\n"),
1806 def deep_trees_leaf_del(base):
1807 """Helper function for deep trees test cases. Delete files and empty
1808 dirs."""
1809 j = os.path.join
1810 F = j(base, 'F', 'alpha')
1811 D = j(base, 'D', 'D1')
1812 DF = j(base, 'DF', 'D1', 'beta')
1813 DD = j(base, 'DD', 'D1', 'D2')
1814 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
1815 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
1816 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
1818 # deep trees state after a call to deep_trees_leaf_del
1819 deep_trees_after_leaf_del = wc.State('', {
1820 'F' : Item(),
1821 'D' : Item(),
1822 'DF' : Item(),
1823 'DF/D1' : Item(),
1824 'DD' : Item(),
1825 'DD/D1' : Item(),
1826 'DDF' : Item(),
1827 'DDF/D1' : Item(),
1828 'DDF/D1/D2' : Item(),
1829 'DDD' : Item(),
1830 'DDD/D1' : Item(),
1831 'DDD/D1/D2' : Item(),
1835 def deep_trees_tree_del(base):
1836 """Helper function for deep trees test cases. Delete top-level dirs."""
1837 j = os.path.join
1838 F = j(base, 'F', 'alpha')
1839 D = j(base, 'D', 'D1')
1840 DF = j(base, 'DF', 'D1')
1841 DD = j(base, 'DD', 'D1')
1842 DDF = j(base, 'DDF', 'D1')
1843 DDD = j(base, 'DDD', 'D1')
1844 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
1846 # deep trees state after a call to deep_trees_tree_del
1847 deep_trees_after_tree_del = wc.State('', {
1848 'F' : Item(),
1849 'D' : Item(),
1850 'DF' : Item(),
1851 'DD' : Item(),
1852 'DDF' : Item(),
1853 'DDD' : Item(),
1856 # deep trees state without any files
1857 deep_trees_empty_dirs = wc.State('', {
1858 'F' : Item(),
1859 'D' : Item(),
1860 'D/D1' : Item(),
1861 'DF' : Item(),
1862 'DF/D1' : Item(),
1863 'DD' : Item(),
1864 'DD/D1' : Item(),
1865 'DD/D1/D2' : Item(),
1866 'DDF' : Item(),
1867 'DDF/D1' : Item(),
1868 'DDF/D1/D2' : Item(),
1869 'DDD' : Item(),
1870 'DDD/D1' : Item(),
1871 'DDD/D1/D2' : Item(),
1872 'DDD/D1/D2/D3' : Item(),
1875 # Expected merge/update/switch output.
1877 deep_trees_conflict_output = wc.State('', {
1878 'F/alpha' : Item(status=' ', treeconflict='C'),
1879 'D/D1' : Item(status=' ', treeconflict='C'),
1880 'DF/D1' : Item(status=' ', treeconflict='C'),
1881 'DD/D1' : Item(status=' ', treeconflict='C'),
1882 'DDF/D1' : Item(status=' ', treeconflict='C'),
1883 'DDD/D1' : Item(status=' ', treeconflict='C'),
1886 deep_trees_conflict_output_skipped = wc.State('', {
1887 'D/D1' : Item(verb='Skipped'),
1888 'F/alpha' : Item(verb='Skipped'),
1889 'DD/D1' : Item(verb='Skipped'),
1890 'DF/D1' : Item(verb='Skipped'),
1891 'DDD/D1' : Item(verb='Skipped'),
1892 'DDF/D1' : Item(verb='Skipped'),
1895 # Expected status output after merge/update/switch.
1897 deep_trees_status_local_tree_del = wc.State('', {
1898 '' : Item(status=' ', wc_rev=3),
1899 'D' : Item(status=' ', wc_rev=3),
1900 'D/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
1901 'DD' : Item(status=' ', wc_rev=3),
1902 'DD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
1903 'DD/D1/D2' : Item(status='D ', wc_rev=2),
1904 'DDD' : Item(status=' ', wc_rev=3),
1905 'DDD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
1906 'DDD/D1/D2' : Item(status='D ', wc_rev=2),
1907 'DDD/D1/D2/D3' : Item(status='D ', wc_rev=2),
1908 'DDF' : Item(status=' ', wc_rev=3),
1909 'DDF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
1910 'DDF/D1/D2' : Item(status='D ', wc_rev=2),
1911 'DDF/D1/D2/gamma' : Item(status='D ', wc_rev=2),
1912 'DF' : Item(status=' ', wc_rev=3),
1913 'DF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
1914 'DF/D1/beta' : Item(status='D ', wc_rev=2),
1915 'F' : Item(status=' ', wc_rev=3),
1916 'F/alpha' : Item(status='D ', wc_rev=2, treeconflict='C'),
1919 deep_trees_status_local_leaf_edit = wc.State('', {
1920 '' : Item(status=' ', wc_rev=3),
1921 'D' : Item(status=' ', wc_rev=3),
1922 'D/D1' : Item(status=' M', wc_rev=2, treeconflict='C'),
1923 'D/D1/delta' : Item(status='A ', wc_rev=0),
1924 'DD' : Item(status=' ', wc_rev=3),
1925 'DD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
1926 'DD/D1/D2' : Item(status=' M', wc_rev=2),
1927 'DD/D1/D2/epsilon' : Item(status='A ', wc_rev=0),
1928 'DDD' : Item(status=' ', wc_rev=3),
1929 'DDD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
1930 'DDD/D1/D2' : Item(status=' ', wc_rev=2),
1931 'DDD/D1/D2/D3' : Item(status=' M', wc_rev=2),
1932 'DDD/D1/D2/D3/zeta' : Item(status='A ', wc_rev=0),
1933 'DDF' : Item(status=' ', wc_rev=3),
1934 'DDF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
1935 'DDF/D1/D2' : Item(status=' ', wc_rev=2),
1936 'DDF/D1/D2/gamma' : Item(status='MM', wc_rev=2),
1937 'DF' : Item(status=' ', wc_rev=3),
1938 'DF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
1939 'DF/D1/beta' : Item(status='MM', wc_rev=2),
1940 'F' : Item(status=' ', wc_rev=3),
1941 'F/alpha' : Item(status='MM', wc_rev=2, treeconflict='C'),
1945 class DeepTreesTestCase:
1946 """Describes one tree-conflicts test case.
1947 See deep_trees_run_tests_scheme_for_update(), ..._switch(), ..._merge().
1949 The name field is the subdirectory name in which the test should be run.
1951 The local_action and incoming_action are the functions to run
1952 to construct the local changes and incoming changes, respectively.
1953 See deep_trees_leaf_edit, deep_trees_tree_del, etc.
1955 The expected_* and error_re_string arguments are described in functions
1956 run_and_verify_[update|switch|merge]
1958 Note: expected_skip is only used in merge, i.e. using
1959 deep_trees_run_tests_scheme_for_merge.
1962 def __init__(self, name, local_action, incoming_action,
1963 expected_output = None, expected_disk = None,
1964 expected_status = None, expected_skip = None,
1965 error_re_string = None):
1966 self.name = name
1967 self.local_action = local_action
1968 self.incoming_action = incoming_action
1969 self.expected_output = expected_output
1970 self.expected_disk = expected_disk
1971 self.expected_status = expected_status
1972 self.expected_skip = expected_skip
1973 self.error_re_string = error_re_string
1977 def deep_trees_run_tests_scheme_for_update(sbox, greater_scheme):
1979 Runs a given list of tests for conflicts occuring at an update operation.
1981 This function wants to save time and perform a number of different
1982 test cases using just a single repository and performing just one commit
1983 for all test cases instead of one for each test case.
1985 1) Each test case is initialized in a separate subdir. Each subdir
1986 again contains one set of "deep_trees", being separate container
1987 dirs for different depths of trees (F, D, DF, DD, DDF, DDD).
1989 2) A commit is performed across all test cases and depths.
1990 (our initial state, -r2)
1992 3) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
1993 its *incoming* action is performed (e.g. "deep_trees_leaf_edit"), in
1994 each of the different depth trees (F, D, DF, ... DDD).
1996 4) A commit is performed across all test cases and depths:
1997 our "incoming" state is "stored away in the repository for now",
1998 -r3.
2000 5) All test case dirs and contained deep_trees are time-warped
2001 (updated) back to -r2, the initial state containing deep_trees.
2003 6) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2004 its *local* action is performed (e.g. "deep_trees_leaf_del"), in
2005 each of the different depth trees (F, D, DF, ... DDD).
2007 7) An update to -r3 is performed across all test cases and depths.
2008 This causes tree-conflicts between the "local" state in the working
2009 copy and the "incoming" state from the repository, -r3.
2011 The sbox parameter is just the sbox passed to a test function. No need
2012 to call sbox.build(), since it is called (once) within this function.
2014 The "table" greater_scheme models all of the different test cases
2015 that should be run using a single repository.
2017 greater_scheme is a list of DeepTreesTestCase items, which define complete
2018 test setups, so that they can be performed as described above.
2021 j = os.path.join
2023 sbox.build()
2024 wc_dir = sbox.wc_dir
2027 # 1) create directories
2029 for test_case in greater_scheme:
2030 try:
2031 add_deep_trees(sbox, test_case.name)
2032 except:
2033 print("ERROR IN: Tests scheme for update: "
2034 + "while setting up deep trees in '%s'" % test_case.name)
2035 raise
2038 # 2) commit initial state
2040 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2043 # 3) apply incoming changes
2045 for test_case in greater_scheme:
2046 try:
2047 test_case.incoming_action(j(sbox.wc_dir, test_case.name))
2048 except:
2049 print("ERROR IN: Tests scheme for update: "
2050 + "while performing incoming action in '%s'" % test_case.name)
2051 raise
2054 # 4) commit incoming changes
2056 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2059 # 5) time-warp back to -r2
2061 main.run_svn(None, 'update', '-r2', wc_dir)
2064 # 6) apply local changes
2066 for test_case in greater_scheme:
2067 try:
2068 test_case.local_action(j(wc_dir, test_case.name))
2069 except:
2070 print("ERROR IN: Tests scheme for update: "
2071 + "while performing local action in '%s'" % test_case.name)
2072 raise
2075 # 7) update to -r3, conflicting with incoming changes.
2076 # A lot of different things are expected.
2077 # Do separate update operations for each test case.
2079 for test_case in greater_scheme:
2080 try:
2081 base = j(wc_dir, test_case.name)
2083 x_out = test_case.expected_output
2084 if x_out != None:
2085 x_out = x_out.copy()
2086 x_out.wc_dir = base
2088 x_disk = test_case.expected_disk
2090 x_status = test_case.expected_status
2091 if x_status != None:
2092 x_status.copy()
2093 x_status.wc_dir = base
2095 run_and_verify_update(base, x_out, x_disk, None,
2096 error_re_string = test_case.error_re_string)
2097 if x_status:
2098 run_and_verify_unquiet_status(base, x_status)
2099 except:
2100 print("ERROR IN: Tests scheme for update: "
2101 + "while verifying in '%s'" % test_case.name)
2102 raise
2106 def deep_trees_skipping_on_update(sbox, test_case, skip_paths,
2107 chdir_skip_paths):
2109 Create tree conflicts, then update again, expecting the existing tree
2110 conflicts to be skipped.
2113 j = os.path.join
2114 wc_dir = sbox.wc_dir
2116 # Initialize silently.
2117 setup_case = DeepTreesTestCase(test_case.name,
2118 test_case.local_action,
2119 test_case.incoming_action,
2120 None,
2121 None,
2122 None)
2123 deep_trees_run_tests_scheme_for_update(sbox, [setup_case])
2125 # Update whole working copy again.
2126 base = j(wc_dir, test_case.name)
2128 x_out = test_case.expected_output
2129 if x_out != None:
2130 x_out = x_out.copy()
2131 x_out.wc_dir = base
2133 x_disk = test_case.expected_disk
2135 x_status = test_case.expected_status
2136 if x_status != None:
2137 x_status.copy()
2138 x_status.wc_dir = base
2140 run_and_verify_update(base, x_out, x_disk, None,
2141 error_re_string = test_case.error_re_string)
2143 run_and_verify_unquiet_status(base, x_status)
2145 # Update subtrees, expecting a single 'Skipped' output for each one.
2146 for path in skip_paths:
2147 run_and_verify_update(j(base, path),
2148 wc.State(base, {path : Item(verb='Skipped')}),
2149 None, None)
2151 # Update subtrees, expecting a single 'Skipped' output for each one.
2152 # This time, cd to the subdir before .
2153 was_cwd = os.getcwd()
2154 for path, skipped in chdir_skip_paths:
2155 #print("CHDIR TO: %s" % j(base, path))
2156 os.chdir(j(base, path))
2157 run_and_verify_update('',
2158 wc.State('', {skipped : Item(verb='Skipped')}),
2159 None, None)
2160 os.chdir(was_cwd)
2162 run_and_verify_unquiet_status(base, x_status)
2165 def deep_trees_run_tests_scheme_for_switch(sbox, greater_scheme):
2167 Runs a given list of tests for conflicts occuring at a switch operation.
2169 This function wants to save time and perform a number of different
2170 test cases using just a single repository and performing just one commit
2171 for all test cases instead of one for each test case.
2173 1) Each test case is initialized in a separate subdir. Each subdir
2174 again contains two subdirs: one "local" and one "incoming" for
2175 the switch operation. These contain a set of deep_trees each.
2177 2) A commit is performed across all test cases and depths.
2178 (our initial state, -r2)
2180 3) In each test case subdir's incoming subdir, the
2181 incoming actions are performed.
2183 4) A commit is performed across all test cases and depths. (-r3)
2185 5) In each test case subdir's local subdir, the local actions are
2186 performed. They remain uncommitted in the working copy.
2188 6) In each test case subdir's local dir, a switch is performed to its
2189 corresponding incoming dir.
2190 This causes conflicts between the "local" state in the working
2191 copy and the "incoming" state from the incoming subdir (still -r3).
2193 The sbox parameter is just the sbox passed to a test function. No need
2194 to call sbox.build(), since it is called (once) within this function.
2196 The "table" greater_scheme models all of the different test cases
2197 that should be run using a single repository.
2199 greater_scheme is a list of DeepTreesTestCase items, which define complete
2200 test setups, so that they can be performed as described above.
2203 j = os.path.join
2205 sbox.build()
2206 wc_dir = sbox.wc_dir
2209 # 1) Create directories.
2211 for test_case in greater_scheme:
2212 try:
2213 base = j(sbox.wc_dir, test_case.name)
2214 os.makedirs(base)
2215 make_deep_trees(j(base, "local"))
2216 make_deep_trees(j(base, "incoming"))
2217 main.run_svn(None, 'add', base)
2218 except:
2219 print("ERROR IN: Tests scheme for switch: "
2220 + "while setting up deep trees in '%s'" % test_case.name)
2221 raise
2224 # 2) Commit initial state (-r2).
2226 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2229 # 3) Apply incoming changes
2231 for test_case in greater_scheme:
2232 try:
2233 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2234 except:
2235 print("ERROR IN: Tests scheme for switch: "
2236 + "while performing incoming action in '%s'" % test_case.name)
2237 raise
2240 # 4) Commit all changes (-r3).
2242 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2245 # 5) Apply local changes in their according subdirs.
2247 for test_case in greater_scheme:
2248 try:
2249 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
2250 except:
2251 print("ERROR IN: Tests scheme for switch: "
2252 + "while performing local action in '%s'" % test_case.name)
2253 raise
2256 # 6) switch the local dir to the incoming url, conflicting with incoming
2257 # changes. A lot of different things are expected.
2258 # Do separate switch operations for each test case.
2260 for test_case in greater_scheme:
2261 try:
2262 local = j(wc_dir, test_case.name, "local")
2263 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
2265 x_out = test_case.expected_output
2266 if x_out != None:
2267 x_out = x_out.copy()
2268 x_out.wc_dir = local
2270 x_disk = test_case.expected_disk
2272 x_status = test_case.expected_status
2273 if x_status != None:
2274 x_status.copy()
2275 x_status.wc_dir = local
2277 run_and_verify_switch(local, local, incoming, x_out, x_disk, None,
2278 error_re_string = test_case.error_re_string)
2279 run_and_verify_unquiet_status(local, x_status)
2280 except:
2281 print("ERROR IN: Tests scheme for switch: "
2282 + "while verifying in '%s'" % test_case.name)
2283 raise
2286 def deep_trees_run_tests_scheme_for_merge(sbox, greater_scheme,
2287 do_commit_local_changes):
2289 Runs a given list of tests for conflicts occuring at a merge operation.
2291 This function wants to save time and perform a number of different
2292 test cases using just a single repository and performing just one commit
2293 for all test cases instead of one for each test case.
2295 1) Each test case is initialized in a separate subdir. Each subdir
2296 initially contains another subdir, called "incoming", which
2297 contains a set of deep_trees.
2299 2) A commit is performed across all test cases and depths.
2300 (a pre-initial state)
2302 3) In each test case subdir, the "incoming" subdir is copied to "local",
2303 via the `svn copy' command. Each test case's subdir now has two sub-
2304 dirs: "local" and "incoming", initial states for the merge operation.
2306 4) An update is performed across all test cases and depths, so that the
2307 copies made in 3) are pulled into the wc.
2309 5) In each test case's "incoming" subdir, the incoming action is
2310 performed.
2312 6) A commit is performed across all test cases and depths, to commit
2313 the incoming changes.
2314 If do_commit_local_changes is True, this becomes step 7 (swap steps).
2316 7) In each test case's "local" subdir, the local_action is performed.
2317 If do_commit_local_changes is True, this becomes step 6 (swap steps).
2318 Then, in effect, the local changes are committed as well.
2320 8) In each test case subdir, the "incoming" subdir is merged into the
2321 "local" subdir.
2322 This causes conflicts between the "local" state in the working
2323 copy and the "incoming" state from the incoming subdir.
2325 The sbox parameter is just the sbox passed to a test function. No need
2326 to call sbox.build(), since it is called (once) within this function.
2328 The "table" greater_scheme models all of the different test cases
2329 that should be run using a single repository.
2331 greater_scheme is a list of DeepTreesTestCase items, which define complete
2332 test setups, so that they can be performed as described above.
2335 j = os.path.join
2337 sbox.build()
2338 wc_dir = sbox.wc_dir
2340 # 1) Create directories.
2341 for test_case in greater_scheme:
2342 try:
2343 base = j(sbox.wc_dir, test_case.name)
2344 os.makedirs(base)
2345 make_deep_trees(j(base, "incoming"))
2346 main.run_svn(None, 'add', base)
2347 except:
2348 print("ERROR IN: Tests scheme for merge: "
2349 + "while setting up deep trees in '%s'" % test_case.name)
2350 raise
2353 # 2) Commit pre-initial state (-r2).
2355 main.run_svn(None, 'commit', '-m', 'pre-initial state', wc_dir)
2358 # 3) Copy "incoming" to "local".
2360 for test_case in greater_scheme:
2361 try:
2362 base_url = sbox.repo_url + "/" + test_case.name
2363 incoming_url = base_url + "/incoming"
2364 local_url = base_url + "/local"
2365 main.run_svn(None, 'cp', incoming_url, local_url, '-m',
2366 'copy incoming to local')
2367 except:
2368 print("ERROR IN: Tests scheme for merge: "
2369 + "while copying deep trees in '%s'" % test_case.name)
2370 raise
2372 # 4) Update to load all of the "/local" subdirs into the working copies.
2374 try:
2375 main.run_svn(None, 'up', sbox.wc_dir)
2376 except:
2377 print("ERROR IN: Tests scheme for merge: "
2378 + "while updating local subdirs")
2379 raise
2382 # 5) Perform incoming actions
2384 for test_case in greater_scheme:
2385 try:
2386 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2387 except:
2388 print("ERROR IN: Tests scheme for merge: "
2389 + "while performing incoming action in '%s'" % test_case.name)
2390 raise
2393 # 6) or 7) Commit all incoming actions
2395 if not do_commit_local_changes:
2396 try:
2397 main.run_svn(None, 'ci', '-m', 'Committing incoming actions',
2398 sbox.wc_dir)
2399 except:
2400 print("ERROR IN: Tests scheme for merge: "
2401 + "while committing incoming actions")
2402 raise
2405 # 7) or 6) Perform all local actions.
2407 for test_case in greater_scheme:
2408 try:
2409 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
2410 except:
2411 print("ERROR IN: Tests scheme for merge: "
2412 + "while performing local action in '%s'" % test_case.name)
2413 raise
2416 # 6) or 7) Commit all incoming actions
2418 if do_commit_local_changes:
2419 try:
2420 main.run_svn(None, 'ci', '-m', 'Committing incoming and local actions',
2421 sbox.wc_dir)
2422 except:
2423 print("ERROR IN: Tests scheme for merge: "
2424 + "while committing incoming and local actions")
2425 raise
2428 # 8) Merge all "incoming" subdirs to their respective "local" subdirs.
2429 # This creates conflicts between the local changes in the "local" wc
2430 # subdirs and the incoming states committed in the "incoming" subdirs.
2432 for test_case in greater_scheme:
2433 try:
2434 local = j(sbox.wc_dir, test_case.name, "local")
2435 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
2437 x_out = test_case.expected_output
2438 if x_out != None:
2439 x_out = x_out.copy()
2440 x_out.wc_dir = local
2442 x_disk = test_case.expected_disk
2444 x_status = test_case.expected_status
2445 if x_status != None:
2446 x_status.copy()
2447 x_status.wc_dir = local
2449 x_skip = test_case.expected_skip
2450 if x_skip != None:
2451 x_skip.copy()
2452 x_skip.wc_dir = local
2454 run_and_verify_merge(local, None, None, incoming,
2455 x_out, x_disk, None, x_skip,
2456 error_re_string = test_case.error_re_string,
2457 dry_run = False)
2458 run_and_verify_unquiet_status(local, x_status)
2459 except:
2460 print("ERROR IN: Tests scheme for merge: "
2461 + "while verifying in '%s'" % test_case.name)
2462 raise