Make the target arguments to sbox.simple_rm() etc. be relpaths relative to
[svnrdump.git] / svntest / actions.py
blobf632323f7ab262c842b593306a8f4cebc7ab2683
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 # Licensed to the Apache Software Foundation (ASF) under one
9 # or more contributor license agreements. See the NOTICE file
10 # distributed with this work for additional information
11 # regarding copyright ownership. The ASF licenses this file
12 # to you under the Apache License, Version 2.0 (the
13 # "License"); you may not use this file except in compliance
14 # with the License. You may obtain a copy of the License at
16 # http://www.apache.org/licenses/LICENSE-2.0
18 # Unless required by applicable law or agreed to in writing,
19 # software distributed under the License is distributed on an
20 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 # KIND, either express or implied. See the License for the
22 # specific language governing permissions and limitations
23 # under the License.
24 ######################################################################
26 import os, shutil, re, sys, errno
27 import difflib, pprint
28 import xml.parsers.expat
29 from xml.dom.minidom import parseString
31 import svntest
32 from svntest import main, verify, tree, wc
33 from svntest import Failure
35 def no_sleep_for_timestamps():
36 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'yes'
38 def do_sleep_for_timestamps():
39 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'no'
41 def no_relocate_validation():
42 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION'] = 'yes'
44 def do_relocate_validation():
45 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION'] = 'no'
47 def setup_pristine_greek_repository():
48 """Create the pristine repository and 'svn import' the greek tree"""
50 # these directories don't exist out of the box, so we may have to create them
51 if not os.path.exists(main.general_wc_dir):
52 os.makedirs(main.general_wc_dir)
54 if not os.path.exists(main.general_repo_dir):
55 os.makedirs(main.general_repo_dir) # this also creates all the intermediate dirs
57 # If there's no pristine repos, create one.
58 if not os.path.exists(main.pristine_greek_repos_dir):
59 main.create_repos(main.pristine_greek_repos_dir)
61 # if this is dav, gives us access rights to import the greek tree.
62 if main.is_ra_type_dav():
63 authz_file = os.path.join(main.work_dir, "authz")
64 main.file_write(authz_file, "[/]\n* = rw\n")
66 # dump the greek tree to disk.
67 main.greek_state.write_to_disk(main.greek_dump_dir)
69 # import the greek tree, using l:foo/p:bar
70 ### todo: svn should not be prompting for auth info when using
71 ### repositories with no auth/auth requirements
72 exit_code, output, errput = main.run_svn(None, 'import', '-m',
73 'Log message for revision 1.',
74 main.greek_dump_dir,
75 main.pristine_greek_repos_url)
77 # check for any errors from the import
78 if len(errput):
79 display_lines("Errors during initial 'svn import':",
80 'STDERR', None, errput)
81 sys.exit(1)
83 # verify the printed output of 'svn import'.
84 lastline = output.pop().strip()
85 match = re.search("(Committed|Imported) revision [0-9]+.", lastline)
86 if not match:
87 print("ERROR: import did not succeed, while creating greek repos.")
88 print("The final line from 'svn import' was:")
89 print(lastline)
90 sys.exit(1)
91 output_tree = wc.State.from_commit(output)
93 expected_output_tree = main.greek_state.copy(main.greek_dump_dir)
94 expected_output_tree.tweak(verb='Adding',
95 contents=None)
97 try:
98 expected_output_tree.compare_and_display('output', output_tree)
99 except tree.SVNTreeUnequal:
100 verify.display_trees("ERROR: output of import command is unexpected.",
101 "OUTPUT TREE",
102 expected_output_tree.old_tree(),
103 output_tree.old_tree())
104 sys.exit(1)
106 # Finally, disallow any changes to the "pristine" repos.
107 error_msg = "Don't modify the pristine repository"
108 create_failing_hook(main.pristine_greek_repos_dir, 'start-commit', error_msg)
109 create_failing_hook(main.pristine_greek_repos_dir, 'pre-lock', error_msg)
110 create_failing_hook(main.pristine_greek_repos_dir, 'pre-revprop-change', error_msg)
113 ######################################################################
115 def guarantee_empty_repository(path):
116 """Guarantee that a local svn repository exists at PATH, containing
117 nothing."""
119 if path == main.pristine_greek_repos_dir:
120 print("ERROR: attempt to overwrite the pristine repos! Aborting.")
121 sys.exit(1)
123 # create an empty repository at PATH.
124 main.safe_rmtree(path)
125 main.create_repos(path)
127 # Used by every test, so that they can run independently of one
128 # another. Every time this routine is called, it recursively copies
129 # the `pristine repos' to a new location.
130 # Note: make sure setup_pristine_greek_repository was called once before
131 # using this function.
132 def guarantee_greek_repository(path):
133 """Guarantee that a local svn repository exists at PATH, containing
134 nothing but the greek-tree at revision 1."""
136 if path == main.pristine_greek_repos_dir:
137 print("ERROR: attempt to overwrite the pristine repos! Aborting.")
138 sys.exit(1)
140 # copy the pristine repository to PATH.
141 main.safe_rmtree(path)
142 if main.copy_repos(main.pristine_greek_repos_dir, path, 1):
143 print("ERROR: copying repository failed.")
144 sys.exit(1)
146 # make the repos world-writeable, for mod_dav_svn's sake.
147 main.chmod_tree(path, 0666, 0666)
150 def run_and_verify_atomic_ra_revprop_change(message,
151 expected_stdout,
152 expected_stderr,
153 expected_exit,
154 url, revision, propname,
155 old_propval, propval,
156 want_error):
157 """Run atomic-ra-revprop-change helper and check its output and exit code.
158 Transforms OLD_PROPVAL and PROPVAL into a skel.
159 For HTTP, the default HTTP library is used."""
161 KEY_OLD_PROPVAL = "old_value_p"
162 KEY_NEW_PROPVAL = "value"
164 def skel_make_atom(word):
165 return "%d %s" % (len(word), word)
167 def make_proplist_skel_part(nick, val):
168 if val is None:
169 return ""
170 else:
171 return "%s %s" % (skel_make_atom(nick), skel_make_atom(val))
173 skel = "( %s %s )" % (make_proplist_skel_part(KEY_OLD_PROPVAL, old_propval),
174 make_proplist_skel_part(KEY_NEW_PROPVAL, propval))
176 exit_code, out, err = main.run_atomic_ra_revprop_change(url, revision,
177 propname, skel,
178 want_error)
179 verify.verify_outputs("Unexpected output", out, err,
180 expected_stdout, expected_stderr)
181 verify.verify_exit_code(message, exit_code, expected_exit)
182 return exit_code, out, err
185 def run_and_verify_svnlook(message, expected_stdout,
186 expected_stderr, *varargs):
187 """Like run_and_verify_svnlook2, but the expected exit code is
188 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
190 expected_exit = 0
191 if expected_stderr is not None and expected_stderr != []:
192 expected_exit = 1
193 return run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
194 expected_exit, *varargs)
196 def run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
197 expected_exit, *varargs):
198 """Run svnlook command and check its output and exit code."""
200 exit_code, out, err = main.run_svnlook(*varargs)
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
207 def run_and_verify_svnadmin(message, expected_stdout,
208 expected_stderr, *varargs):
209 """Like run_and_verify_svnadmin2, but the expected exit code is
210 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
212 expected_exit = 0
213 if expected_stderr is not None and expected_stderr != []:
214 expected_exit = 1
215 return run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
216 expected_exit, *varargs)
218 def run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
219 expected_exit, *varargs):
220 """Run svnadmin command and check its output and exit code."""
222 exit_code, out, err = main.run_svnadmin(*varargs)
223 verify.verify_outputs("Unexpected output", out, err,
224 expected_stdout, expected_stderr)
225 verify.verify_exit_code(message, exit_code, expected_exit)
226 return exit_code, out, err
229 def run_and_verify_svnversion(message, wc_dir, repo_url,
230 expected_stdout, expected_stderr):
231 """like run_and_verify_svnversion2, but the expected exit code is
232 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
234 expected_exit = 0
235 if expected_stderr is not None and expected_stderr != []:
236 expected_exit = 1
237 return run_and_verify_svnversion2(message, wc_dir, repo_url,
238 expected_stdout, expected_stderr,
239 expected_exit)
241 def run_and_verify_svnversion2(message, wc_dir, repo_url,
242 expected_stdout, expected_stderr,
243 expected_exit):
244 """Run svnversion command and check its output and exit code."""
246 exit_code, out, err = main.run_svnversion(wc_dir, repo_url)
247 verify.verify_outputs("Unexpected output", out, err,
248 expected_stdout, expected_stderr)
249 verify.verify_exit_code(message, exit_code, expected_exit)
250 return exit_code, out, err
252 def run_and_verify_svn(message, expected_stdout, expected_stderr, *varargs):
253 """like run_and_verify_svn2, but the expected exit code is assumed to
254 be 0 if no output is expected on stderr, and 1 otherwise."""
256 expected_exit = 0
257 if expected_stderr is not None:
258 if isinstance(expected_stderr, verify.ExpectedOutput):
259 if not expected_stderr.matches([]):
260 expected_exit = 1
261 elif expected_stderr != []:
262 expected_exit = 1
263 return run_and_verify_svn2(message, expected_stdout, expected_stderr,
264 expected_exit, *varargs)
266 def run_and_verify_svn2(message, expected_stdout, expected_stderr,
267 expected_exit, *varargs):
268 """Invoke main.run_svn() with *VARARGS. Return exit code as int; stdout,
269 stderr as lists of lines (including line terminators). For both
270 EXPECTED_STDOUT and EXPECTED_STDERR, create an appropriate instance of
271 verify.ExpectedOutput (if necessary):
273 - If it is an array of strings, create a vanilla ExpectedOutput.
275 - If it is a single string, create a RegexOutput that must match every
276 line (for stdout) or any line (for stderr) of the expected output.
278 - If it is already an instance of ExpectedOutput
279 (e.g. UnorderedOutput), leave it alone.
281 ...and invoke compare_and_display_lines() on MESSAGE, a label based
282 on the name of the stream being compared (e.g. STDOUT), the
283 ExpectedOutput instance, and the actual output.
285 If EXPECTED_STDOUT is None, do not check stdout.
286 EXPECTED_STDERR may not be None.
288 If output checks pass, the expected and actual codes are compared.
290 If a comparison fails, a Failure will be raised."""
292 if expected_stderr is None:
293 raise verify.SVNIncorrectDatatype("expected_stderr must not be None")
295 want_err = None
296 if isinstance(expected_stderr, verify.ExpectedOutput):
297 if not expected_stderr.matches([]):
298 want_err = True
299 elif expected_stderr != []:
300 want_err = True
302 exit_code, out, err = main.run_svn(want_err, *varargs)
303 verify.verify_outputs(message, out, err, expected_stdout, expected_stderr)
304 verify.verify_exit_code(message, exit_code, expected_exit)
305 return exit_code, out, err
307 def run_and_verify_load(repo_dir, dump_file_content):
308 "Runs 'svnadmin load' and reports any errors."
309 if not isinstance(dump_file_content, list):
310 raise TypeError("dump_file_content argument should have list type")
311 expected_stderr = []
312 exit_code, output, errput = main.run_command_stdin(
313 main.svnadmin_binary, expected_stderr, 0, 1, dump_file_content,
314 'load', '--force-uuid', '--quiet', repo_dir)
316 verify.verify_outputs("Unexpected stderr output", None, errput,
317 None, expected_stderr)
320 def run_and_verify_dump(repo_dir, deltas=False):
321 "Runs 'svnadmin dump' and reports any errors, returning the dump content."
322 if deltas:
323 exit_code, output, errput = main.run_svnadmin('dump', '--deltas',
324 repo_dir)
325 else:
326 exit_code, output, errput = main.run_svnadmin('dump', repo_dir)
327 verify.verify_outputs("Missing expected output(s)", output, errput,
328 verify.AnyOutput, verify.AnyOutput)
329 return output
332 def run_and_verify_svnrdump(dumpfile_content, expected_stdout,
333 expected_stderr, expected_exit, *varargs):
334 """Runs 'svnrdump dump|load' depending on dumpfile_content and
335 reports any errors."""
336 exit_code, output, err = main.run_svnrdump(dumpfile_content, *varargs)
338 verify.verify_outputs("Unexpected output", output, err,
339 expected_stdout, expected_stderr)
340 verify.verify_exit_code("Unexpected return code", exit_code, expected_exit)
341 return output
343 def load_repo(sbox, dumpfile_path = None, dump_str = None):
344 "Loads the dumpfile into sbox"
345 if not dump_str:
346 dump_str = open(dumpfile_path, "rb").read()
348 # Create a virgin repos and working copy
349 main.safe_rmtree(sbox.repo_dir, 1)
350 main.safe_rmtree(sbox.wc_dir, 1)
351 main.create_repos(sbox.repo_dir)
353 # Load the mergetracking dumpfile into the repos, and check it out the repo
354 run_and_verify_load(sbox.repo_dir, dump_str.splitlines(True))
355 run_and_verify_svn(None, None, [], "co", sbox.repo_url, sbox.wc_dir)
357 return dump_str
360 ######################################################################
361 # Subversion Actions
363 # These are all routines that invoke 'svn' in particular ways, and
364 # then verify the results by comparing expected trees with actual
365 # trees.
369 def run_and_verify_checkout2(do_remove,
370 URL, wc_dir_name, output_tree, disk_tree,
371 singleton_handler_a = None,
372 a_baton = None,
373 singleton_handler_b = None,
374 b_baton = None,
375 *args):
376 """Checkout the URL into a new directory WC_DIR_NAME. *ARGS are any
377 extra optional args to the checkout subcommand.
379 The subcommand output will be verified against OUTPUT_TREE,
380 and the working copy itself will be verified against DISK_TREE.
381 For the latter comparison, SINGLETON_HANDLER_A and
382 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
383 function's doc string for more details. Return if successful, raise
384 on failure.
386 WC_DIR_NAME is deleted if DO_REMOVE is True.
389 if isinstance(output_tree, wc.State):
390 output_tree = output_tree.old_tree()
391 if isinstance(disk_tree, wc.State):
392 disk_tree = disk_tree.old_tree()
394 # Remove dir if it's already there, unless this is a forced checkout.
395 # In that case assume we want to test a forced checkout's toleration
396 # of obstructing paths.
397 if do_remove:
398 main.safe_rmtree(wc_dir_name)
400 # Checkout and make a tree of the output, using l:foo/p:bar
401 ### todo: svn should not be prompting for auth info when using
402 ### repositories with no auth/auth requirements
403 exit_code, output, errput = main.run_svn(None, 'co',
404 URL, wc_dir_name, *args)
405 actual = tree.build_tree_from_checkout(output)
407 # Verify actual output against expected output.
408 try:
409 tree.compare_trees("output", actual, output_tree)
410 except tree.SVNTreeUnequal:
411 print("ACTUAL OUTPUT TREE:")
412 tree.dump_tree_script(actual, wc_dir_name + os.sep)
413 raise
415 # Create a tree by scanning the working copy
416 actual = tree.build_tree_from_wc(wc_dir_name)
418 # Verify expected disk against actual disk.
419 try:
420 tree.compare_trees("disk", actual, disk_tree,
421 singleton_handler_a, a_baton,
422 singleton_handler_b, b_baton)
423 except tree.SVNTreeUnequal:
424 print("ACTUAL DISK TREE:")
425 tree.dump_tree_script(actual, wc_dir_name + os.sep)
426 raise
428 def run_and_verify_checkout(URL, wc_dir_name, output_tree, disk_tree,
429 singleton_handler_a = None,
430 a_baton = None,
431 singleton_handler_b = None,
432 b_baton = None,
433 *args):
434 """Same as run_and_verify_checkout2(), but without the DO_REMOVE arg.
435 WC_DIR_NAME is deleted if present unless the '--force' option is passed
436 in *ARGS."""
439 # Remove dir if it's already there, unless this is a forced checkout.
440 # In that case assume we want to test a forced checkout's toleration
441 # of obstructing paths.
442 return run_and_verify_checkout2(('--force' not in args),
443 URL, wc_dir_name, output_tree, disk_tree,
444 singleton_handler_a,
445 a_baton,
446 singleton_handler_b,
447 b_baton,
448 *args)
451 def run_and_verify_export(URL, export_dir_name, output_tree, disk_tree,
452 *args):
453 """Export the URL into a new directory WC_DIR_NAME.
455 The subcommand output will be verified against OUTPUT_TREE,
456 and the exported copy itself will be verified against DISK_TREE.
457 Return if successful, raise on failure.
459 assert isinstance(output_tree, wc.State)
460 assert isinstance(disk_tree, wc.State)
462 disk_tree = disk_tree.old_tree()
463 output_tree = output_tree.old_tree()
465 # Export and make a tree of the output, using l:foo/p:bar
466 ### todo: svn should not be prompting for auth info when using
467 ### repositories with no auth/auth requirements
468 exit_code, output, errput = main.run_svn(None, 'export',
469 URL, export_dir_name, *args)
470 actual = tree.build_tree_from_checkout(output)
472 # Verify actual output against expected output.
473 try:
474 tree.compare_trees("output", actual, output_tree)
475 except tree.SVNTreeUnequal:
476 print("ACTUAL OUTPUT TREE:")
477 tree.dump_tree_script(actual, export_dir_name + os.sep)
478 raise
480 # Create a tree by scanning the working copy. Don't ignore
481 # the .svn directories so that we generate an error if they
482 # happen to show up.
483 actual = tree.build_tree_from_wc(export_dir_name, ignore_svn=False)
485 # Verify expected disk against actual disk.
486 try:
487 tree.compare_trees("disk", actual, disk_tree)
488 except tree.SVNTreeUnequal:
489 print("ACTUAL DISK TREE:")
490 tree.dump_tree_script(actual, export_dir_name + os.sep)
491 raise
494 # run_and_verify_log_xml
496 class LogEntry:
497 def __init__(self, revision, changed_paths=None, revprops=None):
498 self.revision = revision
499 if changed_paths == None:
500 self.changed_paths = {}
501 else:
502 self.changed_paths = changed_paths
503 if revprops == None:
504 self.revprops = {}
505 else:
506 self.revprops = revprops
508 def assert_changed_paths(self, changed_paths):
509 """Not implemented, so just raises svntest.Failure.
511 raise Failure('NOT IMPLEMENTED')
513 def assert_revprops(self, revprops):
514 """Assert that the dict revprops is the same as this entry's revprops.
516 Raises svntest.Failure if not.
518 if self.revprops != revprops:
519 raise Failure('\n' + '\n'.join(difflib.ndiff(
520 pprint.pformat(revprops).splitlines(),
521 pprint.pformat(self.revprops).splitlines())))
523 class LogParser:
524 def parse(self, data):
525 """Return a list of LogEntrys parsed from the sequence of strings data.
527 This is the only method of interest to callers.
529 try:
530 for i in data:
531 self.parser.Parse(i)
532 self.parser.Parse('', True)
533 except xml.parsers.expat.ExpatError, e:
534 raise verify.SVNUnexpectedStdout('%s\n%s\n' % (e, ''.join(data),))
535 return self.entries
537 def __init__(self):
538 # for expat
539 self.parser = xml.parsers.expat.ParserCreate()
540 self.parser.StartElementHandler = self.handle_start_element
541 self.parser.EndElementHandler = self.handle_end_element
542 self.parser.CharacterDataHandler = self.handle_character_data
543 # Ignore some things.
544 self.ignore_elements('log', 'paths', 'path', 'revprops')
545 self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
546 # internal state
547 self.cdata = []
548 self.property = None
549 # the result
550 self.entries = []
552 def ignore(self, *args, **kwargs):
553 del self.cdata[:]
554 def ignore_tags(self, *args):
555 for tag in args:
556 setattr(self, tag, self.ignore)
557 def ignore_elements(self, *args):
558 for element in args:
559 self.ignore_tags(element + '_start', element + '_end')
561 # expat handlers
562 def handle_start_element(self, name, attrs):
563 getattr(self, name + '_start')(attrs)
564 def handle_end_element(self, name):
565 getattr(self, name + '_end')()
566 def handle_character_data(self, data):
567 self.cdata.append(data)
569 # element handler utilities
570 def use_cdata(self):
571 result = ''.join(self.cdata).strip()
572 del self.cdata[:]
573 return result
574 def svn_prop(self, name):
575 self.entries[-1].revprops['svn:' + name] = self.use_cdata()
577 # element handlers
578 def logentry_start(self, attrs):
579 self.entries.append(LogEntry(int(attrs['revision'])))
580 def author_end(self):
581 self.svn_prop('author')
582 def msg_end(self):
583 self.svn_prop('log')
584 def date_end(self):
585 # svn:date could be anything, so just note its presence.
586 self.cdata[:] = ['']
587 self.svn_prop('date')
588 def property_start(self, attrs):
589 self.property = attrs['name']
590 def property_end(self):
591 self.entries[-1].revprops[self.property] = self.use_cdata()
593 def run_and_verify_log_xml(message=None, expected_paths=None,
594 expected_revprops=None, expected_stdout=None,
595 expected_stderr=None, args=[]):
596 """Call run_and_verify_svn with log --xml and args (optional) as command
597 arguments, and pass along message, expected_stdout, and expected_stderr.
599 If message is None, pass the svn log command as message.
601 expected_paths checking is not yet implemented.
603 expected_revprops is an optional list of dicts, compared to each
604 revision's revprops. The list must be in the same order the log entries
605 come in. Any svn:date revprops in the dicts must be '' in order to
606 match, as the actual dates could be anything.
608 expected_paths and expected_revprops are ignored if expected_stdout or
609 expected_stderr is specified.
611 if message == None:
612 message = ' '.join(args)
614 # We'll parse the output unless the caller specifies expected_stderr or
615 # expected_stdout for run_and_verify_svn.
616 parse = True
617 if expected_stderr == None:
618 expected_stderr = []
619 else:
620 parse = False
621 if expected_stdout != None:
622 parse = False
624 log_args = list(args)
625 if expected_paths != None:
626 log_args.append('-v')
628 (exit_code, stdout, stderr) = run_and_verify_svn(
629 message, expected_stdout, expected_stderr,
630 'log', '--xml', *log_args)
631 if not parse:
632 return
634 entries = LogParser().parse(stdout)
635 for index in range(len(entries)):
636 entry = entries[index]
637 if expected_revprops != None:
638 entry.assert_revprops(expected_revprops[index])
639 if expected_paths != None:
640 entry.assert_changed_paths(expected_paths[index])
643 def verify_update(actual_output,
644 actual_mergeinfo_output,
645 actual_elision_output,
646 wc_dir_name,
647 output_tree,
648 mergeinfo_output_tree,
649 elision_output_tree,
650 disk_tree,
651 status_tree,
652 singleton_handler_a=None,
653 a_baton=None,
654 singleton_handler_b=None,
655 b_baton=None,
656 check_props=False):
657 """Verify update of WC_DIR_NAME.
659 The subcommand output (found in ACTUAL_OUTPUT, ACTUAL_MERGEINFO_OUTPUT,
660 and ACTUAL_ELISION_OUTPUT) will be verified against OUTPUT_TREE,
661 MERGEINFO_OUTPUT_TREE, and ELISION_OUTPUT_TREE respectively (if any of
662 these is provided, they may be None in which case a comparison is not
663 done). The working copy itself will be verified against DISK_TREE (if
664 provided), and the working copy's 'svn status' output will be verified
665 against STATUS_TREE (if provided). (This is a good way to check that
666 revision numbers were bumped.)
668 Return if successful, raise on failure.
670 For the comparison with DISK_TREE, pass SINGLETON_HANDLER_A and
671 SINGLETON_HANDLER_B to tree.compare_trees -- see that function's doc
672 string for more details. If CHECK_PROPS is set, then disk
673 comparison will examine props."""
675 if isinstance(actual_output, wc.State):
676 actual_output = actual_output.old_tree()
677 if isinstance(actual_mergeinfo_output, wc.State):
678 actual_mergeinfo_output = actual_mergeinfo_output.old_tree()
679 if isinstance(actual_elision_output, wc.State):
680 actual_elision_output = actual_elision_output.old_tree()
681 if isinstance(output_tree, wc.State):
682 output_tree = output_tree.old_tree()
683 if isinstance(mergeinfo_output_tree, wc.State):
684 mergeinfo_output_tree = mergeinfo_output_tree.old_tree()
685 if isinstance(elision_output_tree, wc.State):
686 elision_output_tree = elision_output_tree.old_tree()
687 if isinstance(disk_tree, wc.State):
688 disk_tree = disk_tree.old_tree()
689 if isinstance(status_tree, wc.State):
690 status_tree = status_tree.old_tree()
692 # Verify actual output against expected output.
693 if output_tree:
694 try:
695 tree.compare_trees("output", actual_output, output_tree)
696 except tree.SVNTreeUnequal:
697 print("ACTUAL OUTPUT TREE:")
698 tree.dump_tree_script(actual_output, wc_dir_name + os.sep)
699 raise
701 # Verify actual mergeinfo recording output against expected output.
702 if mergeinfo_output_tree:
703 try:
704 tree.compare_trees("mergeinfo_output", actual_mergeinfo_output,
705 mergeinfo_output_tree)
706 except tree.SVNTreeUnequal:
707 print("ACTUAL MERGEINFO OUTPUT TREE:")
708 tree.dump_tree_script(actual_mergeinfo_output,
709 wc_dir_name + os.sep)
710 raise
712 # Verify actual mergeinfo elision output against expected output.
713 if elision_output_tree:
714 try:
715 tree.compare_trees("elision_output", actual_elision_output,
716 elision_output_tree)
717 except tree.SVNTreeUnequal:
718 print("ACTUAL ELISION OUTPUT TREE:")
719 tree.dump_tree_script(actual_elision_output,
720 wc_dir_name + os.sep)
721 raise
723 # Create a tree by scanning the working copy, and verify it
724 if disk_tree:
725 actual_disk = tree.build_tree_from_wc(wc_dir_name, check_props)
726 try:
727 tree.compare_trees("disk", actual_disk, disk_tree,
728 singleton_handler_a, a_baton,
729 singleton_handler_b, b_baton)
730 except tree.SVNTreeUnequal:
731 print("ACTUAL DISK TREE:")
732 tree.dump_tree_script(actual_disk)
733 raise
735 # Verify via 'status' command too, if possible.
736 if status_tree:
737 run_and_verify_status(wc_dir_name, status_tree)
740 def verify_disk(wc_dir_name, disk_tree, check_props=False):
741 """Verify WC_DIR_NAME against DISK_TREE. If CHECK_PROPS is set,
742 the comparison will examin props. Returns if successful, raises on
743 failure."""
744 verify_update(None, None, None, wc_dir_name, None, None, None, disk_tree,
745 None, check_props=check_props)
749 def run_and_verify_update(wc_dir_name,
750 output_tree, disk_tree, status_tree,
751 error_re_string = None,
752 singleton_handler_a = None,
753 a_baton = None,
754 singleton_handler_b = None,
755 b_baton = None,
756 check_props = False,
757 *args):
759 """Update WC_DIR_NAME. *ARGS are any extra optional args to the
760 update subcommand. NOTE: If *ARGS is specified at all, explicit
761 target paths must be passed in *ARGS as well (or a default `.' will
762 be chosen by the 'svn' binary). This allows the caller to update
763 many items in a single working copy dir, but still verify the entire
764 working copy dir.
766 If ERROR_RE_STRING, the update must exit with error, and the error
767 message must match regular expression ERROR_RE_STRING.
769 Else if ERROR_RE_STRING is None, then:
771 If OUTPUT_TREE is not None, the subcommand output will be verified
772 against OUTPUT_TREE. If DISK_TREE is not None, the working copy
773 itself will be verified against DISK_TREE. If STATUS_TREE is not
774 None, the 'svn status' output will be verified against STATUS_TREE.
775 (This is a good way to check that revision numbers were bumped.)
777 For the DISK_TREE verification, SINGLETON_HANDLER_A and
778 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
779 function's doc string for more details.
781 If CHECK_PROPS is set, then disk comparison will examine props.
783 Return if successful, raise on failure."""
785 # Update and make a tree of the output.
786 if len(args):
787 exit_code, output, errput = main.run_svn(error_re_string, 'up', *args)
788 else:
789 exit_code, output, errput = main.run_svn(error_re_string,
790 'up', wc_dir_name,
791 *args)
793 if error_re_string:
794 rm = re.compile(error_re_string)
795 for line in errput:
796 match = rm.search(line)
797 if match:
798 return
799 raise main.SVNUnmatchedError
801 actual = wc.State.from_checkout(output)
802 verify_update(actual, None, None, wc_dir_name,
803 output_tree, None, None, disk_tree, status_tree,
804 singleton_handler_a, a_baton,
805 singleton_handler_b, b_baton,
806 check_props)
809 def run_and_parse_info(*args):
810 """Run 'svn info' and parse its output into a list of dicts,
811 one dict per target."""
813 # the returned array
814 all_infos = []
816 # per-target variables
817 iter_info = {}
818 prev_key = None
819 lock_comment_lines = 0
820 lock_comments = []
822 exit_code, output, errput = main.run_svn(None, 'info', *args)
824 for line in output:
825 line = line[:-1] # trim '\n'
827 if lock_comment_lines > 0:
828 # mop up any lock comment lines
829 lock_comments.append(line)
830 lock_comment_lines = lock_comment_lines - 1
831 if lock_comment_lines == 0:
832 iter_info[prev_key] = lock_comments
833 elif len(line) == 0:
834 # separator line between items
835 all_infos.append(iter_info)
836 iter_info = {}
837 prev_key = None
838 lock_comment_lines = 0
839 lock_comments = []
840 elif line[0].isspace():
841 # continuation line (for tree conflicts)
842 iter_info[prev_key] += line[1:]
843 else:
844 # normal line
845 key, value = line.split(':', 1)
847 if re.search(' \(\d+ lines?\)$', key):
848 # numbered continuation lines
849 match = re.match('^(.*) \((\d+) lines?\)$', key)
850 key = match.group(1)
851 lock_comment_lines = int(match.group(2))
852 elif len(value) > 1:
853 # normal normal line
854 iter_info[key] = value[1:]
855 else:
856 ### originally added for "Tree conflict:\n" lines;
857 ### tree-conflicts output format has changed since then
858 # continuation lines are implicit (prefixed by whitespace)
859 iter_info[key] = ''
860 prev_key = key
862 return all_infos
864 def run_and_verify_info(expected_infos, *args):
865 """Run 'svn info' with the arguments in *ARGS and verify the results
866 against expected_infos. The latter should be a list of dicts (in the
867 same order as the targets).
869 In the dicts, each key is the before-the-colon part of the 'svn info' output,
870 and each value is either None (meaning that the key should *not* appear in
871 the 'svn info' output) or a regex matching the output value. Output lines
872 not matching a key in the dict are ignored.
874 Return if successful, raise on failure."""
876 actual_infos = run_and_parse_info(*args)
878 try:
879 # zip() won't complain, so check this manually
880 if len(actual_infos) != len(expected_infos):
881 raise verify.SVNUnexpectedStdout(
882 "Expected %d infos, found %d infos"
883 % (len(expected_infos), len(actual_infos)))
885 for actual, expected in zip(actual_infos, expected_infos):
886 # compare dicts
887 for key, value in expected.items():
888 assert ':' not in key # caller passed impossible expectations?
889 if value is None and key in actual:
890 raise main.SVNLineUnequal("Found unexpected key '%s' with value '%s'"
891 % (key, actual[key]))
892 if value is not None and key not in actual:
893 raise main.SVNLineUnequal("Expected key '%s' (with value '%s') "
894 "not found" % (key, value))
895 if value is not None and not re.search(value, actual[key]):
896 raise verify.SVNUnexpectedStdout("Values of key '%s' don't match:\n"
897 " Expected: '%s' (regex)\n"
898 " Found: '%s' (string)\n"
899 % (key, value, actual[key]))
901 except:
902 sys.stderr.write("Bad 'svn info' output:\n"
903 " Received: %s\n"
904 " Expected: %s\n"
905 % (actual_infos, expected_infos))
906 raise
908 def run_and_verify_merge(dir, rev1, rev2, url1, url2,
909 output_tree,
910 mergeinfo_output_tree,
911 elision_output_tree,
912 disk_tree, status_tree, skip_tree,
913 error_re_string = None,
914 singleton_handler_a = None,
915 a_baton = None,
916 singleton_handler_b = None,
917 b_baton = None,
918 check_props = False,
919 dry_run = True,
920 *args):
921 """Run 'svn merge URL1@REV1 URL2@REV2 DIR' if URL2 is not None
922 (for a three-way merge between URLs and WC).
924 If URL2 is None, run 'svn merge -rREV1:REV2 URL1 DIR'. If both REV1
925 and REV2 are None, leave off the '-r' argument.
927 If ERROR_RE_STRING, the merge must exit with error, and the error
928 message must match regular expression ERROR_RE_STRING.
930 Else if ERROR_RE_STRING is None, then:
932 The subcommand output will be verified against OUTPUT_TREE. Output
933 related to mergeinfo notifications will be verified against
934 MERGEINFO_OUTPUT_TREE if that is not None. Output related to mergeinfo
935 elision will be verified against ELISION_OUTPUT_TREE if that is not None.
936 The working copy itself will be verified against DISK_TREE. If optional
937 STATUS_TREE is given, then 'svn status' output will be compared. The
938 'skipped' merge output will be compared to SKIP_TREE.
940 For the DISK_TREE verification, SINGLETON_HANDLER_A and
941 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
942 function's doc string for more details.
944 If CHECK_PROPS is set, then disk comparison will examine props.
946 If DRY_RUN is set then a --dry-run merge will be carried out first and
947 the output compared with that of the full merge.
949 Return if successful, raise on failure.
951 *ARGS are any extra optional args to the merge subcommand.
952 NOTE: If *ARGS is specified at all, an explicit target path must be passed
953 in *ARGS as well. This allows the caller to merge into single items inside
954 the working copy, but still verify the entire working copy dir. """
956 merge_command = [ "merge" ]
957 if url2:
958 merge_command.extend((url1 + "@" + str(rev1), url2 + "@" + str(rev2)))
959 else:
960 if not (rev1 is None and rev2 is None):
961 merge_command.append("-r" + str(rev1) + ":" + str(rev2))
962 merge_command.append(url1)
963 if len(args) == 0:
964 merge_command.append(dir)
965 merge_command = tuple(merge_command)
967 if dry_run:
968 pre_disk = tree.build_tree_from_wc(dir)
969 dry_run_command = merge_command + ('--dry-run',)
970 dry_run_command = dry_run_command + args
971 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
972 *dry_run_command)
973 post_disk = tree.build_tree_from_wc(dir)
974 try:
975 tree.compare_trees("disk", post_disk, pre_disk)
976 except tree.SVNTreeError:
977 print("=============================================================")
978 print("Dry-run merge altered working copy")
979 print("=============================================================")
980 raise
983 # Update and make a tree of the output.
984 merge_command = merge_command + args
985 exit_code, out, err = main.run_svn(error_re_string, *merge_command)
987 if error_re_string:
988 if not error_re_string.startswith(".*"):
989 error_re_string = ".*(" + error_re_string + ")"
990 expected_err = verify.RegexOutput(error_re_string, match_all=False)
991 verify.verify_outputs(None, None, err, None, expected_err)
992 return
993 elif err:
994 raise verify.SVNUnexpectedStderr(err)
996 # Split the output into that related to application of the actual diff
997 # and that related to the recording of mergeinfo describing the merge.
998 merge_diff_out = []
999 mergeinfo_notification_out = []
1000 mergeinfo_elision_out = []
1001 mergeinfo_notifications = False
1002 elision_notifications = False
1003 for line in out:
1004 if line.startswith('--- Recording'):
1005 mergeinfo_notifications = True
1006 elision_notifications = False
1007 elif line.startswith('--- Eliding'):
1008 mergeinfo_notifications = False
1009 elision_notifications = True
1010 elif line.startswith('--- Merging') or \
1011 line.startswith('--- Reverse-merging') or \
1012 line.startswith('Summary of conflicts') or \
1013 line.startswith('Skipped missing target'):
1014 mergeinfo_notifications = False
1015 elision_notifications = False
1017 if mergeinfo_notifications:
1018 mergeinfo_notification_out.append(line)
1019 elif elision_notifications:
1020 mergeinfo_elision_out.append(line)
1021 else:
1022 merge_diff_out.append(line)
1024 if dry_run and merge_diff_out != out_dry:
1025 # Due to the way ra_serf works, it's possible that the dry-run and
1026 # real merge operations did the same thing, but the output came in
1027 # a different order. Let's see if maybe that's the case.
1029 # NOTE: Would be nice to limit this dance to serf tests only, but...
1030 out_copy = merge_diff_out[:]
1031 out_dry_copy = out_dry[:]
1032 out_copy.sort()
1033 out_dry_copy.sort()
1034 if out_copy != out_dry_copy:
1035 print("=============================================================")
1036 print("Merge outputs differ")
1037 print("The dry-run merge output:")
1038 for x in out_dry:
1039 sys.stdout.write(x)
1040 print("The full merge output:")
1041 for x in out:
1042 sys.stdout.write(x)
1043 print("=============================================================")
1044 raise main.SVNUnmatchedError
1046 def missing_skip(a, b):
1047 print("=============================================================")
1048 print("Merge failed to skip: " + a.path)
1049 print("=============================================================")
1050 raise Failure
1051 def extra_skip(a, b):
1052 print("=============================================================")
1053 print("Merge unexpectedly skipped: " + a.path)
1054 print("=============================================================")
1055 raise Failure
1057 myskiptree = tree.build_tree_from_skipped(out)
1058 if isinstance(skip_tree, wc.State):
1059 skip_tree = skip_tree.old_tree()
1060 try:
1061 tree.compare_trees("skip", myskiptree, skip_tree,
1062 extra_skip, None, missing_skip, None)
1063 except tree.SVNTreeUnequal:
1064 print("ACTUAL SKIP TREE:")
1065 tree.dump_tree_script(myskiptree, dir + os.sep)
1066 raise
1068 actual_diff = svntest.wc.State.from_checkout(merge_diff_out, False)
1069 actual_mergeinfo = svntest.wc.State.from_checkout(mergeinfo_notification_out,
1070 False)
1071 actual_elision = svntest.wc.State.from_checkout(mergeinfo_elision_out,
1072 False)
1073 verify_update(actual_diff, actual_mergeinfo, actual_elision, dir,
1074 output_tree, mergeinfo_output_tree, elision_output_tree,
1075 disk_tree, status_tree,
1076 singleton_handler_a, a_baton,
1077 singleton_handler_b, b_baton,
1078 check_props)
1081 def run_and_verify_patch(dir, patch_path,
1082 output_tree, disk_tree, status_tree, skip_tree,
1083 error_re_string=None,
1084 check_props=False,
1085 dry_run=True,
1086 *args):
1087 """Run 'svn patch patch_path DIR'.
1089 If ERROR_RE_STRING, 'svn patch' must exit with error, and the error
1090 message must match regular expression ERROR_RE_STRING.
1092 Else if ERROR_RE_STRING is None, then:
1094 The subcommand output will be verified against OUTPUT_TREE, and the
1095 working copy itself will be verified against DISK_TREE. If optional
1096 STATUS_TREE is given, then 'svn status' output will be compared.
1097 The 'skipped' merge output will be compared to SKIP_TREE.
1099 If CHECK_PROPS is set, then disk comparison will examine props.
1101 If DRY_RUN is set then a --dry-run patch will be carried out first and
1102 the output compared with that of the full patch application.
1104 Returns if successful, raises on failure."""
1106 patch_command = [ "patch" ]
1107 patch_command.append(patch_path)
1108 patch_command.append(dir)
1109 patch_command = tuple(patch_command)
1111 if dry_run:
1112 pre_disk = tree.build_tree_from_wc(dir)
1113 dry_run_command = patch_command + ('--dry-run',)
1114 dry_run_command = dry_run_command + args
1115 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
1116 *dry_run_command)
1117 post_disk = tree.build_tree_from_wc(dir)
1118 try:
1119 tree.compare_trees("disk", post_disk, pre_disk)
1120 except tree.SVNTreeError:
1121 print("=============================================================")
1122 print("'svn patch --dry-run' altered working copy")
1123 print("=============================================================")
1124 raise
1126 # Update and make a tree of the output.
1127 patch_command = patch_command + args
1128 exit_code, out, err = main.run_svn(True, *patch_command)
1130 if error_re_string:
1131 rm = re.compile(error_re_string)
1132 match = None
1133 for line in err:
1134 match = rm.search(line)
1135 if match:
1136 break
1137 if not match:
1138 raise main.SVNUnmatchedError
1139 elif err:
1140 print("UNEXPECTED STDERR:")
1141 for x in err:
1142 sys.stdout.write(x)
1143 raise verify.SVNUnexpectedStderr
1145 if dry_run and out != out_dry:
1146 print("=============================================================")
1147 print("Outputs differ")
1148 print("'svn patch --dry-run' output:")
1149 for x in out_dry:
1150 sys.stdout.write(x)
1151 print("'svn patch' output:")
1152 for x in out:
1153 sys.stdout.write(x)
1154 print("=============================================================")
1155 raise main.SVNUnmatchedError
1157 def missing_skip(a, b):
1158 print("=============================================================")
1159 print("'svn patch' failed to skip: " + a.path)
1160 print("=============================================================")
1161 raise Failure
1162 def extra_skip(a, b):
1163 print("=============================================================")
1164 print("'svn patch' unexpectedly skipped: " + a.path)
1165 print("=============================================================")
1166 raise Failure
1168 myskiptree = tree.build_tree_from_skipped(out)
1169 if isinstance(skip_tree, wc.State):
1170 skip_tree = skip_tree.old_tree()
1171 tree.compare_trees("skip", myskiptree, skip_tree,
1172 extra_skip, None, missing_skip, None)
1174 mytree = tree.build_tree_from_checkout(out, 0)
1176 # when the expected output is a list, we want a line-by-line
1177 # comparison to happen instead of a tree comparison
1178 if isinstance(output_tree, list):
1179 verify.verify_outputs(None, out, err, output_tree, error_re_string)
1180 output_tree = None
1182 verify_update(mytree, None, None, dir,
1183 output_tree, None, None, disk_tree, status_tree,
1184 check_props=check_props)
1187 def run_and_verify_mergeinfo(error_re_string = None,
1188 expected_output = [],
1189 *args):
1190 """Run 'svn mergeinfo ARGS', and compare the result against
1191 EXPECTED_OUTPUT, a list of string representations of revisions
1192 expected in the output. Raise an exception if an unexpected
1193 output is encountered."""
1195 mergeinfo_command = ["mergeinfo"]
1196 mergeinfo_command.extend(args)
1197 exit_code, out, err = main.run_svn(error_re_string, *mergeinfo_command)
1199 if error_re_string:
1200 if not error_re_string.startswith(".*"):
1201 error_re_string = ".*(" + error_re_string + ")"
1202 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1203 verify.verify_outputs(None, None, err, None, expected_err)
1204 return
1206 out = sorted([_f for _f in [x.rstrip()[1:] for x in out] if _f])
1207 expected_output.sort()
1208 extra_out = []
1209 if out != expected_output:
1210 exp_hash = dict.fromkeys(expected_output)
1211 for rev in out:
1212 if rev in exp_hash:
1213 del(exp_hash[rev])
1214 else:
1215 extra_out.append(rev)
1216 extra_exp = list(exp_hash.keys())
1217 raise Exception("Unexpected 'svn mergeinfo' output:\n"
1218 " expected but not found: %s\n"
1219 " found but not expected: %s"
1220 % (', '.join([str(x) for x in extra_exp]),
1221 ', '.join([str(x) for x in extra_out])))
1224 def run_and_verify_switch(wc_dir_name,
1225 wc_target,
1226 switch_url,
1227 output_tree, disk_tree, status_tree,
1228 error_re_string = None,
1229 singleton_handler_a = None,
1230 a_baton = None,
1231 singleton_handler_b = None,
1232 b_baton = None,
1233 check_props = False,
1234 *args):
1236 """Switch WC_TARGET (in working copy dir WC_DIR_NAME) to SWITCH_URL.
1238 If ERROR_RE_STRING, the switch must exit with error, and the error
1239 message must match regular expression ERROR_RE_STRING.
1241 Else if ERROR_RE_STRING is None, then:
1243 The subcommand output will be verified against OUTPUT_TREE, and the
1244 working copy itself will be verified against DISK_TREE. If optional
1245 STATUS_TREE is given, then 'svn status' output will be
1246 compared. (This is a good way to check that revision numbers were
1247 bumped.)
1249 For the DISK_TREE verification, SINGLETON_HANDLER_A and
1250 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
1251 function's doc string for more details.
1253 If CHECK_PROPS is set, then disk comparison will examine props.
1255 Return if successful, raise on failure."""
1257 # Update and make a tree of the output.
1258 exit_code, output, errput = main.run_svn(error_re_string, 'switch',
1259 switch_url, wc_target, *args)
1261 if error_re_string:
1262 if not error_re_string.startswith(".*"):
1263 error_re_string = ".*(" + error_re_string + ")"
1264 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1265 verify.verify_outputs(None, None, errput, None, expected_err)
1266 return
1267 elif errput:
1268 raise verify.SVNUnexpectedStderr(err)
1270 actual = wc.State.from_checkout(output)
1272 verify_update(actual, None, None, wc_dir_name,
1273 output_tree, None, None, disk_tree, status_tree,
1274 singleton_handler_a, a_baton,
1275 singleton_handler_b, b_baton,
1276 check_props)
1278 def process_output_for_commit(output):
1279 """Helper for run_and_verify_commit(), also used in the factory."""
1280 # Remove the final output line, and verify that the commit succeeded.
1281 lastline = ""
1282 if len(output):
1283 lastline = output.pop().strip()
1285 cm = re.compile("(Committed|Imported) revision [0-9]+.")
1286 match = cm.search(lastline)
1287 if not match:
1288 print("ERROR: commit did not succeed.")
1289 print("The final line from 'svn ci' was:")
1290 print(lastline)
1291 raise main.SVNCommitFailure
1293 # The new 'final' line in the output is either a regular line that
1294 # mentions {Adding, Deleting, Sending, ...}, or it could be a line
1295 # that says "Transmitting file data ...". If the latter case, we
1296 # want to remove the line from the output; it should be ignored when
1297 # building a tree.
1298 if len(output):
1299 lastline = output.pop()
1301 tm = re.compile("Transmitting file data.+")
1302 match = tm.search(lastline)
1303 if not match:
1304 # whoops, it was important output, put it back.
1305 output.append(lastline)
1307 return output
1310 def run_and_verify_commit(wc_dir_name, output_tree, status_tree,
1311 error_re_string = None,
1312 *args):
1313 """Commit and verify results within working copy WC_DIR_NAME,
1314 sending ARGS to the commit subcommand.
1316 The subcommand output will be verified against OUTPUT_TREE. If
1317 optional STATUS_TREE is given, then 'svn status' output will
1318 be compared. (This is a good way to check that revision numbers
1319 were bumped.)
1321 If ERROR_RE_STRING is None, the commit must not exit with error. If
1322 ERROR_RE_STRING is a string, the commit must exit with error, and
1323 the error message must match regular expression ERROR_RE_STRING.
1325 Return if successful, raise on failure."""
1327 if isinstance(output_tree, wc.State):
1328 output_tree = output_tree.old_tree()
1329 if isinstance(status_tree, wc.State):
1330 status_tree = status_tree.old_tree()
1332 # Commit.
1333 exit_code, output, errput = main.run_svn(error_re_string, 'ci',
1334 '-m', 'log msg',
1335 *args)
1337 if error_re_string:
1338 if not error_re_string.startswith(".*"):
1339 error_re_string = ".*(" + error_re_string + ")"
1340 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1341 verify.verify_outputs(None, None, errput, None, expected_err)
1342 return
1344 # Else not expecting error:
1346 # Convert the output into a tree.
1347 output = process_output_for_commit(output)
1348 actual = tree.build_tree_from_commit(output)
1350 # Verify actual output against expected output.
1351 try:
1352 tree.compare_trees("output", actual, output_tree)
1353 except tree.SVNTreeError:
1354 verify.display_trees("Output of commit is unexpected",
1355 "OUTPUT TREE", output_tree, actual)
1356 print("ACTUAL OUTPUT TREE:")
1357 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1358 raise
1360 # Verify via 'status' command too, if possible.
1361 if status_tree:
1362 run_and_verify_status(wc_dir_name, status_tree)
1365 # This function always passes '-q' to the status command, which
1366 # suppresses the printing of any unversioned or nonexistent items.
1367 def run_and_verify_status(wc_dir_name, output_tree,
1368 singleton_handler_a = None,
1369 a_baton = None,
1370 singleton_handler_b = None,
1371 b_baton = None):
1372 """Run 'status' on WC_DIR_NAME and compare it with the
1373 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1374 be passed to tree.compare_trees - see that function's doc string for
1375 more details.
1376 Returns on success, raises on failure."""
1378 if isinstance(output_tree, wc.State):
1379 output_state = output_tree
1380 output_tree = output_tree.old_tree()
1381 else:
1382 output_state = None
1384 exit_code, output, errput = main.run_svn(None, 'status', '-v', '-u', '-q',
1385 wc_dir_name)
1387 actual = tree.build_tree_from_status(output)
1389 # Verify actual output against expected output.
1390 try:
1391 tree.compare_trees("status", actual, output_tree,
1392 singleton_handler_a, a_baton,
1393 singleton_handler_b, b_baton)
1394 except tree.SVNTreeError:
1395 verify.display_trees(None, 'STATUS OUTPUT TREE', output_tree, actual)
1396 print("ACTUAL STATUS TREE:")
1397 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1398 raise
1400 # if we have an output State, and we can/are-allowed to create an
1401 # entries-based State, then compare the two.
1402 if output_state:
1403 entries_state = wc.State.from_entries(wc_dir_name)
1404 if entries_state:
1405 tweaked = output_state.copy()
1406 tweaked.tweak_for_entries_compare()
1407 try:
1408 tweaked.compare_and_display('entries', entries_state)
1409 except tree.SVNTreeUnequal:
1410 ### do something more
1411 raise
1414 # A variant of previous func, but doesn't pass '-q'. This allows us
1415 # to verify unversioned or nonexistent items in the list.
1416 def run_and_verify_unquiet_status(wc_dir_name, status_tree):
1417 """Run 'status' on WC_DIR_NAME and compare it with the
1418 expected STATUS_TREE.
1419 Returns on success, raises on failure."""
1421 if isinstance(status_tree, wc.State):
1422 status_tree = status_tree.old_tree()
1424 exit_code, output, errput = main.run_svn(None, 'status', '-v',
1425 '-u', wc_dir_name)
1427 actual = tree.build_tree_from_status(output)
1429 # Verify actual output against expected output.
1430 try:
1431 tree.compare_trees("UNQUIET STATUS", actual, status_tree)
1432 except tree.SVNTreeError:
1433 print("ACTUAL UNQUIET STATUS TREE:")
1434 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1435 raise
1437 def run_and_verify_diff_summarize_xml(error_re_string = [],
1438 expected_prefix = None,
1439 expected_paths = [],
1440 expected_items = [],
1441 expected_props = [],
1442 expected_kinds = [],
1443 *args):
1444 """Run 'diff --summarize --xml' with the arguments *ARGS, which should
1445 contain all arguments beyond for your 'diff --summarize --xml' omitting
1446 said arguments. EXPECTED_PREFIX will store a "common" path prefix
1447 expected to be at the beginning of each summarized path. If
1448 EXPECTED_PREFIX is None, then EXPECTED_PATHS will need to be exactly
1449 as 'svn diff --summarize --xml' will output. If ERROR_RE_STRING, the
1450 command must exit with error, and the error message must match regular
1451 expression ERROR_RE_STRING.
1453 Else if ERROR_RE_STRING is None, the subcommand output will be parsed
1454 into an XML document and will then be verified by comparing the parsed
1455 output to the contents in the EXPECTED_PATHS, EXPECTED_ITEMS,
1456 EXPECTED_PROPS and EXPECTED_KINDS. Returns on success, raises
1457 on failure."""
1459 exit_code, output, errput = run_and_verify_svn(None, None, error_re_string,
1460 'diff', '--summarize',
1461 '--xml', *args)
1464 # Return if errors are present since they were expected
1465 if len(errput) > 0:
1466 return
1468 doc = parseString(''.join(output))
1469 paths = doc.getElementsByTagName("path")
1470 items = expected_items
1471 kinds = expected_kinds
1473 for path in paths:
1474 modified_path = path.childNodes[0].data
1476 if (expected_prefix is not None
1477 and modified_path.find(expected_prefix) == 0):
1478 modified_path = modified_path.replace(expected_prefix, '')[1:].strip()
1480 # Workaround single-object diff
1481 if len(modified_path) == 0:
1482 modified_path = path.childNodes[0].data.split(os.sep)[-1]
1484 # From here on, we use '/' as path separator.
1485 if os.sep != "/":
1486 modified_path = modified_path.replace(os.sep, "/")
1488 if modified_path not in expected_paths:
1489 print("ERROR: %s not expected in the changed paths." % modified_path)
1490 raise Failure
1492 index = expected_paths.index(modified_path)
1493 expected_item = items[index]
1494 expected_kind = kinds[index]
1495 expected_prop = expected_props[index]
1496 actual_item = path.getAttribute('item')
1497 actual_kind = path.getAttribute('kind')
1498 actual_prop = path.getAttribute('props')
1500 if expected_item != actual_item:
1501 print("ERROR: expected: %s actual: %s" % (expected_item, actual_item))
1502 raise Failure
1504 if expected_kind != actual_kind:
1505 print("ERROR: expected: %s actual: %s" % (expected_kind, actual_kind))
1506 raise Failure
1508 if expected_prop != actual_prop:
1509 print("ERROR: expected: %s actual: %s" % (expected_prop, actual_prop))
1510 raise Failure
1512 def run_and_verify_diff_summarize(output_tree, *args):
1513 """Run 'diff --summarize' with the arguments *ARGS.
1515 The subcommand output will be verified against OUTPUT_TREE. Returns
1516 on success, raises on failure.
1519 if isinstance(output_tree, wc.State):
1520 output_tree = output_tree.old_tree()
1522 exit_code, output, errput = main.run_svn(None, 'diff', '--summarize',
1523 *args)
1525 actual = tree.build_tree_from_diff_summarize(output)
1527 # Verify actual output against expected output.
1528 try:
1529 tree.compare_trees("output", actual, output_tree)
1530 except tree.SVNTreeError:
1531 verify.display_trees(None, 'DIFF OUTPUT TREE', output_tree, actual)
1532 print("ACTUAL DIFF OUTPUT TREE:")
1533 tree.dump_tree_script(actual)
1534 raise
1536 def run_and_validate_lock(path, username):
1537 """`svn lock' the given path and validate the contents of the lock.
1538 Use the given username. This is important because locks are
1539 user specific."""
1541 comment = "Locking path:%s." % path
1543 # lock the path
1544 run_and_verify_svn(None, ".*locked by user", [], 'lock',
1545 '--username', username,
1546 '-m', comment, path)
1548 # Run info and check that we get the lock fields.
1549 exit_code, output, err = run_and_verify_svn(None, None, [],
1550 'info','-R',
1551 path)
1553 ### TODO: Leverage RegexOuput([...], match_all=True) here.
1554 # prepare the regexs to compare against
1555 token_re = re.compile(".*?Lock Token: opaquelocktoken:.*?", re.DOTALL)
1556 author_re = re.compile(".*?Lock Owner: %s\n.*?" % username, re.DOTALL)
1557 created_re = re.compile(".*?Lock Created:.*?", re.DOTALL)
1558 comment_re = re.compile(".*?%s\n.*?" % re.escape(comment), re.DOTALL)
1559 # join all output lines into one
1560 output = "".join(output)
1561 # Fail even if one regex does not match
1562 if ( not (token_re.match(output) and
1563 author_re.match(output) and
1564 created_re.match(output) and
1565 comment_re.match(output))):
1566 raise Failure
1568 def _run_and_verify_resolve(cmd, expected_paths, *args):
1569 """Run "svn CMD" (where CMD is 'resolve' or 'resolved') with arguments
1570 ARGS, and verify that it resolves the paths in EXPECTED_PATHS and no others.
1571 If no ARGS are specified, use the elements of EXPECTED_PATHS as the
1572 arguments."""
1573 # TODO: verify that the status of PATHS changes accordingly.
1574 if len(args) == 0:
1575 args = expected_paths
1576 expected_output = verify.UnorderedOutput([
1577 "Resolved conflicted state of '" + path + "'\n" for path in
1578 expected_paths])
1579 run_and_verify_svn(None, expected_output, [],
1580 cmd, *args)
1582 def run_and_verify_resolve(expected_paths, *args):
1583 """Run "svn resolve" with arguments ARGS, and verify that it resolves the
1584 paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the
1585 elements of EXPECTED_PATHS as the arguments."""
1586 _run_and_verify_resolve('resolve', expected_paths, *args)
1588 def run_and_verify_resolved(expected_paths, *args):
1589 """Run "svn resolved" with arguments ARGS, and verify that it resolves the
1590 paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the
1591 elements of EXPECTED_PATHS as the arguments."""
1592 _run_and_verify_resolve('resolved', expected_paths, *args)
1595 ######################################################################
1596 # Other general utilities
1599 # This allows a test to *quickly* bootstrap itself.
1600 def make_repo_and_wc(sbox, create_wc = True, read_only = False):
1601 """Create a fresh 'Greek Tree' repository and check out a WC from it.
1603 If READ_ONLY is False, a dedicated repository will be created, at the path
1604 SBOX.repo_dir. If READ_ONLY is True, the pristine repository will be used.
1605 In either case, SBOX.repo_url is assumed to point to the repository that
1606 will be used.
1608 If create_wc is True, a dedicated working copy will be checked out from
1609 the repository, at the path SBOX.wc_dir.
1611 Returns on success, raises on failure."""
1613 # Create (or copy afresh) a new repos with a greek tree in it.
1614 if not read_only:
1615 guarantee_greek_repository(sbox.repo_dir)
1617 if create_wc:
1618 # Generate the expected output tree.
1619 expected_output = main.greek_state.copy()
1620 expected_output.wc_dir = sbox.wc_dir
1621 expected_output.tweak(status='A ', contents=None)
1623 # Generate an expected wc tree.
1624 expected_wc = main.greek_state
1626 # Do a checkout, and verify the resulting output and disk contents.
1627 run_and_verify_checkout(sbox.repo_url,
1628 sbox.wc_dir,
1629 expected_output,
1630 expected_wc)
1631 else:
1632 # just make sure the parent folder of our working copy is created
1633 try:
1634 os.mkdir(main.general_wc_dir)
1635 except OSError, err:
1636 if err.errno != errno.EEXIST:
1637 raise
1639 # Duplicate a working copy or other dir.
1640 def duplicate_dir(wc_name, wc_copy_name):
1641 """Copy the working copy WC_NAME to WC_COPY_NAME. Overwrite any
1642 existing tree at that location."""
1644 main.safe_rmtree(wc_copy_name)
1645 shutil.copytree(wc_name, wc_copy_name)
1649 def get_virginal_state(wc_dir, rev):
1650 "Return a virginal greek tree state for a WC and repos at revision REV."
1652 rev = str(rev) ### maybe switch rev to an integer?
1654 # copy the greek tree, shift it to the new wc_dir, insert a root elem,
1655 # then tweak all values
1656 state = main.greek_state.copy()
1657 state.wc_dir = wc_dir
1658 state.desc[''] = wc.StateItem()
1659 state.tweak(contents=None, status=' ', wc_rev=rev)
1661 return state
1663 # Cheap administrative directory locking
1664 def lock_admin_dir(wc_dir):
1665 "Lock a SVN administrative directory"
1666 db, root_path, relpath = wc.open_wc_db(wc_dir)
1668 db.execute('insert into wc_lock (wc_id, local_dir_relpath, locked_levels) '
1669 + 'values (?, ?, ?)',
1670 (1, relpath, 0))
1671 db.commit()
1672 db.close()
1674 def get_wc_uuid(wc_dir):
1675 "Return the UUID of the working copy at WC_DIR."
1676 return run_and_parse_info(wc_dir)[0]['Repository UUID']
1678 def get_wc_base_rev(wc_dir):
1679 "Return the BASE revision of the working copy at WC_DIR."
1680 return run_and_parse_info(wc_dir)[0]['Revision']
1682 def hook_failure_message(hook_name):
1683 """Return the error message that the client prints for failure of the
1684 specified hook HOOK_NAME. The wording changed with Subversion 1.5."""
1685 if svntest.main.options.server_minor_version < 5:
1686 return "'%s' hook failed with error output:\n" % hook_name
1687 else:
1688 if hook_name in ["start-commit", "pre-commit"]:
1689 action = "Commit"
1690 elif hook_name == "pre-revprop-change":
1691 action = "Revprop change"
1692 elif hook_name == "pre-lock":
1693 action = "Lock"
1694 elif hook_name == "pre-unlock":
1695 action = "Unlock"
1696 else:
1697 action = None
1698 if action is None:
1699 message = "%s hook failed (exit code 1)" % (hook_name,)
1700 else:
1701 message = "%s blocked by %s hook (exit code 1)" % (action, hook_name)
1702 return message + " with output:\n"
1704 def create_failing_hook(repo_dir, hook_name, text):
1705 """Create a HOOK_NAME hook in the repository at REPO_DIR that prints
1706 TEXT to stderr and exits with an error."""
1708 hook_path = os.path.join(repo_dir, 'hooks', hook_name)
1709 # Embed the text carefully: it might include characters like "%" and "'".
1710 main.create_python_hook_script(hook_path, 'import sys\n'
1711 'sys.stderr.write(' + repr(text) + ')\n'
1712 'sys.exit(1)\n')
1714 def enable_revprop_changes(repo_dir):
1715 """Enable revprop changes in the repository at REPO_DIR by creating a
1716 pre-revprop-change hook script and (if appropriate) making it executable."""
1718 hook_path = main.get_pre_revprop_change_hook_path(repo_dir)
1719 main.create_python_hook_script(hook_path, 'import sys; sys.exit(0)')
1721 def disable_revprop_changes(repo_dir):
1722 """Disable revprop changes in the repository at REPO_DIR by creating a
1723 pre-revprop-change hook script that prints "pre-revprop-change" followed
1724 by its arguments, and returns an error."""
1726 hook_path = main.get_pre_revprop_change_hook_path(repo_dir)
1727 main.create_python_hook_script(hook_path,
1728 'import sys\n'
1729 'sys.stderr.write("pre-revprop-change %s" % " ".join(sys.argv[1:6]))\n'
1730 'sys.exit(1)\n')
1732 def create_failing_post_commit_hook(repo_dir):
1733 """Create a post-commit hook script in the repository at REPO_DIR that always
1734 reports an error."""
1736 hook_path = main.get_post_commit_hook_path(repo_dir)
1737 main.create_python_hook_script(hook_path, 'import sys\n'
1738 'sys.stderr.write("Post-commit hook failed")\n'
1739 'sys.exit(1)')
1741 # set_prop can be used for properties with NULL characters which are not
1742 # handled correctly when passed to subprocess.Popen() and values like "*"
1743 # which are not handled correctly on Windows.
1744 def set_prop(name, value, path, expected_err=None):
1745 """Set a property with specified value"""
1746 if value and (value[0] == '-' or '\x00' in value or sys.platform == 'win32'):
1747 from tempfile import mkstemp
1748 (fd, value_file_path) = mkstemp()
1749 value_file = open(value_file_path, 'wb')
1750 value_file.write(value)
1751 value_file.flush()
1752 value_file.close()
1753 main.run_svn(expected_err, 'propset', '-F', value_file_path, name, path)
1754 os.close(fd)
1755 os.remove(value_file_path)
1756 else:
1757 main.run_svn(expected_err, 'propset', name, value, path)
1759 def check_prop(name, path, exp_out, revprop=None):
1760 """Verify that property NAME on PATH has a value of EXP_OUT.
1761 If REVPROP is not None, then it is a revision number and
1762 a revision property is sought."""
1763 if revprop is not None:
1764 revprop_options = ['--revprop', '-r', revprop]
1765 else:
1766 revprop_options = []
1767 # Not using run_svn because binary_mode must be set
1768 exit_code, out, err = main.run_command(main.svn_binary, None, 1, 'pg',
1769 '--strict', name, path,
1770 '--config-dir',
1771 main.default_config_dir,
1772 '--username', main.wc_author,
1773 '--password', main.wc_passwd,
1774 *revprop_options)
1775 if out != exp_out:
1776 print("svn pg --strict %s output does not match expected." % name)
1777 print("Expected standard output: %s\n" % exp_out)
1778 print("Actual standard output: %s\n" % out)
1779 raise Failure
1781 def fill_file_with_lines(wc_path, line_nbr, line_descrip=None,
1782 append=True):
1783 """Change the file at WC_PATH (adding some lines), and return its
1784 new contents. LINE_NBR indicates the line number at which the new
1785 contents should assume that it's being appended. LINE_DESCRIP is
1786 something like 'This is line' (the default) or 'Conflicting line'."""
1788 if line_descrip is None:
1789 line_descrip = "This is line"
1791 # Generate the new contents for the file.
1792 contents = ""
1793 for n in range(line_nbr, line_nbr + 3):
1794 contents = contents + line_descrip + " " + repr(n) + " in '" + \
1795 os.path.basename(wc_path) + "'.\n"
1797 # Write the new contents to the file.
1798 if append:
1799 main.file_append(wc_path, contents)
1800 else:
1801 main.file_write(wc_path, contents)
1803 return contents
1805 def inject_conflict_into_wc(sbox, state_path, file_path,
1806 expected_disk, expected_status, merged_rev):
1807 """Create a conflict at FILE_PATH by replacing its contents,
1808 committing the change, backdating it to its previous revision,
1809 changing its contents again, then updating it to merge in the
1810 previous change."""
1812 wc_dir = sbox.wc_dir
1814 # Make a change to the file.
1815 contents = fill_file_with_lines(file_path, 1, "This is line", append=False)
1817 # Commit the changed file, first taking note of the current revision.
1818 prev_rev = expected_status.desc[state_path].wc_rev
1819 expected_output = wc.State(wc_dir, {
1820 state_path : wc.StateItem(verb='Sending'),
1822 if expected_status:
1823 expected_status.tweak(state_path, wc_rev=merged_rev)
1824 run_and_verify_commit(wc_dir, expected_output, expected_status,
1825 None, file_path)
1827 # Backdate the file.
1828 exit_code, output, errput = main.run_svn(None, "up", "-r", str(prev_rev),
1829 file_path)
1830 if expected_status:
1831 expected_status.tweak(state_path, wc_rev=prev_rev)
1833 # Make a conflicting change to the file, and backdate the file.
1834 conflicting_contents = fill_file_with_lines(file_path, 1, "Conflicting line",
1835 append=False)
1837 # Merge the previous change into the file to produce a conflict.
1838 if expected_disk:
1839 expected_disk.tweak(state_path, contents="")
1840 expected_output = wc.State(wc_dir, {
1841 state_path : wc.StateItem(status='C '),
1843 inject_conflict_into_expected_state(state_path,
1844 expected_disk, expected_status,
1845 conflicting_contents, contents,
1846 merged_rev)
1847 exit_code, output, errput = main.run_svn(None, "up", "-r", str(merged_rev),
1848 sbox.repo_url + "/" + state_path,
1849 file_path)
1850 if expected_status:
1851 expected_status.tweak(state_path, wc_rev=merged_rev)
1853 def inject_conflict_into_expected_state(state_path,
1854 expected_disk, expected_status,
1855 wc_text, merged_text, merged_rev):
1856 """Update the EXPECTED_DISK and EXPECTED_STATUS trees for the
1857 conflict at STATE_PATH (ignored if None). WC_TEXT, MERGED_TEXT, and
1858 MERGED_REV are used to determine the contents of the conflict (the
1859 text parameters should be newline-terminated)."""
1860 if expected_disk:
1861 conflict_marker = make_conflict_marker_text(wc_text, merged_text,
1862 merged_rev)
1863 existing_text = expected_disk.desc[state_path].contents or ""
1864 expected_disk.tweak(state_path, contents=existing_text + conflict_marker)
1866 if expected_status:
1867 expected_status.tweak(state_path, status='C ')
1869 def make_conflict_marker_text(wc_text, merged_text, merged_rev):
1870 """Return the conflict marker text described by WC_TEXT (the current
1871 text in the working copy, MERGED_TEXT (the conflicting text merged
1872 in), and MERGED_REV (the revision from whence the conflicting text
1873 came)."""
1874 return "<<<<<<< .working\n" + wc_text + "=======\n" + \
1875 merged_text + ">>>>>>> .merge-right.r" + str(merged_rev) + "\n"
1878 def build_greek_tree_conflicts(sbox):
1879 """Create a working copy that has tree-conflict markings.
1880 After this function has been called, sbox.wc_dir is a working
1881 copy that has specific tree-conflict markings.
1883 In particular, this does two conflicting sets of edits and performs an
1884 update so that tree conflicts appear.
1886 Note that this function calls sbox.build() because it needs a clean sbox.
1887 So, there is no need to call sbox.build() before this.
1889 The conflicts are the result of an 'update' on the following changes:
1891 Incoming Local
1893 A/D/G/pi text-mod del
1894 A/D/G/rho del text-mod
1895 A/D/G/tau del del
1897 This function is useful for testing that tree-conflicts are handled
1898 properly once they have appeared, e.g. that commits are blocked, that the
1899 info output is correct, etc.
1901 See also the tree-conflicts tests using deep_trees in various other
1902 .py files, and tree_conflict_tests.py.
1905 sbox.build()
1906 wc_dir = sbox.wc_dir
1907 j = os.path.join
1908 G = j(wc_dir, 'A', 'D', 'G')
1909 pi = j(G, 'pi')
1910 rho = j(G, 'rho')
1911 tau = j(G, 'tau')
1913 # Make incoming changes and "store them away" with a commit.
1914 main.file_append(pi, "Incoming edit.\n")
1915 main.run_svn(None, 'del', rho)
1916 main.run_svn(None, 'del', tau)
1918 expected_output = wc.State(wc_dir, {
1919 'A/D/G/pi' : Item(verb='Sending'),
1920 'A/D/G/rho' : Item(verb='Deleting'),
1921 'A/D/G/tau' : Item(verb='Deleting'),
1923 expected_status = get_virginal_state(wc_dir, 1)
1924 expected_status.tweak('A/D/G/pi', wc_rev='2')
1925 expected_status.remove('A/D/G/rho', 'A/D/G/tau')
1926 run_and_verify_commit(wc_dir, expected_output, expected_status, None,
1927 '-m', 'Incoming changes.', wc_dir )
1929 # Update back to the pristine state ("time-warp").
1930 expected_output = wc.State(wc_dir, {
1931 'A/D/G/pi' : Item(status='U '),
1932 'A/D/G/rho' : Item(status='A '),
1933 'A/D/G/tau' : Item(status='A '),
1935 expected_disk = main.greek_state
1936 expected_status = get_virginal_state(wc_dir, 1)
1937 run_and_verify_update(wc_dir, expected_output, expected_disk,
1938 expected_status, None, None, None, None, None, False,
1939 '-r', '1', wc_dir)
1941 # Make local changes
1942 main.run_svn(None, 'del', pi)
1943 main.file_append(rho, "Local edit.\n")
1944 main.run_svn(None, 'del', tau)
1946 # Update, receiving the incoming changes on top of the local changes,
1947 # causing tree conflicts. Don't check for any particular result: that is
1948 # the job of other tests.
1949 run_and_verify_svn(None, verify.AnyOutput, [], 'update', wc_dir)
1952 def make_deep_trees(base):
1953 """Helper function for deep trees conflicts. Create a set of trees,
1954 each in its own "container" dir. Any conflicts can be tested separately
1955 in each container.
1957 j = os.path.join
1958 # Create the container dirs.
1959 F = j(base, 'F')
1960 D = j(base, 'D')
1961 DF = j(base, 'DF')
1962 DD = j(base, 'DD')
1963 DDF = j(base, 'DDF')
1964 DDD = j(base, 'DDD')
1965 os.makedirs(F)
1966 os.makedirs(j(D, 'D1'))
1967 os.makedirs(j(DF, 'D1'))
1968 os.makedirs(j(DD, 'D1', 'D2'))
1969 os.makedirs(j(DDF, 'D1', 'D2'))
1970 os.makedirs(j(DDD, 'D1', 'D2', 'D3'))
1972 # Create their files.
1973 alpha = j(F, 'alpha')
1974 beta = j(DF, 'D1', 'beta')
1975 gamma = j(DDF, 'D1', 'D2', 'gamma')
1976 main.file_append(alpha, "This is the file 'alpha'.\n")
1977 main.file_append(beta, "This is the file 'beta'.\n")
1978 main.file_append(gamma, "This is the file 'gamma'.\n")
1981 def add_deep_trees(sbox, base_dir_name):
1982 """Prepare a "deep_trees" within a given directory.
1984 The directory <sbox.wc_dir>/<base_dir_name> is created and a deep_tree
1985 is created within. The items are only added, a commit has to be
1986 called separately, if needed.
1988 <base_dir_name> will thus be a container for the set of containers
1989 mentioned in make_deep_trees().
1991 j = os.path.join
1992 base = j(sbox.wc_dir, base_dir_name)
1993 make_deep_trees(base)
1994 main.run_svn(None, 'add', base)
1997 Item = wc.StateItem
1999 # initial deep trees state
2000 deep_trees_virginal_state = wc.State('', {
2001 'F' : Item(),
2002 'F/alpha' : Item("This is the file 'alpha'.\n"),
2003 'D' : Item(),
2004 'D/D1' : Item(),
2005 'DF' : Item(),
2006 'DF/D1' : Item(),
2007 'DF/D1/beta' : Item("This is the file 'beta'.\n"),
2008 'DD' : Item(),
2009 'DD/D1' : Item(),
2010 'DD/D1/D2' : Item(),
2011 'DDF' : Item(),
2012 'DDF/D1' : Item(),
2013 'DDF/D1/D2' : Item(),
2014 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\n"),
2015 'DDD' : Item(),
2016 'DDD/D1' : Item(),
2017 'DDD/D1/D2' : Item(),
2018 'DDD/D1/D2/D3' : Item(),
2022 # Many actions on deep trees and their resulting states...
2024 def deep_trees_leaf_edit(base):
2025 """Helper function for deep trees test cases. Append text to files,
2026 create new files in empty directories, and change leaf node properties."""
2027 j = os.path.join
2028 F = j(base, 'F', 'alpha')
2029 DF = j(base, 'DF', 'D1', 'beta')
2030 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
2031 main.file_append(F, "More text for file alpha.\n")
2032 main.file_append(DF, "More text for file beta.\n")
2033 main.file_append(DDF, "More text for file gamma.\n")
2034 run_and_verify_svn(None, verify.AnyOutput, [],
2035 'propset', 'prop1', '1', F, DF, DDF)
2037 D = j(base, 'D', 'D1')
2038 DD = j(base, 'DD', 'D1', 'D2')
2039 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
2040 run_and_verify_svn(None, verify.AnyOutput, [],
2041 'propset', 'prop1', '1', D, DD, DDD)
2042 D = j(base, 'D', 'D1', 'delta')
2043 DD = j(base, 'DD', 'D1', 'D2', 'epsilon')
2044 DDD = j(base, 'DDD', 'D1', 'D2', 'D3', 'zeta')
2045 main.file_append(D, "This is the file 'delta'.\n")
2046 main.file_append(DD, "This is the file 'epsilon'.\n")
2047 main.file_append(DDD, "This is the file 'zeta'.\n")
2048 run_and_verify_svn(None, verify.AnyOutput, [],
2049 'add', D, DD, DDD)
2051 # deep trees state after a call to deep_trees_leaf_edit
2052 deep_trees_after_leaf_edit = wc.State('', {
2053 'F' : Item(),
2054 'F/alpha' : Item("This is the file 'alpha'.\nMore text for file alpha.\n"),
2055 'D' : Item(),
2056 'D/D1' : Item(),
2057 'D/D1/delta' : Item("This is the file 'delta'.\n"),
2058 'DF' : Item(),
2059 'DF/D1' : Item(),
2060 'DF/D1/beta' : Item("This is the file 'beta'.\nMore text for file beta.\n"),
2061 'DD' : Item(),
2062 'DD/D1' : Item(),
2063 'DD/D1/D2' : Item(),
2064 'DD/D1/D2/epsilon' : Item("This is the file 'epsilon'.\n"),
2065 'DDF' : Item(),
2066 'DDF/D1' : Item(),
2067 'DDF/D1/D2' : Item(),
2068 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\nMore text for file gamma.\n"),
2069 'DDD' : Item(),
2070 'DDD/D1' : Item(),
2071 'DDD/D1/D2' : Item(),
2072 'DDD/D1/D2/D3' : Item(),
2073 'DDD/D1/D2/D3/zeta' : Item("This is the file 'zeta'.\n"),
2077 def deep_trees_leaf_del(base):
2078 """Helper function for deep trees test cases. Delete files and empty
2079 dirs."""
2080 j = os.path.join
2081 F = j(base, 'F', 'alpha')
2082 D = j(base, 'D', 'D1')
2083 DF = j(base, 'DF', 'D1', 'beta')
2084 DD = j(base, 'DD', 'D1', 'D2')
2085 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
2086 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
2087 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
2089 # deep trees state after a call to deep_trees_leaf_del
2090 deep_trees_after_leaf_del = wc.State('', {
2091 'F' : Item(),
2092 'D' : Item(),
2093 'DF' : Item(),
2094 'DF/D1' : Item(),
2095 'DD' : Item(),
2096 'DD/D1' : Item(),
2097 'DDF' : Item(),
2098 'DDF/D1' : Item(),
2099 'DDF/D1/D2' : Item(),
2100 'DDD' : Item(),
2101 'DDD/D1' : Item(),
2102 'DDD/D1/D2' : Item(),
2105 # deep trees state after a call to deep_trees_leaf_del with no commit
2106 def deep_trees_after_leaf_del_no_ci(wc_dir):
2107 if svntest.main.wc_is_singledb(wc_dir):
2108 return deep_trees_after_leaf_del
2109 else:
2110 return deep_trees_empty_dirs
2113 def deep_trees_tree_del(base):
2114 """Helper function for deep trees test cases. Delete top-level dirs."""
2115 j = os.path.join
2116 F = j(base, 'F', 'alpha')
2117 D = j(base, 'D', 'D1')
2118 DF = j(base, 'DF', 'D1')
2119 DD = j(base, 'DD', 'D1')
2120 DDF = j(base, 'DDF', 'D1')
2121 DDD = j(base, 'DDD', 'D1')
2122 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
2124 def deep_trees_rmtree(base):
2125 """Helper function for deep trees test cases. Delete top-level dirs
2126 with rmtree instead of svn del."""
2127 j = os.path.join
2128 F = j(base, 'F', 'alpha')
2129 D = j(base, 'D', 'D1')
2130 DF = j(base, 'DF', 'D1')
2131 DD = j(base, 'DD', 'D1')
2132 DDF = j(base, 'DDF', 'D1')
2133 DDD = j(base, 'DDD', 'D1')
2134 os.unlink(F)
2135 main.safe_rmtree(D)
2136 main.safe_rmtree(DF)
2137 main.safe_rmtree(DD)
2138 main.safe_rmtree(DDF)
2139 main.safe_rmtree(DDD)
2141 # deep trees state after a call to deep_trees_tree_del
2142 deep_trees_after_tree_del = wc.State('', {
2143 'F' : Item(),
2144 'D' : Item(),
2145 'DF' : Item(),
2146 'DD' : Item(),
2147 'DDF' : Item(),
2148 'DDD' : Item(),
2151 # deep trees state without any files
2152 deep_trees_empty_dirs = wc.State('', {
2153 'F' : Item(),
2154 'D' : Item(),
2155 'D/D1' : Item(),
2156 'DF' : Item(),
2157 'DF/D1' : Item(),
2158 'DD' : Item(),
2159 'DD/D1' : Item(),
2160 'DD/D1/D2' : Item(),
2161 'DDF' : Item(),
2162 'DDF/D1' : Item(),
2163 'DDF/D1/D2' : Item(),
2164 'DDD' : Item(),
2165 'DDD/D1' : Item(),
2166 'DDD/D1/D2' : Item(),
2167 'DDD/D1/D2/D3' : Item(),
2170 # deep trees state after a call to deep_trees_tree_del with no commit
2171 def deep_trees_after_tree_del_no_ci(wc_dir):
2172 if svntest.main.wc_is_singledb(wc_dir):
2173 return deep_trees_after_tree_del
2174 else:
2175 return deep_trees_empty_dirs
2177 def deep_trees_tree_del_repos(base):
2178 """Helper function for deep trees test cases. Delete top-level dirs,
2179 directly in the repository."""
2180 j = '/'.join
2181 F = j([base, 'F', 'alpha'])
2182 D = j([base, 'D', 'D1'])
2183 DF = j([base, 'DF', 'D1'])
2184 DD = j([base, 'DD', 'D1'])
2185 DDF = j([base, 'DDF', 'D1'])
2186 DDD = j([base, 'DDD', 'D1'])
2187 main.run_svn(None, 'mkdir', '-m', '', F, D, DF, DD, DDF, DDD)
2189 # Expected merge/update/switch output.
2191 deep_trees_conflict_output = wc.State('', {
2192 'F/alpha' : Item(status=' ', treeconflict='C'),
2193 'D/D1' : Item(status=' ', treeconflict='C'),
2194 'DF/D1' : Item(status=' ', treeconflict='C'),
2195 'DD/D1' : Item(status=' ', treeconflict='C'),
2196 'DDF/D1' : Item(status=' ', treeconflict='C'),
2197 'DDD/D1' : Item(status=' ', treeconflict='C'),
2200 deep_trees_conflict_output_skipped = wc.State('', {
2201 'D/D1' : Item(verb='Skipped'),
2202 'F/alpha' : Item(verb='Skipped'),
2203 'DD/D1' : Item(verb='Skipped'),
2204 'DF/D1' : Item(verb='Skipped'),
2205 'DDD/D1' : Item(verb='Skipped'),
2206 'DDF/D1' : Item(verb='Skipped'),
2209 # Expected status output after merge/update/switch.
2211 deep_trees_status_local_tree_del = wc.State('', {
2212 '' : Item(status=' ', wc_rev=3),
2213 'D' : Item(status=' ', wc_rev=3),
2214 'D/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2215 'DD' : Item(status=' ', wc_rev=3),
2216 'DD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2217 'DD/D1/D2' : Item(status='D ', wc_rev=2),
2218 'DDD' : Item(status=' ', wc_rev=3),
2219 'DDD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2220 'DDD/D1/D2' : Item(status='D ', wc_rev=2),
2221 'DDD/D1/D2/D3' : Item(status='D ', wc_rev=2),
2222 'DDF' : Item(status=' ', wc_rev=3),
2223 'DDF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2224 'DDF/D1/D2' : Item(status='D ', wc_rev=2),
2225 'DDF/D1/D2/gamma' : Item(status='D ', wc_rev=2),
2226 'DF' : Item(status=' ', wc_rev=3),
2227 'DF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2228 'DF/D1/beta' : Item(status='D ', wc_rev=2),
2229 'F' : Item(status=' ', wc_rev=3),
2230 'F/alpha' : Item(status='D ', wc_rev=2, treeconflict='C'),
2233 deep_trees_status_local_leaf_edit = wc.State('', {
2234 '' : Item(status=' ', wc_rev=3),
2235 'D' : Item(status=' ', wc_rev=3),
2236 'D/D1' : Item(status=' M', wc_rev=2, treeconflict='C'),
2237 'D/D1/delta' : Item(status='A ', wc_rev=0),
2238 'DD' : Item(status=' ', wc_rev=3),
2239 'DD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2240 'DD/D1/D2' : Item(status=' M', wc_rev=2),
2241 'DD/D1/D2/epsilon' : Item(status='A ', wc_rev=0),
2242 'DDD' : Item(status=' ', wc_rev=3),
2243 'DDD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2244 'DDD/D1/D2' : Item(status=' ', wc_rev=2),
2245 'DDD/D1/D2/D3' : Item(status=' M', wc_rev=2),
2246 'DDD/D1/D2/D3/zeta' : Item(status='A ', wc_rev=0),
2247 'DDF' : Item(status=' ', wc_rev=3),
2248 'DDF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2249 'DDF/D1/D2' : Item(status=' ', wc_rev=2),
2250 'DDF/D1/D2/gamma' : Item(status='MM', wc_rev=2),
2251 'DF' : Item(status=' ', wc_rev=3),
2252 'DF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2253 'DF/D1/beta' : Item(status='MM', wc_rev=2),
2254 'F' : Item(status=' ', wc_rev=3),
2255 'F/alpha' : Item(status='MM', wc_rev=2, treeconflict='C'),
2259 class DeepTreesTestCase:
2260 """Describes one tree-conflicts test case.
2261 See deep_trees_run_tests_scheme_for_update(), ..._switch(), ..._merge().
2263 The name field is the subdirectory name in which the test should be run.
2265 The local_action and incoming_action are the functions to run
2266 to construct the local changes and incoming changes, respectively.
2267 See deep_trees_leaf_edit, deep_trees_tree_del, etc.
2269 The expected_* and error_re_string arguments are described in functions
2270 run_and_verify_[update|switch|merge]
2271 except expected_info, which is a dict that has path keys with values
2272 that are dicts as passed to run_and_verify_info():
2273 expected_info = {
2274 'F/alpha' : {
2275 'Revision' : '3',
2276 'Tree conflict' :
2277 '^local delete, incoming edit upon update'
2278 + ' Source left: .file.*/F/alpha@2'
2279 + ' Source right: .file.*/F/alpha@3$',
2281 'DF/D1' : {
2282 'Tree conflict' :
2283 '^local delete, incoming edit upon update'
2284 + ' Source left: .dir.*/DF/D1@2'
2285 + ' Source right: .dir.*/DF/D1@3$',
2290 Note: expected_skip is only used in merge, i.e. using
2291 deep_trees_run_tests_scheme_for_merge.
2294 def __init__(self, name, local_action, incoming_action,
2295 expected_output = None, expected_disk = None,
2296 expected_status = None, expected_skip = None,
2297 error_re_string = None,
2298 commit_block_string = ".*remains in conflict.*",
2299 expected_info = None):
2300 self.name = name
2301 self.local_action = local_action
2302 self.incoming_action = incoming_action
2303 self.expected_output = expected_output
2304 self.expected_disk = expected_disk
2305 self.expected_status = expected_status
2306 self.expected_skip = expected_skip
2307 self.error_re_string = error_re_string
2308 self.commit_block_string = commit_block_string
2309 self.expected_info = expected_info
2313 def deep_trees_run_tests_scheme_for_update(sbox, greater_scheme):
2315 Runs a given list of tests for conflicts occuring at an update operation.
2317 This function wants to save time and perform a number of different
2318 test cases using just a single repository and performing just one commit
2319 for all test cases instead of one for each test case.
2321 1) Each test case is initialized in a separate subdir. Each subdir
2322 again contains one set of "deep_trees", being separate container
2323 dirs for different depths of trees (F, D, DF, DD, DDF, DDD).
2325 2) A commit is performed across all test cases and depths.
2326 (our initial state, -r2)
2328 3) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2329 its *incoming* action is performed (e.g. "deep_trees_leaf_edit"), in
2330 each of the different depth trees (F, D, DF, ... DDD).
2332 4) A commit is performed across all test cases and depths:
2333 our "incoming" state is "stored away in the repository for now",
2334 -r3.
2336 5) All test case dirs and contained deep_trees are time-warped
2337 (updated) back to -r2, the initial state containing deep_trees.
2339 6) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2340 its *local* action is performed (e.g. "deep_trees_leaf_del"), in
2341 each of the different depth trees (F, D, DF, ... DDD).
2343 7) An update to -r3 is performed across all test cases and depths.
2344 This causes tree-conflicts between the "local" state in the working
2345 copy and the "incoming" state from the repository, -r3.
2347 8) A commit is performed in each separate container, to verify
2348 that each tree-conflict indeed blocks a commit.
2350 The sbox parameter is just the sbox passed to a test function. No need
2351 to call sbox.build(), since it is called (once) within this function.
2353 The "table" greater_scheme models all of the different test cases
2354 that should be run using a single repository.
2356 greater_scheme is a list of DeepTreesTestCase items, which define complete
2357 test setups, so that they can be performed as described above.
2360 j = os.path.join
2362 if not sbox.is_built():
2363 sbox.build()
2364 wc_dir = sbox.wc_dir
2367 # 1) create directories
2369 for test_case in greater_scheme:
2370 try:
2371 add_deep_trees(sbox, test_case.name)
2372 except:
2373 print("ERROR IN: Tests scheme for update: "
2374 + "while setting up deep trees in '%s'" % test_case.name)
2375 raise
2378 # 2) commit initial state
2380 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2383 # 3) apply incoming changes
2385 for test_case in greater_scheme:
2386 try:
2387 test_case.incoming_action(j(sbox.wc_dir, test_case.name))
2388 except:
2389 print("ERROR IN: Tests scheme for update: "
2390 + "while performing incoming action in '%s'" % test_case.name)
2391 raise
2394 # 4) commit incoming changes
2396 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2399 # 5) time-warp back to -r2
2401 main.run_svn(None, 'update', '-r2', wc_dir)
2404 # 6) apply local changes
2406 for test_case in greater_scheme:
2407 try:
2408 test_case.local_action(j(wc_dir, test_case.name))
2409 except:
2410 print("ERROR IN: Tests scheme for update: "
2411 + "while performing local action in '%s'" % test_case.name)
2412 raise
2415 # 7) update to -r3, conflicting with incoming changes.
2416 # A lot of different things are expected.
2417 # Do separate update operations for each test case.
2419 for test_case in greater_scheme:
2420 try:
2421 base = j(wc_dir, test_case.name)
2423 x_out = test_case.expected_output
2424 if x_out != None:
2425 x_out = x_out.copy()
2426 x_out.wc_dir = base
2428 x_disk = test_case.expected_disk
2430 x_status = test_case.expected_status
2431 if x_status != None:
2432 x_status.copy()
2433 x_status.wc_dir = base
2435 run_and_verify_update(base, x_out, x_disk, None,
2436 error_re_string = test_case.error_re_string)
2437 if x_status:
2438 run_and_verify_unquiet_status(base, x_status)
2440 x_info = test_case.expected_info or {}
2441 for path in x_info:
2442 run_and_verify_info([x_info[path]], j(base, path))
2444 except:
2445 print("ERROR IN: Tests scheme for update: "
2446 + "while verifying in '%s'" % test_case.name)
2447 raise
2450 # 8) Verify that commit fails.
2452 for test_case in greater_scheme:
2453 try:
2454 base = j(wc_dir, test_case.name)
2456 x_status = test_case.expected_status
2457 if x_status != None:
2458 x_status.copy()
2459 x_status.wc_dir = base
2461 run_and_verify_commit(base, None, x_status,
2462 test_case.commit_block_string,
2463 base)
2464 except:
2465 print("ERROR IN: Tests scheme for update: "
2466 + "while checking commit-blocking in '%s'" % test_case.name)
2467 raise
2471 def deep_trees_skipping_on_update(sbox, test_case, skip_paths,
2472 chdir_skip_paths):
2474 Create tree conflicts, then update again, expecting the existing tree
2475 conflicts to be skipped.
2476 SKIP_PATHS is a list of paths, relative to the "base dir", for which
2477 "update" on the "base dir" should report as skipped.
2478 CHDIR_SKIP_PATHS is a list of (target-path, skipped-path) pairs for which
2479 an update of "target-path" (relative to the "base dir") should result in
2480 "skipped-path" (relative to "target-path") being reported as skipped.
2483 """FURTHER_ACTION is a function that will make a further modification to
2484 each target, this being the modification that we expect to be skipped. The
2485 function takes the "base dir" (the WC path to the test case directory) as
2486 its only argument."""
2487 further_action = deep_trees_tree_del_repos
2489 j = os.path.join
2490 wc_dir = sbox.wc_dir
2491 base = j(wc_dir, test_case.name)
2493 # Initialize: generate conflicts. (We do not check anything here.)
2494 setup_case = DeepTreesTestCase(test_case.name,
2495 test_case.local_action,
2496 test_case.incoming_action,
2497 None,
2498 None,
2499 None)
2500 deep_trees_run_tests_scheme_for_update(sbox, [setup_case])
2502 # Make a further change to each target in the repository so there is a new
2503 # revision to update to. (This is r4.)
2504 further_action(sbox.repo_url + '/' + test_case.name)
2506 # Update whole working copy, expecting the nodes still in conflict to be
2507 # skipped.
2509 x_out = test_case.expected_output
2510 if x_out != None:
2511 x_out = x_out.copy()
2512 x_out.wc_dir = base
2514 x_disk = test_case.expected_disk
2516 x_status = test_case.expected_status
2517 if x_status != None:
2518 x_status = x_status.copy()
2519 x_status.wc_dir = base
2520 # Account for nodes that were updated by further_action
2521 x_status.tweak('', 'D', 'F', 'DD', 'DF', 'DDD', 'DDF', wc_rev=4)
2523 run_and_verify_update(base, x_out, x_disk, None,
2524 error_re_string = test_case.error_re_string)
2526 run_and_verify_unquiet_status(base, x_status)
2528 # Try to update each in-conflict subtree. Expect a 'Skipped' output for
2529 # each, and the WC status to be unchanged.
2530 for path in skip_paths:
2531 run_and_verify_update(j(base, path),
2532 wc.State(base, {path : Item(verb='Skipped')}),
2533 None, None)
2535 run_and_verify_unquiet_status(base, x_status)
2537 # Try to update each in-conflict subtree. Expect a 'Skipped' output for
2538 # each, and the WC status to be unchanged.
2539 # This time, cd to the subdir before updating it.
2540 was_cwd = os.getcwd()
2541 for path, skipped in chdir_skip_paths:
2542 #print("CHDIR TO: %s" % j(base, path))
2543 os.chdir(j(base, path))
2544 run_and_verify_update('',
2545 wc.State('', {skipped : Item(verb='Skipped')}),
2546 None, None)
2547 os.chdir(was_cwd)
2549 run_and_verify_unquiet_status(base, x_status)
2551 # Verify that commit still fails.
2552 for path, skipped in chdir_skip_paths:
2554 run_and_verify_commit(j(base, path), None, None,
2555 test_case.commit_block_string,
2556 base)
2558 run_and_verify_unquiet_status(base, x_status)
2561 def deep_trees_run_tests_scheme_for_switch(sbox, greater_scheme):
2563 Runs a given list of tests for conflicts occuring at a switch operation.
2565 This function wants to save time and perform a number of different
2566 test cases using just a single repository and performing just one commit
2567 for all test cases instead of one for each test case.
2569 1) Each test case is initialized in a separate subdir. Each subdir
2570 again contains two subdirs: one "local" and one "incoming" for
2571 the switch operation. These contain a set of deep_trees each.
2573 2) A commit is performed across all test cases and depths.
2574 (our initial state, -r2)
2576 3) In each test case subdir's incoming subdir, the
2577 incoming actions are performed.
2579 4) A commit is performed across all test cases and depths. (-r3)
2581 5) In each test case subdir's local subdir, the local actions are
2582 performed. They remain uncommitted in the working copy.
2584 6) In each test case subdir's local dir, a switch is performed to its
2585 corresponding incoming dir.
2586 This causes conflicts between the "local" state in the working
2587 copy and the "incoming" state from the incoming subdir (still -r3).
2589 7) A commit is performed in each separate container, to verify
2590 that each tree-conflict indeed blocks a commit.
2592 The sbox parameter is just the sbox passed to a test function. No need
2593 to call sbox.build(), since it is called (once) within this function.
2595 The "table" greater_scheme models all of the different test cases
2596 that should be run using a single repository.
2598 greater_scheme is a list of DeepTreesTestCase items, which define complete
2599 test setups, so that they can be performed as described above.
2602 j = os.path.join
2604 if not sbox.is_built():
2605 sbox.build()
2606 wc_dir = sbox.wc_dir
2609 # 1) Create directories.
2611 for test_case in greater_scheme:
2612 try:
2613 base = j(sbox.wc_dir, test_case.name)
2614 os.makedirs(base)
2615 make_deep_trees(j(base, "local"))
2616 make_deep_trees(j(base, "incoming"))
2617 main.run_svn(None, 'add', base)
2618 except:
2619 print("ERROR IN: Tests scheme for switch: "
2620 + "while setting up deep trees in '%s'" % test_case.name)
2621 raise
2624 # 2) Commit initial state (-r2).
2626 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2629 # 3) Apply incoming changes
2631 for test_case in greater_scheme:
2632 try:
2633 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2634 except:
2635 print("ERROR IN: Tests scheme for switch: "
2636 + "while performing incoming action in '%s'" % test_case.name)
2637 raise
2640 # 4) Commit all changes (-r3).
2642 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2645 # 5) Apply local changes in their according subdirs.
2647 for test_case in greater_scheme:
2648 try:
2649 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
2650 except:
2651 print("ERROR IN: Tests scheme for switch: "
2652 + "while performing local action in '%s'" % test_case.name)
2653 raise
2656 # 6) switch the local dir to the incoming url, conflicting with incoming
2657 # changes. A lot of different things are expected.
2658 # Do separate switch operations for each test case.
2660 for test_case in greater_scheme:
2661 try:
2662 local = j(wc_dir, test_case.name, "local")
2663 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
2665 x_out = test_case.expected_output
2666 if x_out != None:
2667 x_out = x_out.copy()
2668 x_out.wc_dir = local
2670 x_disk = test_case.expected_disk
2672 x_status = test_case.expected_status
2673 if x_status != None:
2674 x_status.copy()
2675 x_status.wc_dir = local
2677 run_and_verify_switch(local, local, incoming, x_out, x_disk, None,
2678 error_re_string = test_case.error_re_string)
2679 run_and_verify_unquiet_status(local, x_status)
2681 x_info = test_case.expected_info or {}
2682 for path in x_info:
2683 run_and_verify_info([x_info[path]], j(local, path))
2684 except:
2685 print("ERROR IN: Tests scheme for switch: "
2686 + "while verifying in '%s'" % test_case.name)
2687 raise
2690 # 7) Verify that commit fails.
2692 for test_case in greater_scheme:
2693 try:
2694 local = j(wc_dir, test_case.name, 'local')
2696 x_status = test_case.expected_status
2697 if x_status != None:
2698 x_status.copy()
2699 x_status.wc_dir = local
2701 run_and_verify_commit(local, None, x_status,
2702 test_case.commit_block_string,
2703 local)
2704 except:
2705 print("ERROR IN: Tests scheme for switch: "
2706 + "while checking commit-blocking in '%s'" % test_case.name)
2707 raise
2710 def deep_trees_run_tests_scheme_for_merge(sbox, greater_scheme,
2711 do_commit_local_changes,
2712 do_commit_conflicts=True,
2713 ignore_ancestry=False):
2715 Runs a given list of tests for conflicts occuring at a merge operation.
2717 This function wants to save time and perform a number of different
2718 test cases using just a single repository and performing just one commit
2719 for all test cases instead of one for each test case.
2721 1) Each test case is initialized in a separate subdir. Each subdir
2722 initially contains another subdir, called "incoming", which
2723 contains a set of deep_trees.
2725 2) A commit is performed across all test cases and depths.
2726 (a pre-initial state)
2728 3) In each test case subdir, the "incoming" subdir is copied to "local",
2729 via the `svn copy' command. Each test case's subdir now has two sub-
2730 dirs: "local" and "incoming", initial states for the merge operation.
2732 4) An update is performed across all test cases and depths, so that the
2733 copies made in 3) are pulled into the wc.
2735 5) In each test case's "incoming" subdir, the incoming action is
2736 performed.
2738 6) A commit is performed across all test cases and depths, to commit
2739 the incoming changes.
2740 If do_commit_local_changes is True, this becomes step 7 (swap steps).
2742 7) In each test case's "local" subdir, the local_action is performed.
2743 If do_commit_local_changes is True, this becomes step 6 (swap steps).
2744 Then, in effect, the local changes are committed as well.
2746 8) In each test case subdir, the "incoming" subdir is merged into the
2747 "local" subdir. If ignore_ancestry is True, then the merge is done
2748 with the --ignore-ancestry option, so mergeinfo is neither considered
2749 nor recorded. This causes conflicts between the "local" state in the
2750 working copy and the "incoming" state from the incoming subdir.
2752 9) If do_commit_conflicts is True, then a commit is performed in each
2753 separate container, to verify that each tree-conflict indeed blocks
2754 a commit.
2756 The sbox parameter is just the sbox passed to a test function. No need
2757 to call sbox.build(), since it is called (once) within this function.
2759 The "table" greater_scheme models all of the different test cases
2760 that should be run using a single repository.
2762 greater_scheme is a list of DeepTreesTestCase items, which define complete
2763 test setups, so that they can be performed as described above.
2766 j = os.path.join
2768 if not sbox.is_built():
2769 sbox.build()
2770 wc_dir = sbox.wc_dir
2772 # 1) Create directories.
2773 for test_case in greater_scheme:
2774 try:
2775 base = j(sbox.wc_dir, test_case.name)
2776 os.makedirs(base)
2777 make_deep_trees(j(base, "incoming"))
2778 main.run_svn(None, 'add', base)
2779 except:
2780 print("ERROR IN: Tests scheme for merge: "
2781 + "while setting up deep trees in '%s'" % test_case.name)
2782 raise
2785 # 2) Commit pre-initial state (-r2).
2787 main.run_svn(None, 'commit', '-m', 'pre-initial state', wc_dir)
2790 # 3) Copy "incoming" to "local".
2792 for test_case in greater_scheme:
2793 try:
2794 base_url = sbox.repo_url + "/" + test_case.name
2795 incoming_url = base_url + "/incoming"
2796 local_url = base_url + "/local"
2797 main.run_svn(None, 'cp', incoming_url, local_url, '-m',
2798 'copy incoming to local')
2799 except:
2800 print("ERROR IN: Tests scheme for merge: "
2801 + "while copying deep trees in '%s'" % test_case.name)
2802 raise
2804 # 4) Update to load all of the "/local" subdirs into the working copies.
2806 try:
2807 main.run_svn(None, 'up', sbox.wc_dir)
2808 except:
2809 print("ERROR IN: Tests scheme for merge: "
2810 + "while updating local subdirs")
2811 raise
2814 # 5) Perform incoming actions
2816 for test_case in greater_scheme:
2817 try:
2818 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2819 except:
2820 print("ERROR IN: Tests scheme for merge: "
2821 + "while performing incoming action in '%s'" % test_case.name)
2822 raise
2825 # 6) or 7) Commit all incoming actions
2827 if not do_commit_local_changes:
2828 try:
2829 main.run_svn(None, 'ci', '-m', 'Committing incoming actions',
2830 sbox.wc_dir)
2831 except:
2832 print("ERROR IN: Tests scheme for merge: "
2833 + "while committing incoming actions")
2834 raise
2837 # 7) or 6) Perform all local actions.
2839 for test_case in greater_scheme:
2840 try:
2841 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
2842 except:
2843 print("ERROR IN: Tests scheme for merge: "
2844 + "while performing local action in '%s'" % test_case.name)
2845 raise
2848 # 6) or 7) Commit all incoming actions
2850 if do_commit_local_changes:
2851 try:
2852 main.run_svn(None, 'ci', '-m', 'Committing incoming and local actions',
2853 sbox.wc_dir)
2854 except:
2855 print("ERROR IN: Tests scheme for merge: "
2856 + "while committing incoming and local actions")
2857 raise
2860 # 8) Merge all "incoming" subdirs to their respective "local" subdirs.
2861 # This creates conflicts between the local changes in the "local" wc
2862 # subdirs and the incoming states committed in the "incoming" subdirs.
2864 for test_case in greater_scheme:
2865 try:
2866 local = j(sbox.wc_dir, test_case.name, "local")
2867 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
2869 x_out = test_case.expected_output
2870 if x_out != None:
2871 x_out = x_out.copy()
2872 x_out.wc_dir = local
2874 x_disk = test_case.expected_disk
2876 x_status = test_case.expected_status
2877 if x_status != None:
2878 x_status.copy()
2879 x_status.wc_dir = local
2881 x_skip = test_case.expected_skip
2882 if x_skip != None:
2883 x_skip.copy()
2884 x_skip.wc_dir = local
2886 varargs = (local,)
2887 if ignore_ancestry:
2888 varargs = varargs + ('--ignore-ancestry',)
2890 run_and_verify_merge(local, None, None, incoming, None,
2891 x_out, None, None, x_disk, None, x_skip,
2892 test_case.error_re_string,
2893 None, None, None, None,
2894 False, False, *varargs)
2895 run_and_verify_unquiet_status(local, x_status)
2896 except:
2897 print("ERROR IN: Tests scheme for merge: "
2898 + "while verifying in '%s'" % test_case.name)
2899 raise
2902 # 9) Verify that commit fails.
2904 if do_commit_conflicts:
2905 for test_case in greater_scheme:
2906 try:
2907 local = j(wc_dir, test_case.name, 'local')
2909 x_status = test_case.expected_status
2910 if x_status != None:
2911 x_status.copy()
2912 x_status.wc_dir = local
2914 run_and_verify_commit(local, None, x_status,
2915 test_case.commit_block_string,
2916 local)
2917 except:
2918 print("ERROR IN: Tests scheme for merge: "
2919 + "while checking commit-blocking in '%s'" % test_case.name)
2920 raise