shrink_test_case: ensure that CVS files get closed.
[cvs2svn.git] / svntest / actions.py
blobb69fed19c1c99cbd7b12815fd73639afb0c5f7c6
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, logging
28 import xml.parsers.expat
29 from xml.dom.minidom import parseString
30 if sys.version_info[0] >= 3:
31 # Python >=3.0
32 from io import StringIO
33 else:
34 # Python <3.0
35 from cStringIO import StringIO
37 import svntest
38 from svntest import main, verify, tree, wc
39 from svntest import Failure
41 logger = logging.getLogger()
43 def _log_tree_state(msg, actual, subtree=""):
44 if subtree:
45 subtree += os.sep
46 o = StringIO()
47 o.write(msg + '\n')
48 tree.dump_tree_script(actual, subtree, stream=o)
49 logger.warn(o.getvalue())
50 o.close()
52 def no_sleep_for_timestamps():
53 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'yes'
55 def do_sleep_for_timestamps():
56 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'no'
58 def no_relocate_validation():
59 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION'] = 'yes'
61 def do_relocate_validation():
62 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION'] = 'no'
64 def setup_pristine_greek_repository():
65 """Create the pristine repository and 'svn import' the greek tree"""
67 # these directories don't exist out of the box, so we may have to create them
68 if not os.path.exists(main.general_wc_dir):
69 os.makedirs(main.general_wc_dir)
71 if not os.path.exists(main.general_repo_dir):
72 os.makedirs(main.general_repo_dir) # this also creates all the intermediate dirs
74 # If there's no pristine repos, create one.
75 if not os.path.exists(main.pristine_greek_repos_dir):
76 main.create_repos(main.pristine_greek_repos_dir)
78 # if this is dav, gives us access rights to import the greek tree.
79 if main.is_ra_type_dav():
80 authz_file = os.path.join(main.work_dir, "authz")
81 main.file_write(authz_file, "[/]\n* = rw\n")
83 # dump the greek tree to disk.
84 main.greek_state.write_to_disk(main.greek_dump_dir)
86 # import the greek tree, using l:foo/p:bar
87 ### todo: svn should not be prompting for auth info when using
88 ### repositories with no auth/auth requirements
89 exit_code, output, errput = main.run_svn(None, 'import', '-m',
90 'Log message for revision 1.',
91 main.greek_dump_dir,
92 main.pristine_greek_repos_url)
94 # check for any errors from the import
95 if len(errput):
96 display_lines("Errors during initial 'svn import':",
97 'STDERR', None, errput)
98 sys.exit(1)
100 # verify the printed output of 'svn import'.
101 lastline = output.pop().strip()
102 match = re.search("(Committed|Imported) revision [0-9]+.", lastline)
103 if not match:
104 logger.error("import did not succeed, while creating greek repos.")
105 logger.error("The final line from 'svn import' was:")
106 logger.error(lastline)
107 sys.exit(1)
108 output_tree = wc.State.from_commit(output)
110 expected_output_tree = main.greek_state.copy(main.greek_dump_dir)
111 expected_output_tree.tweak(verb='Adding',
112 contents=None)
114 try:
115 expected_output_tree.compare_and_display('output', output_tree)
116 except tree.SVNTreeUnequal:
117 verify.display_trees("ERROR: output of import command is unexpected.",
118 "OUTPUT TREE",
119 expected_output_tree.old_tree(),
120 output_tree.old_tree())
121 sys.exit(1)
123 # Finally, disallow any changes to the "pristine" repos.
124 error_msg = "Don't modify the pristine repository"
125 create_failing_hook(main.pristine_greek_repos_dir, 'start-commit', error_msg)
126 create_failing_hook(main.pristine_greek_repos_dir, 'pre-lock', error_msg)
127 create_failing_hook(main.pristine_greek_repos_dir, 'pre-revprop-change', error_msg)
130 ######################################################################
132 def guarantee_empty_repository(path):
133 """Guarantee that a local svn repository exists at PATH, containing
134 nothing."""
136 if path == main.pristine_greek_repos_dir:
137 logger.error("attempt to overwrite the pristine repos! Aborting.")
138 sys.exit(1)
140 # create an empty repository at PATH.
141 main.safe_rmtree(path)
142 main.create_repos(path)
144 # Used by every test, so that they can run independently of one
145 # another. Every time this routine is called, it recursively copies
146 # the `pristine repos' to a new location.
147 # Note: make sure setup_pristine_greek_repository was called once before
148 # using this function.
149 def guarantee_greek_repository(path, minor_version):
150 """Guarantee that a local svn repository exists at PATH, containing
151 nothing but the greek-tree at revision 1."""
153 if path == main.pristine_greek_repos_dir:
154 logger.error("attempt to overwrite the pristine repos! Aborting.")
155 sys.exit(1)
157 # copy the pristine repository to PATH.
158 main.safe_rmtree(path)
159 if main.copy_repos(main.pristine_greek_repos_dir, path, 1, 1, minor_version):
160 logger.error("copying repository failed.")
161 sys.exit(1)
163 # make the repos world-writeable, for mod_dav_svn's sake.
164 main.chmod_tree(path, 0666, 0666)
167 def run_and_verify_atomic_ra_revprop_change(message,
168 expected_stdout,
169 expected_stderr,
170 expected_exit,
171 url, revision, propname,
172 old_propval, propval,
173 want_error):
174 """Run atomic-ra-revprop-change helper and check its output and exit code.
175 Transforms OLD_PROPVAL and PROPVAL into a skel.
176 For HTTP, the default HTTP library is used."""
178 KEY_OLD_PROPVAL = "old_value_p"
179 KEY_NEW_PROPVAL = "value"
181 def skel_make_atom(word):
182 return "%d %s" % (len(word), word)
184 def make_proplist_skel_part(nick, val):
185 if val is None:
186 return ""
187 else:
188 return "%s %s" % (skel_make_atom(nick), skel_make_atom(val))
190 skel = "( %s %s )" % (make_proplist_skel_part(KEY_OLD_PROPVAL, old_propval),
191 make_proplist_skel_part(KEY_NEW_PROPVAL, propval))
193 exit_code, out, err = main.run_atomic_ra_revprop_change(url, revision,
194 propname, skel,
195 want_error)
196 verify.verify_outputs("Unexpected output", out, err,
197 expected_stdout, expected_stderr)
198 verify.verify_exit_code(message, exit_code, expected_exit)
199 return exit_code, out, err
202 def run_and_verify_svnlook(message, expected_stdout,
203 expected_stderr, *varargs):
204 """Like run_and_verify_svnlook2, but the expected exit code is
205 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
207 expected_exit = 0
208 if expected_stderr is not None and expected_stderr != []:
209 expected_exit = 1
210 return run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
211 expected_exit, *varargs)
213 def run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
214 expected_exit, *varargs):
215 """Run svnlook command and check its output and exit code."""
217 exit_code, out, err = main.run_svnlook(*varargs)
218 verify.verify_outputs("Unexpected output", out, err,
219 expected_stdout, expected_stderr)
220 verify.verify_exit_code(message, exit_code, expected_exit)
221 return exit_code, out, err
224 def run_and_verify_svnadmin(message, expected_stdout,
225 expected_stderr, *varargs):
226 """Like run_and_verify_svnadmin2, but the expected exit code is
227 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
229 expected_exit = 0
230 if expected_stderr is not None and expected_stderr != []:
231 expected_exit = 1
232 return run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
233 expected_exit, *varargs)
235 def run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
236 expected_exit, *varargs):
237 """Run svnadmin command and check its output and exit code."""
239 exit_code, out, err = main.run_svnadmin(*varargs)
240 verify.verify_outputs("Unexpected output", out, err,
241 expected_stdout, expected_stderr)
242 verify.verify_exit_code(message, exit_code, expected_exit)
243 return exit_code, out, err
246 def run_and_verify_svnversion(message, wc_dir, trail_url,
247 expected_stdout, expected_stderr, *varargs):
248 """like run_and_verify_svnversion2, but the expected exit code is
249 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
251 expected_exit = 0
252 if expected_stderr is not None and expected_stderr != []:
253 expected_exit = 1
254 return run_and_verify_svnversion2(message, wc_dir, trail_url,
255 expected_stdout, expected_stderr,
256 expected_exit, *varargs)
258 def run_and_verify_svnversion2(message, wc_dir, trail_url,
259 expected_stdout, expected_stderr,
260 expected_exit, *varargs):
261 """Run svnversion command and check its output and exit code."""
263 if trail_url is None:
264 exit_code, out, err = main.run_svnversion(wc_dir, *varargs)
265 else:
266 exit_code, out, err = main.run_svnversion(wc_dir, trail_url, *varargs)
268 verify.verify_outputs("Unexpected output", out, err,
269 expected_stdout, expected_stderr)
270 verify.verify_exit_code(message, exit_code, expected_exit)
271 return exit_code, out, err
273 def run_and_verify_svn(message, expected_stdout, expected_stderr, *varargs):
274 """like run_and_verify_svn2, but the expected exit code is assumed to
275 be 0 if no output is expected on stderr, and 1 otherwise."""
277 expected_exit = 0
278 if expected_stderr is not None:
279 if isinstance(expected_stderr, verify.ExpectedOutput):
280 if not expected_stderr.matches([]):
281 expected_exit = 1
282 elif expected_stderr != []:
283 expected_exit = 1
284 return run_and_verify_svn2(message, expected_stdout, expected_stderr,
285 expected_exit, *varargs)
287 def run_and_verify_svn2(message, expected_stdout, expected_stderr,
288 expected_exit, *varargs):
289 """Invoke main.run_svn() with *VARARGS. Return exit code as int; stdout,
290 stderr as lists of lines (including line terminators). For both
291 EXPECTED_STDOUT and EXPECTED_STDERR, create an appropriate instance of
292 verify.ExpectedOutput (if necessary):
294 - If it is an array of strings, create a vanilla ExpectedOutput.
296 - If it is a single string, create a RegexOutput that must match every
297 line (for stdout) or any line (for stderr) of the expected output.
299 - If it is already an instance of ExpectedOutput
300 (e.g. UnorderedOutput), leave it alone.
302 ...and invoke compare_and_display_lines() on MESSAGE, a label based
303 on the name of the stream being compared (e.g. STDOUT), the
304 ExpectedOutput instance, and the actual output.
306 If EXPECTED_STDOUT is None, do not check stdout.
307 EXPECTED_STDERR may not be None.
309 If output checks pass, the expected and actual codes are compared.
311 If a comparison fails, a Failure will be raised."""
313 if expected_stderr is None:
314 raise verify.SVNIncorrectDatatype("expected_stderr must not be None")
316 want_err = None
317 if isinstance(expected_stderr, verify.ExpectedOutput):
318 if not expected_stderr.matches([]):
319 want_err = True
320 elif expected_stderr != []:
321 want_err = True
323 exit_code, out, err = main.run_svn(want_err, *varargs)
324 verify.verify_outputs(message, out, err, expected_stdout, expected_stderr)
325 verify.verify_exit_code(message, exit_code, expected_exit)
326 return exit_code, out, err
328 def run_and_verify_load(repo_dir, dump_file_content,
329 bypass_prop_validation = False):
330 "Runs 'svnadmin load' and reports any errors."
331 if not isinstance(dump_file_content, list):
332 raise TypeError("dump_file_content argument should have list type")
333 expected_stderr = []
334 if bypass_prop_validation:
335 exit_code, output, errput = main.run_command_stdin(
336 main.svnadmin_binary, expected_stderr, 0, 1, dump_file_content,
337 'load', '--force-uuid', '--quiet', '--bypass-prop-validation', repo_dir)
338 else:
339 exit_code, output, errput = main.run_command_stdin(
340 main.svnadmin_binary, expected_stderr, 0, 1, dump_file_content,
341 'load', '--force-uuid', '--quiet', repo_dir)
343 verify.verify_outputs("Unexpected stderr output", None, errput,
344 None, expected_stderr)
347 def run_and_verify_dump(repo_dir, deltas=False):
348 "Runs 'svnadmin dump' and reports any errors, returning the dump content."
349 if deltas:
350 exit_code, output, errput = main.run_svnadmin('dump', '--deltas',
351 repo_dir)
352 else:
353 exit_code, output, errput = main.run_svnadmin('dump', repo_dir)
354 verify.verify_outputs("Missing expected output(s)", output, errput,
355 verify.AnyOutput, verify.AnyOutput)
356 return output
359 def run_and_verify_svnrdump(dumpfile_content, expected_stdout,
360 expected_stderr, expected_exit, *varargs):
361 """Runs 'svnrdump dump|load' depending on dumpfile_content and
362 reports any errors."""
363 exit_code, output, err = main.run_svnrdump(dumpfile_content, *varargs)
365 # Since main.run_svnrdump() uses binary mode, normalize the stderr
366 # line endings on Windows ourselves.
367 if sys.platform == 'win32':
368 err = map(lambda x : x.replace('\r\n', '\n'), err)
370 for index, line in enumerate(err[:]):
371 if re.search("warning: W200007", line):
372 del err[index]
374 verify.verify_outputs("Unexpected output", output, err,
375 expected_stdout, expected_stderr)
376 verify.verify_exit_code("Unexpected return code", exit_code, expected_exit)
377 return output
380 def run_and_verify_svnmucc(message, expected_stdout, expected_stderr,
381 *varargs):
382 """Run svnmucc command and check its output"""
384 expected_exit = 0
385 if expected_stderr is not None and expected_stderr != []:
386 expected_exit = 1
387 return run_and_verify_svnmucc2(message, expected_stdout, expected_stderr,
388 expected_exit, *varargs)
390 def run_and_verify_svnmucc2(message, expected_stdout, expected_stderr,
391 expected_exit, *varargs):
392 """Run svnmucc command and check its output and exit code."""
394 exit_code, out, err = main.run_svnmucc(*varargs)
395 verify.verify_outputs("Unexpected output", out, err,
396 expected_stdout, expected_stderr)
397 verify.verify_exit_code(message, exit_code, expected_exit)
398 return exit_code, out, err
401 def load_repo(sbox, dumpfile_path = None, dump_str = None,
402 bypass_prop_validation = False):
403 "Loads the dumpfile into sbox"
404 if not dump_str:
405 dump_str = open(dumpfile_path, "rb").read()
407 # Create a virgin repos and working copy
408 main.safe_rmtree(sbox.repo_dir, 1)
409 main.safe_rmtree(sbox.wc_dir, 1)
410 main.create_repos(sbox.repo_dir)
412 # Load the mergetracking dumpfile into the repos, and check it out the repo
413 run_and_verify_load(sbox.repo_dir, dump_str.splitlines(True),
414 bypass_prop_validation)
415 run_and_verify_svn(None, None, [], "co", sbox.repo_url, sbox.wc_dir)
417 return dump_str
419 def expected_noop_update_output(rev):
420 """Return an ExpectedOutput object describing what we'd expect to
421 see from an update to revision REV that was effectively a no-op (no
422 server changes transmitted)."""
423 return verify.createExpectedOutput("Updating '.*':|At revision %d."
424 % (rev),
425 "no-op update")
427 ######################################################################
428 # Subversion Actions
430 # These are all routines that invoke 'svn' in particular ways, and
431 # then verify the results by comparing expected trees with actual
432 # trees.
436 def run_and_verify_checkout2(do_remove,
437 URL, wc_dir_name, output_tree, disk_tree,
438 singleton_handler_a = None,
439 a_baton = None,
440 singleton_handler_b = None,
441 b_baton = None,
442 *args):
443 """Checkout the URL into a new directory WC_DIR_NAME. *ARGS are any
444 extra optional args to the checkout subcommand.
446 The subcommand output will be verified against OUTPUT_TREE,
447 and the working copy itself will be verified against DISK_TREE.
448 For the latter comparison, SINGLETON_HANDLER_A and
449 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
450 function's doc string for more details. Return if successful, raise
451 on failure.
453 WC_DIR_NAME is deleted if DO_REMOVE is True.
456 if isinstance(output_tree, wc.State):
457 output_tree = output_tree.old_tree()
458 if isinstance(disk_tree, wc.State):
459 disk_tree = disk_tree.old_tree()
461 # Remove dir if it's already there, unless this is a forced checkout.
462 # In that case assume we want to test a forced checkout's toleration
463 # of obstructing paths.
464 if do_remove:
465 main.safe_rmtree(wc_dir_name)
467 # Checkout and make a tree of the output, using l:foo/p:bar
468 ### todo: svn should not be prompting for auth info when using
469 ### repositories with no auth/auth requirements
470 exit_code, output, errput = main.run_svn(None, 'co',
471 URL, wc_dir_name, *args)
472 actual = tree.build_tree_from_checkout(output)
474 # Verify actual output against expected output.
475 try:
476 tree.compare_trees("output", actual, output_tree)
477 except tree.SVNTreeUnequal:
478 _log_tree_state("ACTUAL OUTPUT TREE:", actual, wc_dir_name)
479 raise
481 # Create a tree by scanning the working copy
482 actual = tree.build_tree_from_wc(wc_dir_name)
484 # Verify expected disk against actual disk.
485 try:
486 tree.compare_trees("disk", actual, disk_tree,
487 singleton_handler_a, a_baton,
488 singleton_handler_b, b_baton)
489 except tree.SVNTreeUnequal:
490 _log_tree_state("ACTUAL DISK TREE:", actual, wc_dir_name)
491 raise
493 def run_and_verify_checkout(URL, wc_dir_name, output_tree, disk_tree,
494 singleton_handler_a = None,
495 a_baton = None,
496 singleton_handler_b = None,
497 b_baton = None,
498 *args):
499 """Same as run_and_verify_checkout2(), but without the DO_REMOVE arg.
500 WC_DIR_NAME is deleted if present unless the '--force' option is passed
501 in *ARGS."""
504 # Remove dir if it's already there, unless this is a forced checkout.
505 # In that case assume we want to test a forced checkout's toleration
506 # of obstructing paths.
507 return run_and_verify_checkout2(('--force' not in args),
508 URL, wc_dir_name, output_tree, disk_tree,
509 singleton_handler_a,
510 a_baton,
511 singleton_handler_b,
512 b_baton,
513 *args)
516 def run_and_verify_export(URL, export_dir_name, output_tree, disk_tree,
517 *args):
518 """Export the URL into a new directory WC_DIR_NAME.
520 The subcommand output will be verified against OUTPUT_TREE,
521 and the exported copy itself will be verified against DISK_TREE.
522 Return if successful, raise on failure.
524 assert isinstance(output_tree, wc.State)
525 assert isinstance(disk_tree, wc.State)
527 disk_tree = disk_tree.old_tree()
528 output_tree = output_tree.old_tree()
530 # Export and make a tree of the output, using l:foo/p:bar
531 ### todo: svn should not be prompting for auth info when using
532 ### repositories with no auth/auth requirements
533 exit_code, output, errput = main.run_svn(None, 'export',
534 URL, export_dir_name, *args)
535 actual = tree.build_tree_from_checkout(output)
537 # Verify actual output against expected output.
538 try:
539 tree.compare_trees("output", actual, output_tree)
540 except tree.SVNTreeUnequal:
541 _log_tree_state("ACTUAL OUTPUT TREE:", actual, export_dir_name)
542 raise
544 # Create a tree by scanning the working copy. Don't ignore
545 # the .svn directories so that we generate an error if they
546 # happen to show up.
547 actual = tree.build_tree_from_wc(export_dir_name, ignore_svn=False)
549 # Verify expected disk against actual disk.
550 try:
551 tree.compare_trees("disk", actual, disk_tree)
552 except tree.SVNTreeUnequal:
553 _log_tree_state("ACTUAL DISK TREE:", actual, export_dir_name)
554 raise
557 # run_and_verify_log_xml
559 class LogEntry:
560 def __init__(self, revision, changed_paths=None, revprops=None):
561 self.revision = revision
562 if changed_paths == None:
563 self.changed_paths = {}
564 else:
565 self.changed_paths = changed_paths
566 if revprops == None:
567 self.revprops = {}
568 else:
569 self.revprops = revprops
571 def assert_changed_paths(self, changed_paths):
572 """Assert that changed_paths is the same as this entry's changed_paths
573 Raises svntest.Failure if not.
575 if self.changed_paths != changed_paths:
576 raise Failure('\n' + '\n'.join(difflib.ndiff(
577 pprint.pformat(changed_paths).splitlines(),
578 pprint.pformat(self.changed_paths).splitlines())))
580 def assert_revprops(self, revprops):
581 """Assert that the dict revprops is the same as this entry's revprops.
583 Raises svntest.Failure if not.
585 if self.revprops != revprops:
586 raise Failure('\n' + '\n'.join(difflib.ndiff(
587 pprint.pformat(revprops).splitlines(),
588 pprint.pformat(self.revprops).splitlines())))
590 class LogParser:
591 def parse(self, data):
592 """Return a list of LogEntrys parsed from the sequence of strings data.
594 This is the only method of interest to callers.
596 try:
597 for i in data:
598 self.parser.Parse(i)
599 self.parser.Parse('', True)
600 except xml.parsers.expat.ExpatError, e:
601 raise verify.SVNUnexpectedStdout('%s\n%s\n' % (e, ''.join(data),))
602 return self.entries
604 def __init__(self):
605 # for expat
606 self.parser = xml.parsers.expat.ParserCreate()
607 self.parser.StartElementHandler = self.handle_start_element
608 self.parser.EndElementHandler = self.handle_end_element
609 self.parser.CharacterDataHandler = self.handle_character_data
610 # Ignore some things.
611 self.ignore_elements('log', 'paths', 'revprops')
612 self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
613 # internal state
614 self.cdata = []
615 self.property = None
616 self.kind = None
617 self.action = None
618 # the result
619 self.entries = []
621 def ignore(self, *args, **kwargs):
622 del self.cdata[:]
623 def ignore_tags(self, *args):
624 for tag in args:
625 setattr(self, tag, self.ignore)
626 def ignore_elements(self, *args):
627 for element in args:
628 self.ignore_tags(element + '_start', element + '_end')
630 # expat handlers
631 def handle_start_element(self, name, attrs):
632 getattr(self, name + '_start')(attrs)
633 def handle_end_element(self, name):
634 getattr(self, name + '_end')()
635 def handle_character_data(self, data):
636 self.cdata.append(data)
638 # element handler utilities
639 def use_cdata(self):
640 result = ''.join(self.cdata).strip()
641 del self.cdata[:]
642 return result
643 def svn_prop(self, name):
644 self.entries[-1].revprops['svn:' + name] = self.use_cdata()
646 # element handlers
647 def logentry_start(self, attrs):
648 self.entries.append(LogEntry(int(attrs['revision'])))
649 def author_end(self):
650 self.svn_prop('author')
651 def msg_end(self):
652 self.svn_prop('log')
653 def date_end(self):
654 # svn:date could be anything, so just note its presence.
655 self.cdata[:] = ['']
656 self.svn_prop('date')
657 def property_start(self, attrs):
658 self.property = attrs['name']
659 def property_end(self):
660 self.entries[-1].revprops[self.property] = self.use_cdata()
661 def path_start(self, attrs):
662 self.kind = attrs['kind']
663 self.action = attrs['action']
664 def path_end(self):
665 self.entries[-1].changed_paths[self.use_cdata()] = [{'kind': self.kind,
666 'action': self.action}]
668 def run_and_verify_log_xml(message=None, expected_paths=None,
669 expected_revprops=None, expected_stdout=None,
670 expected_stderr=None, args=[]):
671 """Call run_and_verify_svn with log --xml and args (optional) as command
672 arguments, and pass along message, expected_stdout, and expected_stderr.
674 If message is None, pass the svn log command as message.
676 expected_paths checking is not yet implemented.
678 expected_revprops is an optional list of dicts, compared to each
679 revision's revprops. The list must be in the same order the log entries
680 come in. Any svn:date revprops in the dicts must be '' in order to
681 match, as the actual dates could be anything.
683 expected_paths and expected_revprops are ignored if expected_stdout or
684 expected_stderr is specified.
686 if message == None:
687 message = ' '.join(args)
689 # We'll parse the output unless the caller specifies expected_stderr or
690 # expected_stdout for run_and_verify_svn.
691 parse = True
692 if expected_stderr == None:
693 expected_stderr = []
694 else:
695 parse = False
696 if expected_stdout != None:
697 parse = False
699 log_args = list(args)
700 if expected_paths != None:
701 log_args.append('-v')
703 (exit_code, stdout, stderr) = run_and_verify_svn(
704 message, expected_stdout, expected_stderr,
705 'log', '--xml', *log_args)
706 if not parse:
707 return
709 entries = LogParser().parse(stdout)
710 for index in range(len(entries)):
711 entry = entries[index]
712 if expected_revprops != None:
713 entry.assert_revprops(expected_revprops[index])
714 if expected_paths != None:
715 entry.assert_changed_paths(expected_paths[index])
718 def verify_update(actual_output,
719 actual_mergeinfo_output,
720 actual_elision_output,
721 wc_dir_name,
722 output_tree,
723 mergeinfo_output_tree,
724 elision_output_tree,
725 disk_tree,
726 status_tree,
727 singleton_handler_a=None,
728 a_baton=None,
729 singleton_handler_b=None,
730 b_baton=None,
731 check_props=False):
732 """Verify update of WC_DIR_NAME.
734 The subcommand output (found in ACTUAL_OUTPUT, ACTUAL_MERGEINFO_OUTPUT,
735 and ACTUAL_ELISION_OUTPUT) will be verified against OUTPUT_TREE,
736 MERGEINFO_OUTPUT_TREE, and ELISION_OUTPUT_TREE respectively (if any of
737 these is provided, they may be None in which case a comparison is not
738 done). The working copy itself will be verified against DISK_TREE (if
739 provided), and the working copy's 'svn status' output will be verified
740 against STATUS_TREE (if provided). (This is a good way to check that
741 revision numbers were bumped.)
743 Return if successful, raise on failure.
745 For the comparison with DISK_TREE, pass SINGLETON_HANDLER_A and
746 SINGLETON_HANDLER_B to tree.compare_trees -- see that function's doc
747 string for more details. If CHECK_PROPS is set, then disk
748 comparison will examine props."""
750 if isinstance(actual_output, wc.State):
751 actual_output = actual_output.old_tree()
752 if isinstance(actual_mergeinfo_output, wc.State):
753 actual_mergeinfo_output = actual_mergeinfo_output.old_tree()
754 if isinstance(actual_elision_output, wc.State):
755 actual_elision_output = actual_elision_output.old_tree()
756 if isinstance(output_tree, wc.State):
757 output_tree = output_tree.old_tree()
758 if isinstance(mergeinfo_output_tree, wc.State):
759 mergeinfo_output_tree = mergeinfo_output_tree.old_tree()
760 if isinstance(elision_output_tree, wc.State):
761 elision_output_tree = elision_output_tree.old_tree()
762 if isinstance(disk_tree, wc.State):
763 disk_tree = disk_tree.old_tree()
764 if isinstance(status_tree, wc.State):
765 status_tree = status_tree.old_tree()
767 # Verify actual output against expected output.
768 if output_tree:
769 try:
770 tree.compare_trees("output", actual_output, output_tree)
771 except tree.SVNTreeUnequal:
772 _log_tree_state("ACTUAL OUTPUT TREE:", actual_output, wc_dir_name)
773 raise
775 # Verify actual mergeinfo recording output against expected output.
776 if mergeinfo_output_tree:
777 try:
778 tree.compare_trees("mergeinfo_output", actual_mergeinfo_output,
779 mergeinfo_output_tree)
780 except tree.SVNTreeUnequal:
781 _log_tree_state("ACTUAL MERGEINFO OUTPUT TREE:", actual_mergeinfo_output,
782 wc_dir_name)
783 raise
785 # Verify actual mergeinfo elision output against expected output.
786 if elision_output_tree:
787 try:
788 tree.compare_trees("elision_output", actual_elision_output,
789 elision_output_tree)
790 except tree.SVNTreeUnequal:
791 _log_tree_state("ACTUAL ELISION OUTPUT TREE:", actual_elision_output,
792 wc_dir_name)
793 raise
795 # Create a tree by scanning the working copy, and verify it
796 if disk_tree:
797 actual_disk = tree.build_tree_from_wc(wc_dir_name, check_props)
798 try:
799 tree.compare_trees("disk", actual_disk, disk_tree,
800 singleton_handler_a, a_baton,
801 singleton_handler_b, b_baton)
802 except tree.SVNTreeUnequal:
803 _log_tree_state("EXPECTED DISK TREE:", disk_tree)
804 _log_tree_state("ACTUAL DISK TREE:", actual_disk)
805 raise
807 # Verify via 'status' command too, if possible.
808 if status_tree:
809 run_and_verify_status(wc_dir_name, status_tree)
812 def verify_disk(wc_dir_name, disk_tree, check_props=False):
813 """Verify WC_DIR_NAME against DISK_TREE. If CHECK_PROPS is set,
814 the comparison will examin props. Returns if successful, raises on
815 failure."""
816 verify_update(None, None, None, wc_dir_name, None, None, None, disk_tree,
817 None, check_props=check_props)
821 def run_and_verify_update(wc_dir_name,
822 output_tree, disk_tree, status_tree,
823 error_re_string = None,
824 singleton_handler_a = None,
825 a_baton = None,
826 singleton_handler_b = None,
827 b_baton = None,
828 check_props = False,
829 *args):
831 """Update WC_DIR_NAME. *ARGS are any extra optional args to the
832 update subcommand. NOTE: If *ARGS is specified at all, explicit
833 target paths must be passed in *ARGS as well (or a default `.' will
834 be chosen by the 'svn' binary). This allows the caller to update
835 many items in a single working copy dir, but still verify the entire
836 working copy dir.
838 If ERROR_RE_STRING, the update must exit with error, and the error
839 message must match regular expression ERROR_RE_STRING.
841 Else if ERROR_RE_STRING is None, then:
843 If OUTPUT_TREE is not None, the subcommand output will be verified
844 against OUTPUT_TREE. If DISK_TREE is not None, the working copy
845 itself will be verified against DISK_TREE. If STATUS_TREE is not
846 None, the 'svn status' output will be verified against STATUS_TREE.
847 (This is a good way to check that revision numbers were bumped.)
849 For the DISK_TREE verification, SINGLETON_HANDLER_A and
850 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
851 function's doc string for more details.
853 If CHECK_PROPS is set, then disk comparison will examine props.
855 Return if successful, raise on failure."""
857 # Update and make a tree of the output.
858 if len(args):
859 exit_code, output, errput = main.run_svn(error_re_string, 'up', *args)
860 else:
861 exit_code, output, errput = main.run_svn(error_re_string,
862 'up', wc_dir_name,
863 *args)
865 if error_re_string:
866 rm = re.compile(error_re_string)
867 for line in errput:
868 match = rm.search(line)
869 if match:
870 return
871 raise main.SVNUnmatchedError
873 actual = wc.State.from_checkout(output)
874 verify_update(actual, None, None, wc_dir_name,
875 output_tree, None, None, disk_tree, status_tree,
876 singleton_handler_a, a_baton,
877 singleton_handler_b, b_baton,
878 check_props)
881 def run_and_parse_info(*args):
882 """Run 'svn info ARGS' and parse its output into a list of dicts,
883 one dict per reported node."""
885 # the returned array
886 all_infos = []
888 # per-target variables
889 iter_info = {}
890 prev_key = None
891 lock_comment_lines = 0
892 lock_comments = []
894 exit_code, output, errput = main.run_svn(None, 'info', *args)
896 for line in output:
897 line = line[:-1] # trim '\n'
899 if lock_comment_lines > 0:
900 # mop up any lock comment lines
901 lock_comments.append(line)
902 lock_comment_lines = lock_comment_lines - 1
903 if lock_comment_lines == 0:
904 iter_info[prev_key] = lock_comments
905 elif len(line) == 0:
906 # separator line between items
907 all_infos.append(iter_info)
908 iter_info = {}
909 prev_key = None
910 lock_comment_lines = 0
911 lock_comments = []
912 elif line[0].isspace():
913 # continuation line (for tree conflicts)
914 iter_info[prev_key] += line[1:]
915 else:
916 # normal line
917 key, value = line.split(':', 1)
919 if re.search(' \(\d+ lines?\)$', key):
920 # numbered continuation lines
921 match = re.match('^(.*) \((\d+) lines?\)$', key)
922 key = match.group(1)
923 lock_comment_lines = int(match.group(2))
924 elif len(value) > 1:
925 # normal normal line
926 iter_info[key] = value[1:]
927 else:
928 ### originally added for "Tree conflict:\n" lines;
929 ### tree-conflicts output format has changed since then
930 # continuation lines are implicit (prefixed by whitespace)
931 iter_info[key] = ''
932 prev_key = key
934 return all_infos
936 def run_and_verify_info(expected_infos, *args):
937 """Run 'svn info' with the arguments in *ARGS and verify the results
938 against expected_infos. The latter should be a list of dicts, one dict
939 per reported node, in the order in which the 'Path' fields of the output
940 will appear after sorting them as Python strings. (The dicts in
941 EXPECTED_INFOS, however, need not have a 'Path' key.)
943 In the dicts, each key is the before-the-colon part of the 'svn info' output,
944 and each value is either None (meaning that the key should *not* appear in
945 the 'svn info' output) or a regex matching the output value. Output lines
946 not matching a key in the dict are ignored.
948 Return if successful, raise on failure."""
950 actual_infos = run_and_parse_info(*args)
951 actual_infos.sort(key=lambda info: info['Path'])
953 try:
954 # zip() won't complain, so check this manually
955 if len(actual_infos) != len(expected_infos):
956 raise verify.SVNUnexpectedStdout(
957 "Expected %d infos, found %d infos"
958 % (len(expected_infos), len(actual_infos)))
960 for actual, expected in zip(actual_infos, expected_infos):
961 # compare dicts
962 for key, value in expected.items():
963 assert ':' not in key # caller passed impossible expectations?
964 if value is None and key in actual:
965 raise main.SVNLineUnequal("Found unexpected key '%s' with value '%s'"
966 % (key, actual[key]))
967 if value is not None and key not in actual:
968 raise main.SVNLineUnequal("Expected key '%s' (with value '%s') "
969 "not found" % (key, value))
970 if value is not None and not re.match(value, actual[key]):
971 raise verify.SVNUnexpectedStdout("Values of key '%s' don't match:\n"
972 " Expected: '%s' (regex)\n"
973 " Found: '%s' (string)\n"
974 % (key, value, actual[key]))
976 except:
977 sys.stderr.write("Bad 'svn info' output:\n"
978 " Received: %s\n"
979 " Expected: %s\n"
980 % (actual_infos, expected_infos))
981 raise
983 def run_and_verify_merge(dir, rev1, rev2, url1, url2,
984 output_tree,
985 mergeinfo_output_tree,
986 elision_output_tree,
987 disk_tree, status_tree, skip_tree,
988 error_re_string = None,
989 singleton_handler_a = None,
990 a_baton = None,
991 singleton_handler_b = None,
992 b_baton = None,
993 check_props = False,
994 dry_run = True,
995 *args):
996 """Run 'svn merge URL1@REV1 URL2@REV2 DIR' if URL2 is not None
997 (for a three-way merge between URLs and WC).
999 If URL2 is None, run 'svn merge -rREV1:REV2 URL1 DIR'. If both REV1
1000 and REV2 are None, leave off the '-r' argument.
1002 If ERROR_RE_STRING, the merge must exit with error, and the error
1003 message must match regular expression ERROR_RE_STRING.
1005 Else if ERROR_RE_STRING is None, then:
1007 The subcommand output will be verified against OUTPUT_TREE. Output
1008 related to mergeinfo notifications will be verified against
1009 MERGEINFO_OUTPUT_TREE if that is not None. Output related to mergeinfo
1010 elision will be verified against ELISION_OUTPUT_TREE if that is not None.
1011 The working copy itself will be verified against DISK_TREE. If optional
1012 STATUS_TREE is given, then 'svn status' output will be compared. The
1013 'skipped' merge output will be compared to SKIP_TREE.
1015 For the DISK_TREE verification, SINGLETON_HANDLER_A and
1016 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
1017 function's doc string for more details.
1019 If CHECK_PROPS is set, then disk comparison will examine props.
1021 If DRY_RUN is set then a --dry-run merge will be carried out first and
1022 the output compared with that of the full merge.
1024 Return if successful, raise on failure.
1026 *ARGS are any extra optional args to the merge subcommand.
1027 NOTE: If *ARGS is specified at all, an explicit target path must be passed
1028 in *ARGS as well. This allows the caller to merge into single items inside
1029 the working copy, but still verify the entire working copy dir. """
1031 merge_command = [ "merge" ]
1032 if url2:
1033 merge_command.extend((url1 + "@" + str(rev1), url2 + "@" + str(rev2)))
1034 else:
1035 if not (rev1 is None and rev2 is None):
1036 merge_command.append("-r" + str(rev1) + ":" + str(rev2))
1037 merge_command.append(url1)
1038 if len(args) == 0:
1039 merge_command.append(dir)
1040 merge_command = tuple(merge_command)
1042 if dry_run:
1043 pre_disk = tree.build_tree_from_wc(dir)
1044 dry_run_command = merge_command + ('--dry-run',)
1045 dry_run_command = dry_run_command + args
1046 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
1047 *dry_run_command)
1048 post_disk = tree.build_tree_from_wc(dir)
1049 try:
1050 tree.compare_trees("disk", post_disk, pre_disk)
1051 except tree.SVNTreeError:
1052 logger.warn("=============================================================")
1053 logger.warn("Dry-run merge altered working copy")
1054 logger.warn("=============================================================")
1055 raise
1058 # Update and make a tree of the output.
1059 merge_command = merge_command + args
1060 exit_code, out, err = main.run_svn(error_re_string, *merge_command)
1062 if error_re_string:
1063 if not error_re_string.startswith(".*"):
1064 error_re_string = ".*(" + error_re_string + ")"
1065 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1066 verify.verify_outputs(None, None, err, None, expected_err)
1067 return
1068 elif err:
1069 raise verify.SVNUnexpectedStderr(err)
1071 # Split the output into that related to application of the actual diff
1072 # and that related to the recording of mergeinfo describing the merge.
1073 merge_diff_out = []
1074 mergeinfo_notification_out = []
1075 mergeinfo_elision_out = []
1076 mergeinfo_notifications = False
1077 elision_notifications = False
1078 for line in out:
1079 if line.startswith('--- Recording'):
1080 mergeinfo_notifications = True
1081 elision_notifications = False
1082 elif line.startswith('--- Eliding'):
1083 mergeinfo_notifications = False
1084 elision_notifications = True
1085 elif line.startswith('--- Merging') or \
1086 line.startswith('--- Reverse-merging') or \
1087 line.startswith('Summary of conflicts') or \
1088 line.startswith('Skipped missing target'):
1089 mergeinfo_notifications = False
1090 elision_notifications = False
1092 if mergeinfo_notifications:
1093 mergeinfo_notification_out.append(line)
1094 elif elision_notifications:
1095 mergeinfo_elision_out.append(line)
1096 else:
1097 merge_diff_out.append(line)
1099 if dry_run and merge_diff_out != out_dry:
1100 # Due to the way ra_serf works, it's possible that the dry-run and
1101 # real merge operations did the same thing, but the output came in
1102 # a different order. Let's see if maybe that's the case by comparing
1103 # the outputs as unordered sets rather than as lists.
1105 # This now happens for other RA layers with modern APR because the
1106 # hash order now varies.
1108 # The different orders of the real and dry-run merges may cause
1109 # the "Merging rX through rY into" lines to be duplicated a
1110 # different number of times in the two outputs. The list-set
1111 # conversion removes duplicates so these differences are ignored.
1112 # It also removes "U some/path" duplicate lines. Perhaps we
1113 # should avoid that?
1114 out_copy = set(merge_diff_out[:])
1115 out_dry_copy = set(out_dry[:])
1117 if out_copy != out_dry_copy:
1118 logger.warn("=============================================================")
1119 logger.warn("Merge outputs differ")
1120 logger.warn("The dry-run merge output:")
1121 for x in out_dry:
1122 logger.warn(x)
1123 logger.warn("The full merge output:")
1124 for x in out:
1125 logger.warn(x)
1126 logger.warn("=============================================================")
1127 raise main.SVNUnmatchedError
1129 def missing_skip(a, b):
1130 logger.warn("=============================================================")
1131 logger.warn("Merge failed to skip: %s", a.path)
1132 logger.warn("=============================================================")
1133 raise Failure
1134 def extra_skip(a, b):
1135 logger.warn("=============================================================")
1136 logger.warn("Merge unexpectedly skipped: %s", a.path)
1137 logger.warn("=============================================================")
1138 raise Failure
1140 myskiptree = tree.build_tree_from_skipped(out)
1141 if isinstance(skip_tree, wc.State):
1142 skip_tree = skip_tree.old_tree()
1143 try:
1144 tree.compare_trees("skip", myskiptree, skip_tree,
1145 extra_skip, None, missing_skip, None)
1146 except tree.SVNTreeUnequal:
1147 _log_tree_state("ACTUAL SKIP TREE:", myskiptree, dir)
1148 raise
1150 actual_diff = svntest.wc.State.from_checkout(merge_diff_out, False)
1151 actual_mergeinfo = svntest.wc.State.from_checkout(mergeinfo_notification_out,
1152 False)
1153 actual_elision = svntest.wc.State.from_checkout(mergeinfo_elision_out,
1154 False)
1155 verify_update(actual_diff, actual_mergeinfo, actual_elision, dir,
1156 output_tree, mergeinfo_output_tree, elision_output_tree,
1157 disk_tree, status_tree,
1158 singleton_handler_a, a_baton,
1159 singleton_handler_b, b_baton,
1160 check_props)
1163 def run_and_verify_patch(dir, patch_path,
1164 output_tree, disk_tree, status_tree, skip_tree,
1165 error_re_string=None,
1166 check_props=False,
1167 dry_run=True,
1168 *args):
1169 """Run 'svn patch patch_path DIR'.
1171 If ERROR_RE_STRING, 'svn patch' must exit with error, and the error
1172 message must match regular expression ERROR_RE_STRING.
1174 Else if ERROR_RE_STRING is None, then:
1176 The subcommand output will be verified against OUTPUT_TREE, and the
1177 working copy itself will be verified against DISK_TREE. If optional
1178 STATUS_TREE is given, then 'svn status' output will be compared.
1179 The 'skipped' merge output will be compared to SKIP_TREE.
1181 If CHECK_PROPS is set, then disk comparison will examine props.
1183 If DRY_RUN is set then a --dry-run patch will be carried out first and
1184 the output compared with that of the full patch application.
1186 Returns if successful, raises on failure."""
1188 patch_command = [ "patch" ]
1189 patch_command.append(patch_path)
1190 patch_command.append(dir)
1191 patch_command = tuple(patch_command)
1193 if dry_run:
1194 pre_disk = tree.build_tree_from_wc(dir)
1195 dry_run_command = patch_command + ('--dry-run',)
1196 dry_run_command = dry_run_command + args
1197 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
1198 *dry_run_command)
1199 post_disk = tree.build_tree_from_wc(dir)
1200 try:
1201 tree.compare_trees("disk", post_disk, pre_disk)
1202 except tree.SVNTreeError:
1203 logger.warn("=============================================================")
1204 logger.warn("'svn patch --dry-run' altered working copy")
1205 logger.warn("=============================================================")
1206 raise
1208 # Update and make a tree of the output.
1209 patch_command = patch_command + args
1210 exit_code, out, err = main.run_svn(True, *patch_command)
1212 if error_re_string:
1213 rm = re.compile(error_re_string)
1214 match = None
1215 for line in err:
1216 match = rm.search(line)
1217 if match:
1218 break
1219 if not match:
1220 raise main.SVNUnmatchedError
1221 elif err:
1222 logger.warn("UNEXPECTED STDERR:")
1223 for x in err:
1224 logger.warn(x)
1225 raise verify.SVNUnexpectedStderr
1227 if dry_run and out != out_dry:
1228 # APR hash order means the output order can vary, assume everything is OK
1229 # if only the order changes.
1230 out_dry_expected = svntest.verify.UnorderedOutput(out)
1231 verify.compare_and_display_lines('dry-run patch output not as expected',
1232 '', out_dry_expected, out_dry)
1234 def missing_skip(a, b):
1235 logger.warn("=============================================================")
1236 logger.warn("'svn patch' failed to skip: %s", a.path)
1237 logger.warn("=============================================================")
1238 raise Failure
1239 def extra_skip(a, b):
1240 logger.warn("=============================================================")
1241 logger.warn("'svn patch' unexpectedly skipped: %s", a.path)
1242 logger.warn("=============================================================")
1243 raise Failure
1245 myskiptree = tree.build_tree_from_skipped(out)
1246 if isinstance(skip_tree, wc.State):
1247 skip_tree = skip_tree.old_tree()
1248 tree.compare_trees("skip", myskiptree, skip_tree,
1249 extra_skip, None, missing_skip, None)
1251 mytree = tree.build_tree_from_checkout(out, 0)
1253 # when the expected output is a list, we want a line-by-line
1254 # comparison to happen instead of a tree comparison
1255 if (isinstance(output_tree, list)
1256 or isinstance(output_tree, verify.UnorderedOutput)):
1257 verify.verify_outputs(None, out, err, output_tree, error_re_string)
1258 output_tree = None
1260 verify_update(mytree, None, None, dir,
1261 output_tree, None, None, disk_tree, status_tree,
1262 check_props=check_props)
1265 def run_and_verify_mergeinfo(error_re_string = None,
1266 expected_output = [],
1267 *args):
1268 """Run 'svn mergeinfo ARGS', and compare the result against
1269 EXPECTED_OUTPUT, a list of string representations of revisions
1270 expected in the output. Raise an exception if an unexpected
1271 output is encountered."""
1273 mergeinfo_command = ["mergeinfo"]
1274 mergeinfo_command.extend(args)
1275 exit_code, out, err = main.run_svn(error_re_string, *mergeinfo_command)
1277 if error_re_string:
1278 if not error_re_string.startswith(".*"):
1279 error_re_string = ".*(" + error_re_string + ")"
1280 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1281 verify.verify_outputs(None, None, err, None, expected_err)
1282 return
1284 out = [_f for _f in [x.rstrip()[1:] for x in out] if _f]
1285 expected_output.sort()
1286 extra_out = []
1287 if out != expected_output:
1288 exp_hash = dict.fromkeys(expected_output)
1289 for rev in out:
1290 if rev in exp_hash:
1291 del(exp_hash[rev])
1292 else:
1293 extra_out.append(rev)
1294 extra_exp = list(exp_hash.keys())
1295 raise Exception("Unexpected 'svn mergeinfo' output:\n"
1296 " expected but not found: %s\n"
1297 " found but not expected: %s"
1298 % (', '.join([str(x) for x in extra_exp]),
1299 ', '.join([str(x) for x in extra_out])))
1302 def run_and_verify_switch(wc_dir_name,
1303 wc_target,
1304 switch_url,
1305 output_tree, disk_tree, status_tree,
1306 error_re_string = None,
1307 singleton_handler_a = None,
1308 a_baton = None,
1309 singleton_handler_b = None,
1310 b_baton = None,
1311 check_props = False,
1312 *args):
1314 """Switch WC_TARGET (in working copy dir WC_DIR_NAME) to SWITCH_URL.
1316 If ERROR_RE_STRING, the switch must exit with error, and the error
1317 message must match regular expression ERROR_RE_STRING.
1319 Else if ERROR_RE_STRING is None, then:
1321 The subcommand output will be verified against OUTPUT_TREE, and the
1322 working copy itself will be verified against DISK_TREE. If optional
1323 STATUS_TREE is given, then 'svn status' output will be
1324 compared. (This is a good way to check that revision numbers were
1325 bumped.)
1327 For the DISK_TREE verification, SINGLETON_HANDLER_A and
1328 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
1329 function's doc string for more details.
1331 If CHECK_PROPS is set, then disk comparison will examine props.
1333 Return if successful, raise on failure."""
1335 # Update and make a tree of the output.
1336 exit_code, output, errput = main.run_svn(error_re_string, 'switch',
1337 switch_url, wc_target, *args)
1339 if error_re_string:
1340 if not error_re_string.startswith(".*"):
1341 error_re_string = ".*(" + error_re_string + ")"
1342 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1343 verify.verify_outputs(None, None, errput, None, expected_err)
1344 return
1345 elif errput:
1346 raise verify.SVNUnexpectedStderr(err)
1348 actual = wc.State.from_checkout(output)
1350 verify_update(actual, None, None, wc_dir_name,
1351 output_tree, None, None, disk_tree, status_tree,
1352 singleton_handler_a, a_baton,
1353 singleton_handler_b, b_baton,
1354 check_props)
1356 def process_output_for_commit(output):
1357 """Helper for run_and_verify_commit(), also used in the factory."""
1358 # Remove the final output line, and verify that the commit succeeded.
1359 lastline = ""
1360 rest = []
1362 def external_removal(line):
1363 return line.startswith('Removing external') \
1364 or line.startswith('Removed external')
1366 if len(output):
1367 lastline = output.pop().strip()
1369 while len(output) and external_removal(lastline):
1370 rest.append(lastline)
1371 lastline = output.pop().strip()
1373 cm = re.compile("(Committed|Imported) revision [0-9]+.")
1374 match = cm.search(lastline)
1375 if not match:
1376 logger.warn("ERROR: commit did not succeed.")
1377 logger.warn("The final line from 'svn ci' was:")
1378 logger.warn(lastline)
1379 raise main.SVNCommitFailure
1381 # The new 'final' line in the output is either a regular line that
1382 # mentions {Adding, Deleting, Sending, ...}, or it could be a line
1383 # that says "Transmitting file data ...". If the latter case, we
1384 # want to remove the line from the output; it should be ignored when
1385 # building a tree.
1386 if len(output):
1387 lastline = output.pop()
1389 tm = re.compile("Transmitting file data.+")
1390 match = tm.search(lastline)
1391 if not match:
1392 # whoops, it was important output, put it back.
1393 output.append(lastline)
1395 if len(rest):
1396 output.extend(rest)
1398 return output
1401 def run_and_verify_commit(wc_dir_name, output_tree, status_tree,
1402 error_re_string = None,
1403 *args):
1404 """Commit and verify results within working copy WC_DIR_NAME,
1405 sending ARGS to the commit subcommand.
1407 The subcommand output will be verified against OUTPUT_TREE. If
1408 optional STATUS_TREE is given, then 'svn status' output will
1409 be compared. (This is a good way to check that revision numbers
1410 were bumped.)
1412 If ERROR_RE_STRING is None, the commit must not exit with error. If
1413 ERROR_RE_STRING is a string, the commit must exit with error, and
1414 the error message must match regular expression ERROR_RE_STRING.
1416 Return if successful, raise on failure."""
1418 if isinstance(output_tree, wc.State):
1419 output_tree = output_tree.old_tree()
1420 if isinstance(status_tree, wc.State):
1421 status_tree = status_tree.old_tree()
1423 # Commit.
1424 if '-m' not in args and '-F' not in args:
1425 args = list(args) + ['-m', 'log msg']
1426 exit_code, output, errput = main.run_svn(error_re_string, 'ci',
1427 *args)
1429 if error_re_string:
1430 if not error_re_string.startswith(".*"):
1431 error_re_string = ".*(" + error_re_string + ")"
1432 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1433 verify.verify_outputs(None, None, errput, None, expected_err)
1434 return
1436 # Else not expecting error:
1438 # Convert the output into a tree.
1439 output = process_output_for_commit(output)
1440 actual = tree.build_tree_from_commit(output)
1442 # Verify actual output against expected output.
1443 try:
1444 tree.compare_trees("output", actual, output_tree)
1445 except tree.SVNTreeError:
1446 verify.display_trees("Output of commit is unexpected",
1447 "OUTPUT TREE", output_tree, actual)
1448 _log_tree_state("ACTUAL OUTPUT TREE:", actual, wc_dir_name)
1449 raise
1451 # Verify via 'status' command too, if possible.
1452 if status_tree:
1453 run_and_verify_status(wc_dir_name, status_tree)
1456 # This function always passes '-q' to the status command, which
1457 # suppresses the printing of any unversioned or nonexistent items.
1458 def run_and_verify_status(wc_dir_name, output_tree,
1459 singleton_handler_a = None,
1460 a_baton = None,
1461 singleton_handler_b = None,
1462 b_baton = None):
1463 """Run 'status' on WC_DIR_NAME and compare it with the
1464 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1465 be passed to tree.compare_trees - see that function's doc string for
1466 more details.
1467 Returns on success, raises on failure."""
1469 if isinstance(output_tree, wc.State):
1470 output_state = output_tree
1471 output_tree = output_tree.old_tree()
1472 else:
1473 output_state = None
1475 exit_code, output, errput = main.run_svn(None, 'status', '-v', '-u', '-q',
1476 wc_dir_name)
1478 actual = tree.build_tree_from_status(output)
1480 # Verify actual output against expected output.
1481 try:
1482 tree.compare_trees("status", actual, output_tree,
1483 singleton_handler_a, a_baton,
1484 singleton_handler_b, b_baton)
1485 except tree.SVNTreeError:
1486 verify.display_trees(None, 'STATUS OUTPUT TREE', output_tree, actual)
1487 _log_tree_state("ACTUAL STATUS TREE:", actual, wc_dir_name)
1488 raise
1490 # if we have an output State, and we can/are-allowed to create an
1491 # entries-based State, then compare the two.
1492 if output_state:
1493 entries_state = wc.State.from_entries(wc_dir_name)
1494 if entries_state:
1495 tweaked = output_state.copy()
1496 tweaked.tweak_for_entries_compare()
1497 try:
1498 tweaked.compare_and_display('entries', entries_state)
1499 except tree.SVNTreeUnequal:
1500 ### do something more
1501 raise
1504 # A variant of previous func, but doesn't pass '-q'. This allows us
1505 # to verify unversioned or nonexistent items in the list.
1506 def run_and_verify_unquiet_status(wc_dir_name, status_tree):
1507 """Run 'status' on WC_DIR_NAME and compare it with the
1508 expected STATUS_TREE.
1509 Returns on success, raises on failure."""
1511 if isinstance(status_tree, wc.State):
1512 status_tree = status_tree.old_tree()
1514 exit_code, output, errput = main.run_svn(None, 'status', '-v',
1515 '-u', wc_dir_name)
1517 actual = tree.build_tree_from_status(output)
1519 # Verify actual output against expected output.
1520 try:
1521 tree.compare_trees("UNQUIET STATUS", actual, status_tree)
1522 except tree.SVNTreeError:
1523 _log_tree_state("ACTUAL UNQUIET STATUS TREE:", actual, wc_dir_name)
1524 raise
1526 def run_and_verify_status_xml(expected_entries = [],
1527 *args):
1528 """ Run 'status --xml' with arguments *ARGS. If successful the output
1529 is parsed into an XML document and will be verified by comparing against
1530 EXPECTED_ENTRIES.
1533 exit_code, output, errput = run_and_verify_svn(None, None, [],
1534 'status', '--xml', *args)
1536 if len(errput) > 0:
1537 raise Failure
1539 doc = parseString(''.join(output))
1540 entries = doc.getElementsByTagName('entry')
1542 def getText(nodelist):
1543 rc = []
1544 for node in nodelist:
1545 if node.nodeType == node.TEXT_NODE:
1546 rc.append(node.data)
1547 return ''.join(rc)
1549 actual_entries = {}
1550 for entry in entries:
1551 wcstatus = entry.getElementsByTagName('wc-status')[0]
1552 commit = entry.getElementsByTagName('commit')
1553 author = entry.getElementsByTagName('author')
1554 rstatus = entry.getElementsByTagName('repos-status')
1556 actual_entry = {'wcprops' : wcstatus.getAttribute('props'),
1557 'wcitem' : wcstatus.getAttribute('item'),
1559 if wcstatus.hasAttribute('revision'):
1560 actual_entry['wcrev'] = wcstatus.getAttribute('revision')
1561 if (commit):
1562 actual_entry['crev'] = commit[0].getAttribute('revision')
1563 if (author):
1564 actual_entry['author'] = getText(author[0].childNodes)
1565 if (rstatus):
1566 actual_entry['rprops'] = rstatus[0].getAttribute('props')
1567 actual_entry['ritem'] = rstatus[0].getAttribute('item')
1569 actual_entries[entry.getAttribute('path')] = actual_entry
1571 if expected_entries != actual_entries:
1572 raise Failure('\n' + '\n'.join(difflib.ndiff(
1573 pprint.pformat(expected_entries).splitlines(),
1574 pprint.pformat(actual_entries).splitlines())))
1576 def run_and_verify_diff_summarize_xml(error_re_string = [],
1577 expected_prefix = None,
1578 expected_paths = [],
1579 expected_items = [],
1580 expected_props = [],
1581 expected_kinds = [],
1582 *args):
1583 """Run 'diff --summarize --xml' with the arguments *ARGS, which should
1584 contain all arguments beyond for your 'diff --summarize --xml' omitting
1585 said arguments. EXPECTED_PREFIX will store a "common" path prefix
1586 expected to be at the beginning of each summarized path. If
1587 EXPECTED_PREFIX is None, then EXPECTED_PATHS will need to be exactly
1588 as 'svn diff --summarize --xml' will output. If ERROR_RE_STRING, the
1589 command must exit with error, and the error message must match regular
1590 expression ERROR_RE_STRING.
1592 Else if ERROR_RE_STRING is None, the subcommand output will be parsed
1593 into an XML document and will then be verified by comparing the parsed
1594 output to the contents in the EXPECTED_PATHS, EXPECTED_ITEMS,
1595 EXPECTED_PROPS and EXPECTED_KINDS. Returns on success, raises
1596 on failure."""
1598 exit_code, output, errput = run_and_verify_svn(None, None, error_re_string,
1599 'diff', '--summarize',
1600 '--xml', *args)
1603 # Return if errors are present since they were expected
1604 if len(errput) > 0:
1605 return
1607 doc = parseString(''.join(output))
1608 paths = doc.getElementsByTagName("path")
1609 items = expected_items
1610 kinds = expected_kinds
1612 for path in paths:
1613 modified_path = path.childNodes[0].data
1615 if (expected_prefix is not None
1616 and modified_path.find(expected_prefix) == 0):
1617 modified_path = modified_path.replace(expected_prefix, '')[1:].strip()
1619 # Workaround single-object diff
1620 if len(modified_path) == 0:
1621 modified_path = path.childNodes[0].data.split(os.sep)[-1]
1623 # From here on, we use '/' as path separator.
1624 if os.sep != "/":
1625 modified_path = modified_path.replace(os.sep, "/")
1627 if modified_path not in expected_paths:
1628 logger.warn("ERROR: %s not expected in the changed paths.", modified_path)
1629 raise Failure
1631 index = expected_paths.index(modified_path)
1632 expected_item = items[index]
1633 expected_kind = kinds[index]
1634 expected_prop = expected_props[index]
1635 actual_item = path.getAttribute('item')
1636 actual_kind = path.getAttribute('kind')
1637 actual_prop = path.getAttribute('props')
1639 if expected_item != actual_item:
1640 logger.warn("ERROR: expected: %s actual: %s", expected_item, actual_item)
1641 raise Failure
1643 if expected_kind != actual_kind:
1644 logger.warn("ERROR: expected: %s actual: %s", expected_kind, actual_kind)
1645 raise Failure
1647 if expected_prop != actual_prop:
1648 logger.warn("ERROR: expected: %s actual: %s", expected_prop, actual_prop)
1649 raise Failure
1651 def run_and_verify_diff_summarize(output_tree, *args):
1652 """Run 'diff --summarize' with the arguments *ARGS.
1654 The subcommand output will be verified against OUTPUT_TREE. Returns
1655 on success, raises on failure.
1658 if isinstance(output_tree, wc.State):
1659 output_tree = output_tree.old_tree()
1661 exit_code, output, errput = main.run_svn(None, 'diff', '--summarize',
1662 *args)
1664 actual = tree.build_tree_from_diff_summarize(output)
1666 # Verify actual output against expected output.
1667 try:
1668 tree.compare_trees("output", actual, output_tree)
1669 except tree.SVNTreeError:
1670 verify.display_trees(None, 'DIFF OUTPUT TREE', output_tree, actual)
1671 _log_tree_state("ACTUAL DIFF OUTPUT TREE:", actual)
1672 raise
1674 def run_and_validate_lock(path, username):
1675 """`svn lock' the given path and validate the contents of the lock.
1676 Use the given username. This is important because locks are
1677 user specific."""
1679 comment = "Locking path:%s." % path
1681 # lock the path
1682 run_and_verify_svn(None, ".*locked by user", [], 'lock',
1683 '--username', username,
1684 '-m', comment, path)
1686 # Run info and check that we get the lock fields.
1687 exit_code, output, err = run_and_verify_svn(None, None, [],
1688 'info','-R',
1689 path)
1691 ### TODO: Leverage RegexOuput([...], match_all=True) here.
1692 # prepare the regexs to compare against
1693 token_re = re.compile(".*?Lock Token: opaquelocktoken:.*?", re.DOTALL)
1694 author_re = re.compile(".*?Lock Owner: %s\n.*?" % username, re.DOTALL)
1695 created_re = re.compile(".*?Lock Created:.*?", re.DOTALL)
1696 comment_re = re.compile(".*?%s\n.*?" % re.escape(comment), re.DOTALL)
1697 # join all output lines into one
1698 output = "".join(output)
1699 # Fail even if one regex does not match
1700 if ( not (token_re.match(output) and
1701 author_re.match(output) and
1702 created_re.match(output) and
1703 comment_re.match(output))):
1704 raise Failure
1706 def _run_and_verify_resolve(cmd, expected_paths, *args):
1707 """Run "svn CMD" (where CMD is 'resolve' or 'resolved') with arguments
1708 ARGS, and verify that it resolves the paths in EXPECTED_PATHS and no others.
1709 If no ARGS are specified, use the elements of EXPECTED_PATHS as the
1710 arguments."""
1711 # TODO: verify that the status of PATHS changes accordingly.
1712 if len(args) == 0:
1713 args = expected_paths
1714 expected_output = verify.UnorderedOutput([
1715 "Resolved conflicted state of '" + path + "'\n" for path in
1716 expected_paths])
1717 run_and_verify_svn(None, expected_output, [],
1718 cmd, *args)
1720 def run_and_verify_resolve(expected_paths, *args):
1721 """Run "svn resolve" with arguments ARGS, and verify that it resolves the
1722 paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the
1723 elements of EXPECTED_PATHS as the arguments."""
1724 _run_and_verify_resolve('resolve', expected_paths, *args)
1726 def run_and_verify_resolved(expected_paths, *args):
1727 """Run "svn resolved" with arguments ARGS, and verify that it resolves the
1728 paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the
1729 elements of EXPECTED_PATHS as the arguments."""
1730 _run_and_verify_resolve('resolved', expected_paths, *args)
1732 def run_and_verify_revert(expected_paths, *args):
1733 """Run "svn revert" with arguments ARGS, and verify that it reverts
1734 the paths in EXPECTED_PATHS and no others. If no ARGS are
1735 specified, use the elements of EXPECTED_PATHS as the arguments."""
1736 if len(args) == 0:
1737 args = expected_paths
1738 expected_output = verify.UnorderedOutput([
1739 "Reverted '" + path + "'\n" for path in
1740 expected_paths])
1741 run_and_verify_svn(None, expected_output, [],
1742 "revert", *args)
1745 ######################################################################
1746 # Other general utilities
1749 # This allows a test to *quickly* bootstrap itself.
1750 def make_repo_and_wc(sbox, create_wc = True, read_only = False,
1751 minor_version = None):
1752 """Create a fresh 'Greek Tree' repository and check out a WC from it.
1754 If READ_ONLY is False, a dedicated repository will be created, at the path
1755 SBOX.repo_dir. If READ_ONLY is True, the pristine repository will be used.
1756 In either case, SBOX.repo_url is assumed to point to the repository that
1757 will be used.
1759 If create_wc is True, a dedicated working copy will be checked out from
1760 the repository, at the path SBOX.wc_dir.
1762 Returns on success, raises on failure."""
1764 # Create (or copy afresh) a new repos with a greek tree in it.
1765 if not read_only:
1766 guarantee_greek_repository(sbox.repo_dir, minor_version)
1768 if create_wc:
1769 # Generate the expected output tree.
1770 expected_output = main.greek_state.copy()
1771 expected_output.wc_dir = sbox.wc_dir
1772 expected_output.tweak(status='A ', contents=None)
1774 # Generate an expected wc tree.
1775 expected_wc = main.greek_state
1777 # Do a checkout, and verify the resulting output and disk contents.
1778 run_and_verify_checkout(sbox.repo_url,
1779 sbox.wc_dir,
1780 expected_output,
1781 expected_wc)
1782 else:
1783 # just make sure the parent folder of our working copy is created
1784 try:
1785 os.mkdir(main.general_wc_dir)
1786 except OSError, err:
1787 if err.errno != errno.EEXIST:
1788 raise
1790 # Duplicate a working copy or other dir.
1791 def duplicate_dir(wc_name, wc_copy_name):
1792 """Copy the working copy WC_NAME to WC_COPY_NAME. Overwrite any
1793 existing tree at that location."""
1795 main.safe_rmtree(wc_copy_name)
1796 shutil.copytree(wc_name, wc_copy_name)
1800 def get_virginal_state(wc_dir, rev):
1801 "Return a virginal greek tree state for a WC and repos at revision REV."
1803 rev = str(rev) ### maybe switch rev to an integer?
1805 # copy the greek tree, shift it to the new wc_dir, insert a root elem,
1806 # then tweak all values
1807 state = main.greek_state.copy()
1808 state.wc_dir = wc_dir
1809 state.desc[''] = wc.StateItem()
1810 state.tweak(contents=None, status=' ', wc_rev=rev)
1812 return state
1814 # Cheap administrative directory locking
1815 def lock_admin_dir(wc_dir, recursive=False):
1816 "Lock a SVN administrative directory"
1817 db, root_path, relpath = wc.open_wc_db(wc_dir)
1819 svntest.main.run_wc_lock_tester(recursive, wc_dir)
1821 def set_incomplete(wc_dir, revision):
1822 "Make wc_dir incomplete at revision"
1824 svntest.main.run_wc_incomplete_tester(wc_dir, revision)
1826 def get_wc_uuid(wc_dir):
1827 "Return the UUID of the working copy at WC_DIR."
1828 return run_and_parse_info(wc_dir)[0]['Repository UUID']
1830 def get_wc_base_rev(wc_dir):
1831 "Return the BASE revision of the working copy at WC_DIR."
1832 return run_and_parse_info(wc_dir)[0]['Revision']
1834 def hook_failure_message(hook_name):
1835 """Return the error message that the client prints for failure of the
1836 specified hook HOOK_NAME. The wording changed with Subversion 1.5."""
1837 if svntest.main.options.server_minor_version < 5:
1838 return "'%s' hook failed with error output:\n" % hook_name
1839 else:
1840 if hook_name in ["start-commit", "pre-commit"]:
1841 action = "Commit"
1842 elif hook_name == "pre-revprop-change":
1843 action = "Revprop change"
1844 elif hook_name == "pre-lock":
1845 action = "Lock"
1846 elif hook_name == "pre-unlock":
1847 action = "Unlock"
1848 else:
1849 action = None
1850 if action is None:
1851 message = "%s hook failed (exit code 1)" % (hook_name,)
1852 else:
1853 message = "%s blocked by %s hook (exit code 1)" % (action, hook_name)
1854 return message + " with output:\n"
1856 def create_failing_hook(repo_dir, hook_name, text):
1857 """Create a HOOK_NAME hook in the repository at REPO_DIR that prints
1858 TEXT to stderr and exits with an error."""
1860 hook_path = os.path.join(repo_dir, 'hooks', hook_name)
1861 # Embed the text carefully: it might include characters like "%" and "'".
1862 main.create_python_hook_script(hook_path, 'import sys\n'
1863 'sys.stderr.write(' + repr(text) + ')\n'
1864 'sys.exit(1)\n')
1866 def enable_revprop_changes(repo_dir):
1867 """Enable revprop changes in the repository at REPO_DIR by creating a
1868 pre-revprop-change hook script and (if appropriate) making it executable."""
1870 hook_path = main.get_pre_revprop_change_hook_path(repo_dir)
1871 main.create_python_hook_script(hook_path, 'import sys; sys.exit(0)',
1872 cmd_alternative='@exit 0')
1874 def disable_revprop_changes(repo_dir):
1875 """Disable revprop changes in the repository at REPO_DIR by creating a
1876 pre-revprop-change hook script that prints "pre-revprop-change" followed
1877 by its arguments, and returns an error."""
1879 hook_path = main.get_pre_revprop_change_hook_path(repo_dir)
1880 main.create_python_hook_script(hook_path,
1881 'import sys\n'
1882 'sys.stderr.write("pre-revprop-change %s" %'
1883 ' " ".join(sys.argv[1:]))\n'
1884 'sys.exit(1)\n',
1885 cmd_alternative=
1886 '@echo pre-revprop-change %* 1>&2\n'
1887 '@exit 1\n')
1889 def create_failing_post_commit_hook(repo_dir):
1890 """Create a post-commit hook script in the repository at REPO_DIR that always
1891 reports an error."""
1893 hook_path = main.get_post_commit_hook_path(repo_dir)
1894 main.create_python_hook_script(hook_path, 'import sys\n'
1895 'sys.stderr.write("Post-commit hook failed")\n'
1896 'sys.exit(1)\n',
1897 cmd_alternative=
1898 '@echo Post-commit hook failed 1>&2\n'
1899 '@exit 1\n')
1901 # set_prop can be used for properties with NULL characters which are not
1902 # handled correctly when passed to subprocess.Popen() and values like "*"
1903 # which are not handled correctly on Windows.
1904 def set_prop(name, value, path, expected_re_string=None):
1905 """Set a property with specified value"""
1906 if value and (value[0] == '-' or '\x00' in value or sys.platform == 'win32'):
1907 from tempfile import mkstemp
1908 (fd, value_file_path) = mkstemp()
1909 os.close(fd)
1910 value_file = open(value_file_path, 'wb')
1911 value_file.write(value)
1912 value_file.flush()
1913 value_file.close()
1914 exit_code, out, err = main.run_svn(expected_re_string, 'propset',
1915 '-F', value_file_path, name, path)
1916 os.remove(value_file_path)
1917 else:
1918 exit_code, out, err = main.run_svn(expected_re_string, 'propset',
1919 name, value, path)
1920 if expected_re_string:
1921 if not expected_re_string.startswith(".*"):
1922 expected_re_string = ".*(" + expected_re_string + ")"
1923 expected_err = verify.RegexOutput(expected_re_string, match_all=False)
1924 verify.verify_outputs(None, None, err, None, expected_err)
1926 def check_prop(name, path, exp_out, revprop=None):
1927 """Verify that property NAME on PATH has a value of EXP_OUT.
1928 If REVPROP is not None, then it is a revision number and
1929 a revision property is sought."""
1930 if revprop is not None:
1931 revprop_options = ['--revprop', '-r', revprop]
1932 else:
1933 revprop_options = []
1934 # Not using run_svn because binary_mode must be set
1935 exit_code, out, err = main.run_command(main.svn_binary, None, 1, 'pg',
1936 '--strict', name, path,
1937 '--config-dir',
1938 main.default_config_dir,
1939 '--username', main.wc_author,
1940 '--password', main.wc_passwd,
1941 *revprop_options)
1942 if out != exp_out:
1943 logger.warn("svn pg --strict %s output does not match expected.", name)
1944 logger.warn("Expected standard output: %s\n", exp_out)
1945 logger.warn("Actual standard output: %s\n", out)
1946 raise Failure
1948 def fill_file_with_lines(wc_path, line_nbr, line_descrip=None,
1949 append=True):
1950 """Change the file at WC_PATH (adding some lines), and return its
1951 new contents. LINE_NBR indicates the line number at which the new
1952 contents should assume that it's being appended. LINE_DESCRIP is
1953 something like 'This is line' (the default) or 'Conflicting line'."""
1955 if line_descrip is None:
1956 line_descrip = "This is line"
1958 # Generate the new contents for the file.
1959 contents = ""
1960 for n in range(line_nbr, line_nbr + 3):
1961 contents = contents + line_descrip + " " + repr(n) + " in '" + \
1962 os.path.basename(wc_path) + "'.\n"
1964 # Write the new contents to the file.
1965 if append:
1966 main.file_append(wc_path, contents)
1967 else:
1968 main.file_write(wc_path, contents)
1970 return contents
1972 def inject_conflict_into_wc(sbox, state_path, file_path,
1973 expected_disk, expected_status, merged_rev):
1974 """Create a conflict at FILE_PATH by replacing its contents,
1975 committing the change, backdating it to its previous revision,
1976 changing its contents again, then updating it to merge in the
1977 previous change."""
1979 wc_dir = sbox.wc_dir
1981 # Make a change to the file.
1982 contents = fill_file_with_lines(file_path, 1, "This is line", append=False)
1984 # Commit the changed file, first taking note of the current revision.
1985 prev_rev = expected_status.desc[state_path].wc_rev
1986 expected_output = wc.State(wc_dir, {
1987 state_path : wc.StateItem(verb='Sending'),
1989 if expected_status:
1990 expected_status.tweak(state_path, wc_rev=merged_rev)
1991 run_and_verify_commit(wc_dir, expected_output, expected_status,
1992 None, file_path)
1994 # Backdate the file.
1995 exit_code, output, errput = main.run_svn(None, "up", "-r", str(prev_rev),
1996 file_path)
1997 if expected_status:
1998 expected_status.tweak(state_path, wc_rev=prev_rev)
2000 # Make a conflicting change to the file, and backdate the file.
2001 conflicting_contents = fill_file_with_lines(file_path, 1, "Conflicting line",
2002 append=False)
2004 # Merge the previous change into the file to produce a conflict.
2005 if expected_disk:
2006 expected_disk.tweak(state_path, contents="")
2007 expected_output = wc.State(wc_dir, {
2008 state_path : wc.StateItem(status='C '),
2010 inject_conflict_into_expected_state(state_path,
2011 expected_disk, expected_status,
2012 conflicting_contents, contents,
2013 merged_rev)
2014 exit_code, output, errput = main.run_svn(None, "up", "-r", str(merged_rev),
2015 file_path)
2016 if expected_status:
2017 expected_status.tweak(state_path, wc_rev=merged_rev)
2019 def inject_conflict_into_expected_state(state_path,
2020 expected_disk, expected_status,
2021 wc_text, merged_text, merged_rev):
2022 """Update the EXPECTED_DISK and EXPECTED_STATUS trees for the
2023 conflict at STATE_PATH (ignored if None). WC_TEXT, MERGED_TEXT, and
2024 MERGED_REV are used to determine the contents of the conflict (the
2025 text parameters should be newline-terminated)."""
2026 if expected_disk:
2027 conflict_marker = make_conflict_marker_text(wc_text, merged_text,
2028 merged_rev)
2029 existing_text = expected_disk.desc[state_path].contents or ""
2030 expected_disk.tweak(state_path, contents=existing_text + conflict_marker)
2032 if expected_status:
2033 expected_status.tweak(state_path, status='C ')
2035 def make_conflict_marker_text(wc_text, merged_text, merged_rev):
2036 """Return the conflict marker text described by WC_TEXT (the current
2037 text in the working copy, MERGED_TEXT (the conflicting text merged
2038 in), and MERGED_REV (the revision from whence the conflicting text
2039 came)."""
2040 return "<<<<<<< .working\n" + wc_text + "=======\n" + \
2041 merged_text + ">>>>>>> .merge-right.r" + str(merged_rev) + "\n"
2044 def build_greek_tree_conflicts(sbox):
2045 """Create a working copy that has tree-conflict markings.
2046 After this function has been called, sbox.wc_dir is a working
2047 copy that has specific tree-conflict markings.
2049 In particular, this does two conflicting sets of edits and performs an
2050 update so that tree conflicts appear.
2052 Note that this function calls sbox.build() because it needs a clean sbox.
2053 So, there is no need to call sbox.build() before this.
2055 The conflicts are the result of an 'update' on the following changes:
2057 Incoming Local
2059 A/D/G/pi text-mod del
2060 A/D/G/rho del text-mod
2061 A/D/G/tau del del
2063 This function is useful for testing that tree-conflicts are handled
2064 properly once they have appeared, e.g. that commits are blocked, that the
2065 info output is correct, etc.
2067 See also the tree-conflicts tests using deep_trees in various other
2068 .py files, and tree_conflict_tests.py.
2071 sbox.build()
2072 wc_dir = sbox.wc_dir
2073 j = os.path.join
2074 G = j(wc_dir, 'A', 'D', 'G')
2075 pi = j(G, 'pi')
2076 rho = j(G, 'rho')
2077 tau = j(G, 'tau')
2079 # Make incoming changes and "store them away" with a commit.
2080 main.file_append(pi, "Incoming edit.\n")
2081 main.run_svn(None, 'del', rho)
2082 main.run_svn(None, 'del', tau)
2084 expected_output = wc.State(wc_dir, {
2085 'A/D/G/pi' : Item(verb='Sending'),
2086 'A/D/G/rho' : Item(verb='Deleting'),
2087 'A/D/G/tau' : Item(verb='Deleting'),
2089 expected_status = get_virginal_state(wc_dir, 1)
2090 expected_status.tweak('A/D/G/pi', wc_rev='2')
2091 expected_status.remove('A/D/G/rho', 'A/D/G/tau')
2092 run_and_verify_commit(wc_dir, expected_output, expected_status, None,
2093 '-m', 'Incoming changes.', wc_dir )
2095 # Update back to the pristine state ("time-warp").
2096 expected_output = wc.State(wc_dir, {
2097 'A/D/G/pi' : Item(status='U '),
2098 'A/D/G/rho' : Item(status='A '),
2099 'A/D/G/tau' : Item(status='A '),
2101 expected_disk = main.greek_state
2102 expected_status = get_virginal_state(wc_dir, 1)
2103 run_and_verify_update(wc_dir, expected_output, expected_disk,
2104 expected_status, None, None, None, None, None, False,
2105 '-r', '1', wc_dir)
2107 # Make local changes
2108 main.run_svn(None, 'del', pi)
2109 main.file_append(rho, "Local edit.\n")
2110 main.run_svn(None, 'del', tau)
2112 # Update, receiving the incoming changes on top of the local changes,
2113 # causing tree conflicts. Don't check for any particular result: that is
2114 # the job of other tests.
2115 run_and_verify_svn(None, verify.AnyOutput, [], 'update', wc_dir)
2118 def make_deep_trees(base):
2119 """Helper function for deep trees conflicts. Create a set of trees,
2120 each in its own "container" dir. Any conflicts can be tested separately
2121 in each container.
2123 j = os.path.join
2124 # Create the container dirs.
2125 F = j(base, 'F')
2126 D = j(base, 'D')
2127 DF = j(base, 'DF')
2128 DD = j(base, 'DD')
2129 DDF = j(base, 'DDF')
2130 DDD = j(base, 'DDD')
2131 os.makedirs(F)
2132 os.makedirs(j(D, 'D1'))
2133 os.makedirs(j(DF, 'D1'))
2134 os.makedirs(j(DD, 'D1', 'D2'))
2135 os.makedirs(j(DDF, 'D1', 'D2'))
2136 os.makedirs(j(DDD, 'D1', 'D2', 'D3'))
2138 # Create their files.
2139 alpha = j(F, 'alpha')
2140 beta = j(DF, 'D1', 'beta')
2141 gamma = j(DDF, 'D1', 'D2', 'gamma')
2142 main.file_append(alpha, "This is the file 'alpha'.\n")
2143 main.file_append(beta, "This is the file 'beta'.\n")
2144 main.file_append(gamma, "This is the file 'gamma'.\n")
2147 def add_deep_trees(sbox, base_dir_name):
2148 """Prepare a "deep_trees" within a given directory.
2150 The directory <sbox.wc_dir>/<base_dir_name> is created and a deep_tree
2151 is created within. The items are only added, a commit has to be
2152 called separately, if needed.
2154 <base_dir_name> will thus be a container for the set of containers
2155 mentioned in make_deep_trees().
2157 j = os.path.join
2158 base = j(sbox.wc_dir, base_dir_name)
2159 make_deep_trees(base)
2160 main.run_svn(None, 'add', base)
2163 Item = wc.StateItem
2165 # initial deep trees state
2166 deep_trees_virginal_state = wc.State('', {
2167 'F' : Item(),
2168 'F/alpha' : Item("This is the file 'alpha'.\n"),
2169 'D' : Item(),
2170 'D/D1' : Item(),
2171 'DF' : Item(),
2172 'DF/D1' : Item(),
2173 'DF/D1/beta' : Item("This is the file 'beta'.\n"),
2174 'DD' : Item(),
2175 'DD/D1' : Item(),
2176 'DD/D1/D2' : Item(),
2177 'DDF' : Item(),
2178 'DDF/D1' : Item(),
2179 'DDF/D1/D2' : Item(),
2180 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\n"),
2181 'DDD' : Item(),
2182 'DDD/D1' : Item(),
2183 'DDD/D1/D2' : Item(),
2184 'DDD/D1/D2/D3' : Item(),
2188 # Many actions on deep trees and their resulting states...
2190 def deep_trees_leaf_edit(base):
2191 """Helper function for deep trees test cases. Append text to files,
2192 create new files in empty directories, and change leaf node properties."""
2193 j = os.path.join
2194 F = j(base, 'F', 'alpha')
2195 DF = j(base, 'DF', 'D1', 'beta')
2196 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
2197 main.file_append(F, "More text for file alpha.\n")
2198 main.file_append(DF, "More text for file beta.\n")
2199 main.file_append(DDF, "More text for file gamma.\n")
2200 run_and_verify_svn(None, verify.AnyOutput, [],
2201 'propset', 'prop1', '1', F, DF, DDF)
2203 D = j(base, 'D', 'D1')
2204 DD = j(base, 'DD', 'D1', 'D2')
2205 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
2206 run_and_verify_svn(None, verify.AnyOutput, [],
2207 'propset', 'prop1', '1', D, DD, DDD)
2208 D = j(base, 'D', 'D1', 'delta')
2209 DD = j(base, 'DD', 'D1', 'D2', 'epsilon')
2210 DDD = j(base, 'DDD', 'D1', 'D2', 'D3', 'zeta')
2211 main.file_append(D, "This is the file 'delta'.\n")
2212 main.file_append(DD, "This is the file 'epsilon'.\n")
2213 main.file_append(DDD, "This is the file 'zeta'.\n")
2214 run_and_verify_svn(None, verify.AnyOutput, [],
2215 'add', D, DD, DDD)
2217 # deep trees state after a call to deep_trees_leaf_edit
2218 deep_trees_after_leaf_edit = wc.State('', {
2219 'F' : Item(),
2220 'F/alpha' : Item("This is the file 'alpha'.\nMore text for file alpha.\n"),
2221 'D' : Item(),
2222 'D/D1' : Item(),
2223 'D/D1/delta' : Item("This is the file 'delta'.\n"),
2224 'DF' : Item(),
2225 'DF/D1' : Item(),
2226 'DF/D1/beta' : Item("This is the file 'beta'.\nMore text for file beta.\n"),
2227 'DD' : Item(),
2228 'DD/D1' : Item(),
2229 'DD/D1/D2' : Item(),
2230 'DD/D1/D2/epsilon' : Item("This is the file 'epsilon'.\n"),
2231 'DDF' : Item(),
2232 'DDF/D1' : Item(),
2233 'DDF/D1/D2' : Item(),
2234 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\nMore text for file gamma.\n"),
2235 'DDD' : Item(),
2236 'DDD/D1' : Item(),
2237 'DDD/D1/D2' : Item(),
2238 'DDD/D1/D2/D3' : Item(),
2239 'DDD/D1/D2/D3/zeta' : Item("This is the file 'zeta'.\n"),
2243 def deep_trees_leaf_del(base):
2244 """Helper function for deep trees test cases. Delete files and empty
2245 dirs."""
2246 j = os.path.join
2247 F = j(base, 'F', 'alpha')
2248 D = j(base, 'D', 'D1')
2249 DF = j(base, 'DF', 'D1', 'beta')
2250 DD = j(base, 'DD', 'D1', 'D2')
2251 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
2252 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
2253 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
2255 # deep trees state after a call to deep_trees_leaf_del
2256 deep_trees_after_leaf_del = wc.State('', {
2257 'F' : Item(),
2258 'D' : Item(),
2259 'DF' : Item(),
2260 'DF/D1' : Item(),
2261 'DD' : Item(),
2262 'DD/D1' : Item(),
2263 'DDF' : Item(),
2264 'DDF/D1' : Item(),
2265 'DDF/D1/D2' : Item(),
2266 'DDD' : Item(),
2267 'DDD/D1' : Item(),
2268 'DDD/D1/D2' : Item(),
2271 # deep trees state after a call to deep_trees_leaf_del with no commit
2272 def deep_trees_after_leaf_del_no_ci(wc_dir):
2273 if svntest.main.wc_is_singledb(wc_dir):
2274 return deep_trees_after_leaf_del
2275 else:
2276 return deep_trees_empty_dirs
2279 def deep_trees_tree_del(base):
2280 """Helper function for deep trees test cases. Delete top-level dirs."""
2281 j = os.path.join
2282 F = j(base, 'F', 'alpha')
2283 D = j(base, 'D', 'D1')
2284 DF = j(base, 'DF', 'D1')
2285 DD = j(base, 'DD', 'D1')
2286 DDF = j(base, 'DDF', 'D1')
2287 DDD = j(base, 'DDD', 'D1')
2288 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
2290 def deep_trees_rmtree(base):
2291 """Helper function for deep trees test cases. Delete top-level dirs
2292 with rmtree instead of svn del."""
2293 j = os.path.join
2294 F = j(base, 'F', 'alpha')
2295 D = j(base, 'D', 'D1')
2296 DF = j(base, 'DF', 'D1')
2297 DD = j(base, 'DD', 'D1')
2298 DDF = j(base, 'DDF', 'D1')
2299 DDD = j(base, 'DDD', 'D1')
2300 os.unlink(F)
2301 main.safe_rmtree(D)
2302 main.safe_rmtree(DF)
2303 main.safe_rmtree(DD)
2304 main.safe_rmtree(DDF)
2305 main.safe_rmtree(DDD)
2307 # deep trees state after a call to deep_trees_tree_del
2308 deep_trees_after_tree_del = wc.State('', {
2309 'F' : Item(),
2310 'D' : Item(),
2311 'DF' : Item(),
2312 'DD' : Item(),
2313 'DDF' : Item(),
2314 'DDD' : Item(),
2317 # deep trees state without any files
2318 deep_trees_empty_dirs = wc.State('', {
2319 'F' : Item(),
2320 'D' : Item(),
2321 'D/D1' : Item(),
2322 'DF' : Item(),
2323 'DF/D1' : Item(),
2324 'DD' : Item(),
2325 'DD/D1' : Item(),
2326 'DD/D1/D2' : Item(),
2327 'DDF' : Item(),
2328 'DDF/D1' : Item(),
2329 'DDF/D1/D2' : Item(),
2330 'DDD' : Item(),
2331 'DDD/D1' : Item(),
2332 'DDD/D1/D2' : Item(),
2333 'DDD/D1/D2/D3' : Item(),
2336 # deep trees state after a call to deep_trees_tree_del with no commit
2337 def deep_trees_after_tree_del_no_ci(wc_dir):
2338 if svntest.main.wc_is_singledb(wc_dir):
2339 return deep_trees_after_tree_del
2340 else:
2341 return deep_trees_empty_dirs
2343 def deep_trees_tree_del_repos(base):
2344 """Helper function for deep trees test cases. Delete top-level dirs,
2345 directly in the repository."""
2346 j = '/'.join
2347 F = j([base, 'F', 'alpha'])
2348 D = j([base, 'D', 'D1'])
2349 DF = j([base, 'DF', 'D1'])
2350 DD = j([base, 'DD', 'D1'])
2351 DDF = j([base, 'DDF', 'D1'])
2352 DDD = j([base, 'DDD', 'D1'])
2353 main.run_svn(None, 'mkdir', '-m', '', F, D, DF, DD, DDF, DDD)
2355 # Expected merge/update/switch output.
2357 deep_trees_conflict_output = wc.State('', {
2358 'F/alpha' : Item(status=' ', treeconflict='C'),
2359 'D/D1' : Item(status=' ', treeconflict='C'),
2360 'DF/D1' : Item(status=' ', treeconflict='C'),
2361 'DD/D1' : Item(status=' ', treeconflict='C'),
2362 'DDF/D1' : Item(status=' ', treeconflict='C'),
2363 'DDD/D1' : Item(status=' ', treeconflict='C'),
2366 deep_trees_conflict_output_skipped = wc.State('', {
2367 'D/D1' : Item(verb='Skipped'),
2368 'F/alpha' : Item(verb='Skipped'),
2369 'DD/D1' : Item(verb='Skipped'),
2370 'DF/D1' : Item(verb='Skipped'),
2371 'DDD/D1' : Item(verb='Skipped'),
2372 'DDF/D1' : Item(verb='Skipped'),
2375 # Expected status output after merge/update/switch.
2377 deep_trees_status_local_tree_del = wc.State('', {
2378 '' : Item(status=' ', wc_rev=3),
2379 'D' : Item(status=' ', wc_rev=3),
2380 'D/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2381 'DD' : Item(status=' ', wc_rev=3),
2382 'DD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2383 'DD/D1/D2' : Item(status='D ', wc_rev=2),
2384 'DDD' : Item(status=' ', wc_rev=3),
2385 'DDD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2386 'DDD/D1/D2' : Item(status='D ', wc_rev=2),
2387 'DDD/D1/D2/D3' : Item(status='D ', wc_rev=2),
2388 'DDF' : Item(status=' ', wc_rev=3),
2389 'DDF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2390 'DDF/D1/D2' : Item(status='D ', wc_rev=2),
2391 'DDF/D1/D2/gamma' : Item(status='D ', wc_rev=2),
2392 'DF' : Item(status=' ', wc_rev=3),
2393 'DF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2394 'DF/D1/beta' : Item(status='D ', wc_rev=2),
2395 'F' : Item(status=' ', wc_rev=3),
2396 'F/alpha' : Item(status='D ', wc_rev=2, treeconflict='C'),
2399 deep_trees_status_local_leaf_edit = wc.State('', {
2400 '' : Item(status=' ', wc_rev=3),
2401 'D' : Item(status=' ', wc_rev=3),
2402 'D/D1' : Item(status=' M', wc_rev=2, treeconflict='C'),
2403 'D/D1/delta' : Item(status='A ', wc_rev=0),
2404 'DD' : Item(status=' ', wc_rev=3),
2405 'DD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2406 'DD/D1/D2' : Item(status=' M', wc_rev=2),
2407 'DD/D1/D2/epsilon' : Item(status='A ', wc_rev=0),
2408 'DDD' : Item(status=' ', wc_rev=3),
2409 'DDD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2410 'DDD/D1/D2' : Item(status=' ', wc_rev=2),
2411 'DDD/D1/D2/D3' : Item(status=' M', wc_rev=2),
2412 'DDD/D1/D2/D3/zeta' : Item(status='A ', wc_rev=0),
2413 'DDF' : Item(status=' ', wc_rev=3),
2414 'DDF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2415 'DDF/D1/D2' : Item(status=' ', wc_rev=2),
2416 'DDF/D1/D2/gamma' : Item(status='MM', wc_rev=2),
2417 'DF' : Item(status=' ', wc_rev=3),
2418 'DF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2419 'DF/D1/beta' : Item(status='MM', wc_rev=2),
2420 'F' : Item(status=' ', wc_rev=3),
2421 'F/alpha' : Item(status='MM', wc_rev=2, treeconflict='C'),
2425 class DeepTreesTestCase:
2426 """Describes one tree-conflicts test case.
2427 See deep_trees_run_tests_scheme_for_update(), ..._switch(), ..._merge().
2429 The name field is the subdirectory name in which the test should be run.
2431 The local_action and incoming_action are the functions to run
2432 to construct the local changes and incoming changes, respectively.
2433 See deep_trees_leaf_edit, deep_trees_tree_del, etc.
2435 The expected_* and error_re_string arguments are described in functions
2436 run_and_verify_[update|switch|merge]
2437 except expected_info, which is a dict that has path keys with values
2438 that are dicts as passed to run_and_verify_info():
2439 expected_info = {
2440 'F/alpha' : {
2441 'Revision' : '3',
2442 'Tree conflict' :
2443 '^local delete, incoming edit upon update'
2444 + ' Source left: .file.*/F/alpha@2'
2445 + ' Source right: .file.*/F/alpha@3$',
2447 'DF/D1' : {
2448 'Tree conflict' :
2449 '^local delete, incoming edit upon update'
2450 + ' Source left: .dir.*/DF/D1@2'
2451 + ' Source right: .dir.*/DF/D1@3$',
2456 Note: expected_skip is only used in merge, i.e. using
2457 deep_trees_run_tests_scheme_for_merge.
2460 def __init__(self, name, local_action, incoming_action,
2461 expected_output = None, expected_disk = None,
2462 expected_status = None, expected_skip = None,
2463 error_re_string = None,
2464 commit_block_string = ".*remains in conflict.*",
2465 expected_info = None):
2466 self.name = name
2467 self.local_action = local_action
2468 self.incoming_action = incoming_action
2469 self.expected_output = expected_output
2470 self.expected_disk = expected_disk
2471 self.expected_status = expected_status
2472 self.expected_skip = expected_skip
2473 self.error_re_string = error_re_string
2474 self.commit_block_string = commit_block_string
2475 self.expected_info = expected_info
2479 def deep_trees_run_tests_scheme_for_update(sbox, greater_scheme):
2481 Runs a given list of tests for conflicts occuring at an update operation.
2483 This function wants to save time and perform a number of different
2484 test cases using just a single repository and performing just one commit
2485 for all test cases instead of one for each test case.
2487 1) Each test case is initialized in a separate subdir. Each subdir
2488 again contains one set of "deep_trees", being separate container
2489 dirs for different depths of trees (F, D, DF, DD, DDF, DDD).
2491 2) A commit is performed across all test cases and depths.
2492 (our initial state, -r2)
2494 3) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2495 its *incoming* action is performed (e.g. "deep_trees_leaf_edit"), in
2496 each of the different depth trees (F, D, DF, ... DDD).
2498 4) A commit is performed across all test cases and depths:
2499 our "incoming" state is "stored away in the repository for now",
2500 -r3.
2502 5) All test case dirs and contained deep_trees are time-warped
2503 (updated) back to -r2, the initial state containing deep_trees.
2505 6) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2506 its *local* action is performed (e.g. "deep_trees_leaf_del"), in
2507 each of the different depth trees (F, D, DF, ... DDD).
2509 7) An update to -r3 is performed across all test cases and depths.
2510 This causes tree-conflicts between the "local" state in the working
2511 copy and the "incoming" state from the repository, -r3.
2513 8) A commit is performed in each separate container, to verify
2514 that each tree-conflict indeed blocks a commit.
2516 The sbox parameter is just the sbox passed to a test function. No need
2517 to call sbox.build(), since it is called (once) within this function.
2519 The "table" greater_scheme models all of the different test cases
2520 that should be run using a single repository.
2522 greater_scheme is a list of DeepTreesTestCase items, which define complete
2523 test setups, so that they can be performed as described above.
2526 j = os.path.join
2528 if not sbox.is_built():
2529 sbox.build()
2530 wc_dir = sbox.wc_dir
2533 # 1) create directories
2535 for test_case in greater_scheme:
2536 try:
2537 add_deep_trees(sbox, test_case.name)
2538 except:
2539 logger.warn("ERROR IN: Tests scheme for update: "
2540 + "while setting up deep trees in '%s'", test_case.name)
2541 raise
2544 # 2) commit initial state
2546 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2549 # 3) apply incoming changes
2551 for test_case in greater_scheme:
2552 try:
2553 test_case.incoming_action(j(sbox.wc_dir, test_case.name))
2554 except:
2555 logger.warn("ERROR IN: Tests scheme for update: "
2556 + "while performing incoming action in '%s'", test_case.name)
2557 raise
2560 # 4) commit incoming changes
2562 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2565 # 5) time-warp back to -r2
2567 main.run_svn(None, 'update', '-r2', wc_dir)
2570 # 6) apply local changes
2572 for test_case in greater_scheme:
2573 try:
2574 test_case.local_action(j(wc_dir, test_case.name))
2575 except:
2576 logger.warn("ERROR IN: Tests scheme for update: "
2577 + "while performing local action in '%s'", test_case.name)
2578 raise
2581 # 7) update to -r3, conflicting with incoming changes.
2582 # A lot of different things are expected.
2583 # Do separate update operations for each test case.
2585 for test_case in greater_scheme:
2586 try:
2587 base = j(wc_dir, test_case.name)
2589 x_out = test_case.expected_output
2590 if x_out != None:
2591 x_out = x_out.copy()
2592 x_out.wc_dir = base
2594 x_disk = test_case.expected_disk
2596 x_status = test_case.expected_status
2597 if x_status != None:
2598 x_status.copy()
2599 x_status.wc_dir = base
2601 run_and_verify_update(base, x_out, x_disk, None,
2602 error_re_string = test_case.error_re_string)
2603 if x_status:
2604 run_and_verify_unquiet_status(base, x_status)
2606 x_info = test_case.expected_info or {}
2607 for path in x_info:
2608 run_and_verify_info([x_info[path]], j(base, path))
2610 except:
2611 logger.warn("ERROR IN: Tests scheme for update: "
2612 + "while verifying in '%s'", test_case.name)
2613 raise
2616 # 8) Verify that commit fails.
2618 for test_case in greater_scheme:
2619 try:
2620 base = j(wc_dir, test_case.name)
2622 x_status = test_case.expected_status
2623 if x_status != None:
2624 x_status.copy()
2625 x_status.wc_dir = base
2627 run_and_verify_commit(base, None, x_status,
2628 test_case.commit_block_string,
2629 base)
2630 except:
2631 logger.warn("ERROR IN: Tests scheme for update: "
2632 + "while checking commit-blocking in '%s'", test_case.name)
2633 raise
2637 def deep_trees_skipping_on_update(sbox, test_case, skip_paths,
2638 chdir_skip_paths):
2640 Create tree conflicts, then update again, expecting the existing tree
2641 conflicts to be skipped.
2642 SKIP_PATHS is a list of paths, relative to the "base dir", for which
2643 "update" on the "base dir" should report as skipped.
2644 CHDIR_SKIP_PATHS is a list of (target-path, skipped-path) pairs for which
2645 an update of "target-path" (relative to the "base dir") should result in
2646 "skipped-path" (relative to "target-path") being reported as skipped.
2649 """FURTHER_ACTION is a function that will make a further modification to
2650 each target, this being the modification that we expect to be skipped. The
2651 function takes the "base dir" (the WC path to the test case directory) as
2652 its only argument."""
2653 further_action = deep_trees_tree_del_repos
2655 j = os.path.join
2656 wc_dir = sbox.wc_dir
2657 base = j(wc_dir, test_case.name)
2659 # Initialize: generate conflicts. (We do not check anything here.)
2660 setup_case = DeepTreesTestCase(test_case.name,
2661 test_case.local_action,
2662 test_case.incoming_action,
2663 None,
2664 None,
2665 None)
2666 deep_trees_run_tests_scheme_for_update(sbox, [setup_case])
2668 # Make a further change to each target in the repository so there is a new
2669 # revision to update to. (This is r4.)
2670 further_action(sbox.repo_url + '/' + test_case.name)
2672 # Update whole working copy, expecting the nodes still in conflict to be
2673 # skipped.
2675 x_out = test_case.expected_output
2676 if x_out != None:
2677 x_out = x_out.copy()
2678 x_out.wc_dir = base
2680 x_disk = test_case.expected_disk
2682 x_status = test_case.expected_status
2683 if x_status != None:
2684 x_status = x_status.copy()
2685 x_status.wc_dir = base
2686 # Account for nodes that were updated by further_action
2687 x_status.tweak('', 'D', 'F', 'DD', 'DF', 'DDD', 'DDF', wc_rev=4)
2689 run_and_verify_update(base, x_out, x_disk, None,
2690 error_re_string = test_case.error_re_string)
2692 run_and_verify_unquiet_status(base, x_status)
2694 # Try to update each in-conflict subtree. Expect a 'Skipped' output for
2695 # each, and the WC status to be unchanged.
2696 for path in skip_paths:
2697 run_and_verify_update(j(base, path),
2698 wc.State(base, {path : Item(verb='Skipped')}),
2699 None, None)
2701 run_and_verify_unquiet_status(base, x_status)
2703 # Try to update each in-conflict subtree. Expect a 'Skipped' output for
2704 # each, and the WC status to be unchanged.
2705 # This time, cd to the subdir before updating it.
2706 was_cwd = os.getcwd()
2707 for path, skipped in chdir_skip_paths:
2708 if isinstance(skipped, list):
2709 expected_skip = {}
2710 for p in skipped:
2711 expected_skip[p] = Item(verb='Skipped')
2712 else:
2713 expected_skip = {skipped : Item(verb='Skipped')}
2714 p = j(base, path)
2715 run_and_verify_update(p,
2716 wc.State(p, expected_skip),
2717 None, None)
2718 os.chdir(was_cwd)
2720 run_and_verify_unquiet_status(base, x_status)
2722 # Verify that commit still fails.
2723 for path, skipped in chdir_skip_paths:
2725 run_and_verify_commit(j(base, path), None, None,
2726 test_case.commit_block_string,
2727 base)
2729 run_and_verify_unquiet_status(base, x_status)
2732 def deep_trees_run_tests_scheme_for_switch(sbox, greater_scheme):
2734 Runs a given list of tests for conflicts occuring at a switch operation.
2736 This function wants to save time and perform a number of different
2737 test cases using just a single repository and performing just one commit
2738 for all test cases instead of one for each test case.
2740 1) Each test case is initialized in a separate subdir. Each subdir
2741 again contains two subdirs: one "local" and one "incoming" for
2742 the switch operation. These contain a set of deep_trees each.
2744 2) A commit is performed across all test cases and depths.
2745 (our initial state, -r2)
2747 3) In each test case subdir's incoming subdir, the
2748 incoming actions are performed.
2750 4) A commit is performed across all test cases and depths. (-r3)
2752 5) In each test case subdir's local subdir, the local actions are
2753 performed. They remain uncommitted in the working copy.
2755 6) In each test case subdir's local dir, a switch is performed to its
2756 corresponding incoming dir.
2757 This causes conflicts between the "local" state in the working
2758 copy and the "incoming" state from the incoming subdir (still -r3).
2760 7) A commit is performed in each separate container, to verify
2761 that each tree-conflict indeed blocks a commit.
2763 The sbox parameter is just the sbox passed to a test function. No need
2764 to call sbox.build(), since it is called (once) within this function.
2766 The "table" greater_scheme models all of the different test cases
2767 that should be run using a single repository.
2769 greater_scheme is a list of DeepTreesTestCase items, which define complete
2770 test setups, so that they can be performed as described above.
2773 j = os.path.join
2775 if not sbox.is_built():
2776 sbox.build()
2777 wc_dir = sbox.wc_dir
2780 # 1) Create directories.
2782 for test_case in greater_scheme:
2783 try:
2784 base = j(sbox.wc_dir, test_case.name)
2785 os.makedirs(base)
2786 make_deep_trees(j(base, "local"))
2787 make_deep_trees(j(base, "incoming"))
2788 main.run_svn(None, 'add', base)
2789 except:
2790 logger.warn("ERROR IN: Tests scheme for switch: "
2791 + "while setting up deep trees in '%s'", test_case.name)
2792 raise
2795 # 2) Commit initial state (-r2).
2797 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2800 # 3) Apply incoming changes
2802 for test_case in greater_scheme:
2803 try:
2804 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2805 except:
2806 logger.warn("ERROR IN: Tests scheme for switch: "
2807 + "while performing incoming action in '%s'", test_case.name)
2808 raise
2811 # 4) Commit all changes (-r3).
2813 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2816 # 5) Apply local changes in their according subdirs.
2818 for test_case in greater_scheme:
2819 try:
2820 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
2821 except:
2822 logger.warn("ERROR IN: Tests scheme for switch: "
2823 + "while performing local action in '%s'", test_case.name)
2824 raise
2827 # 6) switch the local dir to the incoming url, conflicting with incoming
2828 # changes. A lot of different things are expected.
2829 # Do separate switch operations for each test case.
2831 for test_case in greater_scheme:
2832 try:
2833 local = j(wc_dir, test_case.name, "local")
2834 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
2836 x_out = test_case.expected_output
2837 if x_out != None:
2838 x_out = x_out.copy()
2839 x_out.wc_dir = local
2841 x_disk = test_case.expected_disk
2843 x_status = test_case.expected_status
2844 if x_status != None:
2845 x_status.copy()
2846 x_status.wc_dir = local
2848 run_and_verify_switch(local, local, incoming, x_out, x_disk, None,
2849 test_case.error_re_string, None, None, None,
2850 None, False, '--ignore-ancestry')
2851 run_and_verify_unquiet_status(local, x_status)
2853 x_info = test_case.expected_info or {}
2854 for path in x_info:
2855 run_and_verify_info([x_info[path]], j(local, path))
2856 except:
2857 logger.warn("ERROR IN: Tests scheme for switch: "
2858 + "while verifying in '%s'", test_case.name)
2859 raise
2862 # 7) Verify that commit fails.
2864 for test_case in greater_scheme:
2865 try:
2866 local = j(wc_dir, test_case.name, 'local')
2868 x_status = test_case.expected_status
2869 if x_status != None:
2870 x_status.copy()
2871 x_status.wc_dir = local
2873 run_and_verify_commit(local, None, x_status,
2874 test_case.commit_block_string,
2875 local)
2876 except:
2877 logger.warn("ERROR IN: Tests scheme for switch: "
2878 + "while checking commit-blocking in '%s'", test_case.name)
2879 raise
2882 def deep_trees_run_tests_scheme_for_merge(sbox, greater_scheme,
2883 do_commit_local_changes,
2884 do_commit_conflicts=True,
2885 ignore_ancestry=False):
2887 Runs a given list of tests for conflicts occuring at a merge operation.
2889 This function wants to save time and perform a number of different
2890 test cases using just a single repository and performing just one commit
2891 for all test cases instead of one for each test case.
2893 1) Each test case is initialized in a separate subdir. Each subdir
2894 initially contains another subdir, called "incoming", which
2895 contains a set of deep_trees.
2897 2) A commit is performed across all test cases and depths.
2898 (a pre-initial state)
2900 3) In each test case subdir, the "incoming" subdir is copied to "local",
2901 via the `svn copy' command. Each test case's subdir now has two sub-
2902 dirs: "local" and "incoming", initial states for the merge operation.
2904 4) An update is performed across all test cases and depths, so that the
2905 copies made in 3) are pulled into the wc.
2907 5) In each test case's "incoming" subdir, the incoming action is
2908 performed.
2910 6) A commit is performed across all test cases and depths, to commit
2911 the incoming changes.
2912 If do_commit_local_changes is True, this becomes step 7 (swap steps).
2914 7) In each test case's "local" subdir, the local_action is performed.
2915 If do_commit_local_changes is True, this becomes step 6 (swap steps).
2916 Then, in effect, the local changes are committed as well.
2918 8) In each test case subdir, the "incoming" subdir is merged into the
2919 "local" subdir. If ignore_ancestry is True, then the merge is done
2920 with the --ignore-ancestry option, so mergeinfo is neither considered
2921 nor recorded. This causes conflicts between the "local" state in the
2922 working copy and the "incoming" state from the incoming subdir.
2924 9) If do_commit_conflicts is True, then a commit is performed in each
2925 separate container, to verify that each tree-conflict indeed blocks
2926 a commit.
2928 The sbox parameter is just the sbox passed to a test function. No need
2929 to call sbox.build(), since it is called (once) within this function.
2931 The "table" greater_scheme models all of the different test cases
2932 that should be run using a single repository.
2934 greater_scheme is a list of DeepTreesTestCase items, which define complete
2935 test setups, so that they can be performed as described above.
2938 j = os.path.join
2940 if not sbox.is_built():
2941 sbox.build()
2942 wc_dir = sbox.wc_dir
2944 # 1) Create directories.
2945 for test_case in greater_scheme:
2946 try:
2947 base = j(sbox.wc_dir, test_case.name)
2948 os.makedirs(base)
2949 make_deep_trees(j(base, "incoming"))
2950 main.run_svn(None, 'add', base)
2951 except:
2952 logger.warn("ERROR IN: Tests scheme for merge: "
2953 + "while setting up deep trees in '%s'", test_case.name)
2954 raise
2957 # 2) Commit pre-initial state (-r2).
2959 main.run_svn(None, 'commit', '-m', 'pre-initial state', wc_dir)
2962 # 3) Copy "incoming" to "local".
2964 for test_case in greater_scheme:
2965 try:
2966 base_url = sbox.repo_url + "/" + test_case.name
2967 incoming_url = base_url + "/incoming"
2968 local_url = base_url + "/local"
2969 main.run_svn(None, 'cp', incoming_url, local_url, '-m',
2970 'copy incoming to local')
2971 except:
2972 logger.warn("ERROR IN: Tests scheme for merge: "
2973 + "while copying deep trees in '%s'", test_case.name)
2974 raise
2976 # 4) Update to load all of the "/local" subdirs into the working copies.
2978 try:
2979 main.run_svn(None, 'up', sbox.wc_dir)
2980 except:
2981 logger.warn("ERROR IN: Tests scheme for merge: "
2982 + "while updating local subdirs")
2983 raise
2986 # 5) Perform incoming actions
2988 for test_case in greater_scheme:
2989 try:
2990 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2991 except:
2992 logger.warn("ERROR IN: Tests scheme for merge: "
2993 + "while performing incoming action in '%s'", test_case.name)
2994 raise
2997 # 6) or 7) Commit all incoming actions
2999 if not do_commit_local_changes:
3000 try:
3001 main.run_svn(None, 'ci', '-m', 'Committing incoming actions',
3002 sbox.wc_dir)
3003 except:
3004 logger.warn("ERROR IN: Tests scheme for merge: "
3005 + "while committing incoming actions")
3006 raise
3009 # 7) or 6) Perform all local actions.
3011 for test_case in greater_scheme:
3012 try:
3013 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
3014 except:
3015 logger.warn("ERROR IN: Tests scheme for merge: "
3016 + "while performing local action in '%s'", test_case.name)
3017 raise
3020 # 6) or 7) Commit all incoming actions
3022 if do_commit_local_changes:
3023 try:
3024 main.run_svn(None, 'ci', '-m', 'Committing incoming and local actions',
3025 sbox.wc_dir)
3026 except:
3027 logger.warn("ERROR IN: Tests scheme for merge: "
3028 + "while committing incoming and local actions")
3029 raise
3032 # 8) Merge all "incoming" subdirs to their respective "local" subdirs.
3033 # This creates conflicts between the local changes in the "local" wc
3034 # subdirs and the incoming states committed in the "incoming" subdirs.
3036 for test_case in greater_scheme:
3037 try:
3038 local = j(sbox.wc_dir, test_case.name, "local")
3039 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
3041 x_out = test_case.expected_output
3042 if x_out != None:
3043 x_out = x_out.copy()
3044 x_out.wc_dir = local
3046 x_disk = test_case.expected_disk
3048 x_status = test_case.expected_status
3049 if x_status != None:
3050 x_status.copy()
3051 x_status.wc_dir = local
3053 x_skip = test_case.expected_skip
3054 if x_skip != None:
3055 x_skip.copy()
3056 x_skip.wc_dir = local
3058 varargs = (local,)
3059 if ignore_ancestry:
3060 varargs = varargs + ('--ignore-ancestry',)
3062 run_and_verify_merge(local, None, None, incoming, None,
3063 x_out, None, None, x_disk, None, x_skip,
3064 test_case.error_re_string,
3065 None, None, None, None,
3066 False, False, *varargs)
3067 run_and_verify_unquiet_status(local, x_status)
3068 except:
3069 logger.warn("ERROR IN: Tests scheme for merge: "
3070 + "while verifying in '%s'", test_case.name)
3071 raise
3074 # 9) Verify that commit fails.
3076 if do_commit_conflicts:
3077 for test_case in greater_scheme:
3078 try:
3079 local = j(wc_dir, test_case.name, 'local')
3081 x_status = test_case.expected_status
3082 if x_status != None:
3083 x_status.copy()
3084 x_status.wc_dir = local
3086 run_and_verify_commit(local, None, x_status,
3087 test_case.commit_block_string,
3088 local)
3089 except:
3090 logger.warn("ERROR IN: Tests scheme for merge: "
3091 + "while checking commit-blocking in '%s'", test_case.name)
3092 raise