Diagnose test 35 failure
[svnrdump.git] / svntest / actions.py
blob260bcaa1133c65baeb23e7eae9b8a6143fff68ae
2 # actions.py: routines that actually run the svn client.
4 # Subversion is a tool for revision control.
5 # See http://subversion.tigris.org for more information.
7 # ====================================================================
8 # Licensed to the Apache Software Foundation (ASF) under one
9 # or more contributor license agreements. See the NOTICE file
10 # distributed with this work for additional information
11 # regarding copyright ownership. The ASF licenses this file
12 # to you under the Apache License, Version 2.0 (the
13 # "License"); you may not use this file except in compliance
14 # with the License. You may obtain a copy of the License at
16 # http://www.apache.org/licenses/LICENSE-2.0
18 # Unless required by applicable law or agreed to in writing,
19 # software distributed under the License is distributed on an
20 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 # KIND, either express or implied. See the License for the
22 # specific language governing permissions and limitations
23 # under the License.
24 ######################################################################
26 import os, shutil, re, sys, errno
27 import difflib, pprint
28 import xml.parsers.expat
29 from xml.dom.minidom import parseString
31 import svntest
32 from svntest import main, verify, tree, wc
33 from svntest import Failure
35 def no_sleep_for_timestamps():
36 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'yes'
38 def do_sleep_for_timestamps():
39 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'no'
41 def no_relocate_validation():
42 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION'] = 'yes'
44 def do_relocate_validation():
45 os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION'] = 'no'
47 def setup_pristine_greek_repository():
48 """Create the pristine repository and 'svn import' the greek tree"""
50 # these directories don't exist out of the box, so we may have to create them
51 if not os.path.exists(main.general_wc_dir):
52 os.makedirs(main.general_wc_dir)
54 if not os.path.exists(main.general_repo_dir):
55 os.makedirs(main.general_repo_dir) # this also creates all the intermediate dirs
57 # If there's no pristine repos, create one.
58 if not os.path.exists(main.pristine_greek_repos_dir):
59 main.create_repos(main.pristine_greek_repos_dir)
61 # if this is dav, gives us access rights to import the greek tree.
62 if main.is_ra_type_dav():
63 authz_file = os.path.join(main.work_dir, "authz")
64 main.file_write(authz_file, "[/]\n* = rw\n")
66 # dump the greek tree to disk.
67 main.greek_state.write_to_disk(main.greek_dump_dir)
69 # import the greek tree, using l:foo/p:bar
70 ### todo: svn should not be prompting for auth info when using
71 ### repositories with no auth/auth requirements
72 exit_code, output, errput = main.run_svn(None, 'import', '-m',
73 'Log message for revision 1.',
74 main.greek_dump_dir,
75 main.pristine_greek_repos_url)
77 # check for any errors from the import
78 if len(errput):
79 display_lines("Errors during initial 'svn import':",
80 'STDERR', None, errput)
81 sys.exit(1)
83 # verify the printed output of 'svn import'.
84 lastline = output.pop().strip()
85 match = re.search("(Committed|Imported) revision [0-9]+.", lastline)
86 if not match:
87 print("ERROR: import did not succeed, while creating greek repos.")
88 print("The final line from 'svn import' was:")
89 print(lastline)
90 sys.exit(1)
91 output_tree = wc.State.from_commit(output)
93 expected_output_tree = main.greek_state.copy(main.greek_dump_dir)
94 expected_output_tree.tweak(verb='Adding',
95 contents=None)
97 try:
98 expected_output_tree.compare_and_display('output', output_tree)
99 except tree.SVNTreeUnequal:
100 verify.display_trees("ERROR: output of import command is unexpected.",
101 "OUTPUT TREE",
102 expected_output_tree.old_tree(),
103 output_tree.old_tree())
104 sys.exit(1)
106 # Finally, disallow any changes to the "pristine" repos.
107 error_msg = "Don't modify the pristine repository"
108 create_failing_hook(main.pristine_greek_repos_dir, 'start-commit', error_msg)
109 create_failing_hook(main.pristine_greek_repos_dir, 'pre-lock', error_msg)
110 create_failing_hook(main.pristine_greek_repos_dir, 'pre-revprop-change', error_msg)
113 ######################################################################
115 def guarantee_empty_repository(path):
116 """Guarantee that a local svn repository exists at PATH, containing
117 nothing."""
119 if path == main.pristine_greek_repos_dir:
120 print("ERROR: attempt to overwrite the pristine repos! Aborting.")
121 sys.exit(1)
123 # create an empty repository at PATH.
124 main.safe_rmtree(path)
125 main.create_repos(path)
127 # Used by every test, so that they can run independently of one
128 # another. Every time this routine is called, it recursively copies
129 # the `pristine repos' to a new location.
130 # Note: make sure setup_pristine_greek_repository was called once before
131 # using this function.
132 def guarantee_greek_repository(path):
133 """Guarantee that a local svn repository exists at PATH, containing
134 nothing but the greek-tree at revision 1."""
136 if path == main.pristine_greek_repos_dir:
137 print("ERROR: attempt to overwrite the pristine repos! Aborting.")
138 sys.exit(1)
140 # copy the pristine repository to PATH.
141 main.safe_rmtree(path)
142 if main.copy_repos(main.pristine_greek_repos_dir, path, 1):
143 print("ERROR: copying repository failed.")
144 sys.exit(1)
146 # make the repos world-writeable, for mod_dav_svn's sake.
147 main.chmod_tree(path, 0666, 0666)
150 def run_and_verify_svnlook(message, expected_stdout,
151 expected_stderr, *varargs):
152 """Like run_and_verify_svnlook2, but the expected exit code is
153 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
155 expected_exit = 0
156 if expected_stderr is not None and expected_stderr != []:
157 expected_exit = 1
158 return run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
159 expected_exit, *varargs)
161 def run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
162 expected_exit, *varargs):
163 """Run svnlook command and check its output and exit code."""
165 exit_code, out, err = main.run_svnlook(*varargs)
166 verify.verify_outputs("Unexpected output", out, err,
167 expected_stdout, expected_stderr)
168 verify.verify_exit_code(message, exit_code, expected_exit)
169 return exit_code, out, err
172 def run_and_verify_svnadmin(message, expected_stdout,
173 expected_stderr, *varargs):
174 """Like run_and_verify_svnadmin2, but the expected exit code is
175 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
177 expected_exit = 0
178 if expected_stderr is not None and expected_stderr != []:
179 expected_exit = 1
180 return run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
181 expected_exit, *varargs)
183 def run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
184 expected_exit, *varargs):
185 """Run svnadmin command and check its output and exit code."""
187 exit_code, out, err = main.run_svnadmin(*varargs)
188 verify.verify_outputs("Unexpected output", out, err,
189 expected_stdout, expected_stderr)
190 verify.verify_exit_code(message, exit_code, expected_exit)
191 return exit_code, out, err
194 def run_and_verify_svnversion(message, wc_dir, repo_url,
195 expected_stdout, expected_stderr):
196 """like run_and_verify_svnversion2, but the expected exit code is
197 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
199 expected_exit = 0
200 if expected_stderr is not None and expected_stderr != []:
201 expected_exit = 1
202 return run_and_verify_svnversion2(message, wc_dir, repo_url,
203 expected_stdout, expected_stderr,
204 expected_exit)
206 def run_and_verify_svnversion2(message, wc_dir, repo_url,
207 expected_stdout, expected_stderr,
208 expected_exit):
209 """Run svnversion command and check its output and exit code."""
211 exit_code, out, err = main.run_svnversion(wc_dir, repo_url)
212 verify.verify_outputs("Unexpected output", out, err,
213 expected_stdout, expected_stderr)
214 verify.verify_exit_code(message, exit_code, expected_exit)
215 return exit_code, out, err
217 def run_and_verify_svn(message, expected_stdout, expected_stderr, *varargs):
218 """like run_and_verify_svn2, but the expected exit code is assumed to
219 be 0 if no output is expected on stderr, and 1 otherwise."""
221 expected_exit = 0
222 if expected_stderr is not None:
223 if isinstance(expected_stderr, verify.ExpectedOutput):
224 if not expected_stderr.matches([]):
225 expected_exit = 1
226 elif expected_stderr != []:
227 expected_exit = 1
228 return run_and_verify_svn2(message, expected_stdout, expected_stderr,
229 expected_exit, *varargs)
231 def run_and_verify_svn2(message, expected_stdout, expected_stderr,
232 expected_exit, *varargs):
233 """Invoke main.run_svn() with *VARARGS. Return exit code as int; stdout,
234 stderr as lists of lines (including line terminators). For both
235 EXPECTED_STDOUT and EXPECTED_STDERR, create an appropriate instance of
236 verify.ExpectedOutput (if necessary):
238 - If it is an array of strings, create a vanilla ExpectedOutput.
240 - If it is a single string, create a RegexOutput that must match every
241 line (for stdout) or any line (for stderr) of the expected output.
243 - If it is already an instance of ExpectedOutput
244 (e.g. UnorderedOutput), leave it alone.
246 ...and invoke compare_and_display_lines() on MESSAGE, a label based
247 on the name of the stream being compared (e.g. STDOUT), the
248 ExpectedOutput instance, and the actual output.
250 If EXPECTED_STDOUT is None, do not check stdout.
251 EXPECTED_STDERR may not be None.
253 If output checks pass, the expected and actual codes are compared.
255 If a comparison fails, a Failure will be raised."""
257 if expected_stderr is None:
258 raise verify.SVNIncorrectDatatype("expected_stderr must not be None")
260 want_err = None
261 if isinstance(expected_stderr, verify.ExpectedOutput):
262 if not expected_stderr.matches([]):
263 want_err = True
264 elif expected_stderr != []:
265 want_err = True
267 exit_code, out, err = main.run_svn(want_err, *varargs)
268 verify.verify_outputs(message, out, err, expected_stdout, expected_stderr)
269 verify.verify_exit_code(message, exit_code, expected_exit)
270 return exit_code, out, err
272 def run_and_verify_load(repo_dir, dump_file_content):
273 "Runs 'svnadmin load' and reports any errors."
274 if not isinstance(dump_file_content, list):
275 raise TypeError("dump_file_content argument should have list type")
276 expected_stderr = []
277 exit_code, output, errput = main.run_command_stdin(
278 main.svnadmin_binary, expected_stderr, 0, 1, dump_file_content,
279 'load', '--force-uuid', '--quiet', repo_dir)
281 verify.verify_outputs("Unexpected stderr output", None, errput,
282 None, expected_stderr)
285 def run_and_verify_dump(repo_dir, deltas=False):
286 "Runs 'svnadmin dump' and reports any errors, returning the dump content."
287 if deltas:
288 exit_code, output, errput = main.run_svnadmin('dump', '--deltas',
289 repo_dir)
290 else:
291 exit_code, output, errput = main.run_svnadmin('dump', repo_dir)
292 verify.verify_outputs("Missing expected output(s)", output, errput,
293 verify.AnyOutput, verify.AnyOutput)
294 return output
297 def run_and_verify_svnrdump(dumpfile_content, expected_stdout,
298 expected_stderr, expected_exit, *varargs):
299 """Runs 'svnrdump dump|load' depending on dumpfile_content and
300 reports any errors."""
301 exit_code, output, err = main.run_svnrdump(dumpfile_content, *varargs)
303 verify.verify_outputs("Unexpected output", output, err,
304 expected_stdout, expected_stderr)
305 verify.verify_exit_code("Unexpected return code", exit_code, expected_exit)
306 return output
308 def load_repo(sbox, dumpfile_path = None, dump_str = None):
309 "Loads the dumpfile into sbox"
310 if not dump_str:
311 dump_str = open(dumpfile_path, "rb").read()
313 # Create a virgin repos and working copy
314 main.safe_rmtree(sbox.repo_dir, 1)
315 main.safe_rmtree(sbox.wc_dir, 1)
316 main.create_repos(sbox.repo_dir)
318 # Load the mergetracking dumpfile into the repos, and check it out the repo
319 run_and_verify_load(sbox.repo_dir, dump_str.splitlines(True))
320 run_and_verify_svn(None, None, [], "co", sbox.repo_url, sbox.wc_dir)
322 return dump_str
325 ######################################################################
326 # Subversion Actions
328 # These are all routines that invoke 'svn' in particular ways, and
329 # then verify the results by comparing expected trees with actual
330 # trees.
334 def run_and_verify_checkout2(do_remove,
335 URL, wc_dir_name, output_tree, disk_tree,
336 singleton_handler_a = None,
337 a_baton = None,
338 singleton_handler_b = None,
339 b_baton = None,
340 *args):
341 """Checkout the URL into a new directory WC_DIR_NAME. *ARGS are any
342 extra optional args to the checkout subcommand.
344 The subcommand output will be verified against OUTPUT_TREE,
345 and the working copy itself will be verified against DISK_TREE.
346 For the latter comparison, SINGLETON_HANDLER_A and
347 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
348 function's doc string for more details. Return if successful, raise
349 on failure.
351 WC_DIR_NAME is deleted if DO_REMOVE is True.
354 if isinstance(output_tree, wc.State):
355 output_tree = output_tree.old_tree()
356 if isinstance(disk_tree, wc.State):
357 disk_tree = disk_tree.old_tree()
359 # Remove dir if it's already there, unless this is a forced checkout.
360 # In that case assume we want to test a forced checkout's toleration
361 # of obstructing paths.
362 if do_remove:
363 main.safe_rmtree(wc_dir_name)
365 # Checkout and make a tree of the output, using l:foo/p:bar
366 ### todo: svn should not be prompting for auth info when using
367 ### repositories with no auth/auth requirements
368 exit_code, output, errput = main.run_svn(None, 'co',
369 URL, wc_dir_name, *args)
370 actual = tree.build_tree_from_checkout(output)
372 # Verify actual output against expected output.
373 try:
374 tree.compare_trees("output", actual, output_tree)
375 except tree.SVNTreeUnequal:
376 print("ACTUAL OUTPUT TREE:")
377 tree.dump_tree_script(actual, wc_dir_name + os.sep)
378 raise
380 # Create a tree by scanning the working copy
381 actual = tree.build_tree_from_wc(wc_dir_name)
383 # Verify expected disk against actual disk.
384 try:
385 tree.compare_trees("disk", actual, disk_tree,
386 singleton_handler_a, a_baton,
387 singleton_handler_b, b_baton)
388 except tree.SVNTreeUnequal:
389 print("ACTUAL DISK TREE:")
390 tree.dump_tree_script(actual, wc_dir_name + os.sep)
391 raise
393 def run_and_verify_checkout(URL, wc_dir_name, output_tree, disk_tree,
394 singleton_handler_a = None,
395 a_baton = None,
396 singleton_handler_b = None,
397 b_baton = None,
398 *args):
399 """Same as run_and_verify_checkout2(), but without the DO_REMOVE arg.
400 WC_DIR_NAME is deleted if present unless the '--force' option is passed
401 in *ARGS."""
404 # Remove dir if it's already there, unless this is a forced checkout.
405 # In that case assume we want to test a forced checkout's toleration
406 # of obstructing paths.
407 return run_and_verify_checkout2(('--force' not in args),
408 URL, wc_dir_name, output_tree, disk_tree,
409 singleton_handler_a,
410 a_baton,
411 singleton_handler_b,
412 b_baton,
413 *args)
416 def run_and_verify_export(URL, export_dir_name, output_tree, disk_tree,
417 *args):
418 """Export the URL into a new directory WC_DIR_NAME.
420 The subcommand output will be verified against OUTPUT_TREE,
421 and the exported copy itself will be verified against DISK_TREE.
422 Return if successful, raise on failure.
424 assert isinstance(output_tree, wc.State)
425 assert isinstance(disk_tree, wc.State)
427 disk_tree = disk_tree.old_tree()
428 output_tree = output_tree.old_tree()
430 # Export and make a tree of the output, using l:foo/p:bar
431 ### todo: svn should not be prompting for auth info when using
432 ### repositories with no auth/auth requirements
433 exit_code, output, errput = main.run_svn(None, 'export',
434 URL, export_dir_name, *args)
435 actual = tree.build_tree_from_checkout(output)
437 # Verify actual output against expected output.
438 try:
439 tree.compare_trees("output", actual, output_tree)
440 except tree.SVNTreeUnequal:
441 print("ACTUAL OUTPUT TREE:")
442 tree.dump_tree_script(actual, export_dir_name + os.sep)
443 raise
445 # Create a tree by scanning the working copy. Don't ignore
446 # the .svn directories so that we generate an error if they
447 # happen to show up.
448 actual = tree.build_tree_from_wc(export_dir_name, ignore_svn=False)
450 # Verify expected disk against actual disk.
451 try:
452 tree.compare_trees("disk", actual, disk_tree)
453 except tree.SVNTreeUnequal:
454 print("ACTUAL DISK TREE:")
455 tree.dump_tree_script(actual, export_dir_name + os.sep)
456 raise
459 # run_and_verify_log_xml
461 class LogEntry:
462 def __init__(self, revision, changed_paths=None, revprops=None):
463 self.revision = revision
464 if changed_paths == None:
465 self.changed_paths = {}
466 else:
467 self.changed_paths = changed_paths
468 if revprops == None:
469 self.revprops = {}
470 else:
471 self.revprops = revprops
473 def assert_changed_paths(self, changed_paths):
474 """Not implemented, so just raises svntest.Failure.
476 raise Failure('NOT IMPLEMENTED')
478 def assert_revprops(self, revprops):
479 """Assert that the dict revprops is the same as this entry's revprops.
481 Raises svntest.Failure if not.
483 if self.revprops != revprops:
484 raise Failure('\n' + '\n'.join(difflib.ndiff(
485 pprint.pformat(revprops).splitlines(),
486 pprint.pformat(self.revprops).splitlines())))
488 class LogParser:
489 def parse(self, data):
490 """Return a list of LogEntrys parsed from the sequence of strings data.
492 This is the only method of interest to callers.
494 try:
495 for i in data:
496 self.parser.Parse(i)
497 self.parser.Parse('', True)
498 except xml.parsers.expat.ExpatError, e:
499 raise verify.SVNUnexpectedStdout('%s\n%s\n' % (e, ''.join(data),))
500 return self.entries
502 def __init__(self):
503 # for expat
504 self.parser = xml.parsers.expat.ParserCreate()
505 self.parser.StartElementHandler = self.handle_start_element
506 self.parser.EndElementHandler = self.handle_end_element
507 self.parser.CharacterDataHandler = self.handle_character_data
508 # Ignore some things.
509 self.ignore_elements('log', 'paths', 'path', 'revprops')
510 self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
511 # internal state
512 self.cdata = []
513 self.property = None
514 # the result
515 self.entries = []
517 def ignore(self, *args, **kwargs):
518 del self.cdata[:]
519 def ignore_tags(self, *args):
520 for tag in args:
521 setattr(self, tag, self.ignore)
522 def ignore_elements(self, *args):
523 for element in args:
524 self.ignore_tags(element + '_start', element + '_end')
526 # expat handlers
527 def handle_start_element(self, name, attrs):
528 getattr(self, name + '_start')(attrs)
529 def handle_end_element(self, name):
530 getattr(self, name + '_end')()
531 def handle_character_data(self, data):
532 self.cdata.append(data)
534 # element handler utilities
535 def use_cdata(self):
536 result = ''.join(self.cdata).strip()
537 del self.cdata[:]
538 return result
539 def svn_prop(self, name):
540 self.entries[-1].revprops['svn:' + name] = self.use_cdata()
542 # element handlers
543 def logentry_start(self, attrs):
544 self.entries.append(LogEntry(int(attrs['revision'])))
545 def author_end(self):
546 self.svn_prop('author')
547 def msg_end(self):
548 self.svn_prop('log')
549 def date_end(self):
550 # svn:date could be anything, so just note its presence.
551 self.cdata[:] = ['']
552 self.svn_prop('date')
553 def property_start(self, attrs):
554 self.property = attrs['name']
555 def property_end(self):
556 self.entries[-1].revprops[self.property] = self.use_cdata()
558 def run_and_verify_log_xml(message=None, expected_paths=None,
559 expected_revprops=None, expected_stdout=None,
560 expected_stderr=None, args=[]):
561 """Call run_and_verify_svn with log --xml and args (optional) as command
562 arguments, and pass along message, expected_stdout, and expected_stderr.
564 If message is None, pass the svn log command as message.
566 expected_paths checking is not yet implemented.
568 expected_revprops is an optional list of dicts, compared to each
569 revision's revprops. The list must be in the same order the log entries
570 come in. Any svn:date revprops in the dicts must be '' in order to
571 match, as the actual dates could be anything.
573 expected_paths and expected_revprops are ignored if expected_stdout or
574 expected_stderr is specified.
576 if message == None:
577 message = ' '.join(args)
579 # We'll parse the output unless the caller specifies expected_stderr or
580 # expected_stdout for run_and_verify_svn.
581 parse = True
582 if expected_stderr == None:
583 expected_stderr = []
584 else:
585 parse = False
586 if expected_stdout != None:
587 parse = False
589 log_args = list(args)
590 if expected_paths != None:
591 log_args.append('-v')
593 (exit_code, stdout, stderr) = run_and_verify_svn(
594 message, expected_stdout, expected_stderr,
595 'log', '--xml', *log_args)
596 if not parse:
597 return
599 entries = LogParser().parse(stdout)
600 for index in range(len(entries)):
601 entry = entries[index]
602 if expected_revprops != None:
603 entry.assert_revprops(expected_revprops[index])
604 if expected_paths != None:
605 entry.assert_changed_paths(expected_paths[index])
608 def verify_update(actual_output,
609 actual_mergeinfo_output,
610 actual_elision_output,
611 wc_dir_name,
612 output_tree,
613 mergeinfo_output_tree,
614 elision_output_tree,
615 disk_tree,
616 status_tree,
617 singleton_handler_a=None,
618 a_baton=None,
619 singleton_handler_b=None,
620 b_baton=None,
621 check_props=False):
622 """Verify update of WC_DIR_NAME.
624 The subcommand output (found in ACTUAL_OUTPUT, ACTUAL_MERGEINFO_OUTPUT,
625 and ACTUAL_ELISION_OUTPUT) will be verified against OUTPUT_TREE,
626 MERGEINFO_OUTPUT_TREE, and ELISION_OUTPUT_TREE respectively (if any of
627 these is provided, they may be None in which case a comparison is not
628 done). The working copy itself will be verified against DISK_TREE (if
629 provided), and the working copy's 'svn status' output will be verified
630 against STATUS_TREE (if provided). (This is a good way to check that
631 revision numbers were bumped.)
633 Return if successful, raise on failure.
635 For the comparison with DISK_TREE, pass SINGLETON_HANDLER_A and
636 SINGLETON_HANDLER_B to tree.compare_trees -- see that function's doc
637 string for more details. If CHECK_PROPS is set, then disk
638 comparison will examine props."""
640 if isinstance(actual_output, wc.State):
641 actual_output = actual_output.old_tree()
642 if isinstance(actual_mergeinfo_output, wc.State):
643 actual_mergeinfo_output = actual_mergeinfo_output.old_tree()
644 if isinstance(actual_elision_output, wc.State):
645 actual_elision_output = actual_elision_output.old_tree()
646 if isinstance(output_tree, wc.State):
647 output_tree = output_tree.old_tree()
648 if isinstance(mergeinfo_output_tree, wc.State):
649 mergeinfo_output_tree = mergeinfo_output_tree.old_tree()
650 if isinstance(elision_output_tree, wc.State):
651 elision_output_tree = elision_output_tree.old_tree()
652 if isinstance(disk_tree, wc.State):
653 disk_tree = disk_tree.old_tree()
654 if isinstance(status_tree, wc.State):
655 status_tree = status_tree.old_tree()
657 # Verify actual output against expected output.
658 if output_tree:
659 try:
660 tree.compare_trees("output", actual_output, output_tree)
661 except tree.SVNTreeUnequal:
662 print("ACTUAL OUTPUT TREE:")
663 tree.dump_tree_script(actual_output, wc_dir_name + os.sep)
664 raise
666 # Verify actual mergeinfo recording output against expected output.
667 if mergeinfo_output_tree:
668 try:
669 tree.compare_trees("mergeinfo_output", actual_mergeinfo_output,
670 mergeinfo_output_tree)
671 except tree.SVNTreeUnequal:
672 print("ACTUAL MERGEINFO OUTPUT TREE:")
673 tree.dump_tree_script(actual_mergeinfo_output,
674 wc_dir_name + os.sep)
675 raise
677 # Verify actual mergeinfo elision output against expected output.
678 if elision_output_tree:
679 try:
680 tree.compare_trees("elision_output", actual_elision_output,
681 elision_output_tree)
682 except tree.SVNTreeUnequal:
683 print("ACTUAL ELISION OUTPUT TREE:")
684 tree.dump_tree_script(actual_elision_output,
685 wc_dir_name + os.sep)
686 raise
688 # Create a tree by scanning the working copy, and verify it
689 if disk_tree:
690 actual_disk = tree.build_tree_from_wc(wc_dir_name, check_props)
691 try:
692 tree.compare_trees("disk", actual_disk, disk_tree,
693 singleton_handler_a, a_baton,
694 singleton_handler_b, b_baton)
695 except tree.SVNTreeUnequal:
696 print("ACTUAL DISK TREE:")
697 tree.dump_tree_script(actual_disk)
698 raise
700 # Verify via 'status' command too, if possible.
701 if status_tree:
702 run_and_verify_status(wc_dir_name, status_tree)
705 def verify_disk(wc_dir_name, disk_tree, check_props=False):
706 """Verify WC_DIR_NAME against DISK_TREE. If CHECK_PROPS is set,
707 the comparison will examin props. Returns if successful, raises on
708 failure."""
709 verify_update(None, None, None, wc_dir_name, None, None, None, disk_tree,
710 None, check_props=check_props)
714 def run_and_verify_update(wc_dir_name,
715 output_tree, disk_tree, status_tree,
716 error_re_string = None,
717 singleton_handler_a = None,
718 a_baton = None,
719 singleton_handler_b = None,
720 b_baton = None,
721 check_props = False,
722 *args):
724 """Update WC_DIR_NAME. *ARGS are any extra optional args to the
725 update subcommand. NOTE: If *ARGS is specified at all, explicit
726 target paths must be passed in *ARGS as well (or a default `.' will
727 be chosen by the 'svn' binary). This allows the caller to update
728 many items in a single working copy dir, but still verify the entire
729 working copy dir.
731 If ERROR_RE_STRING, the update must exit with error, and the error
732 message must match regular expression ERROR_RE_STRING.
734 Else if ERROR_RE_STRING is None, then:
736 If OUTPUT_TREE is not None, the subcommand output will be verified
737 against OUTPUT_TREE. If DISK_TREE is not None, the working copy
738 itself will be verified against DISK_TREE. If STATUS_TREE is not
739 None, the 'svn status' output will be verified against STATUS_TREE.
740 (This is a good way to check that revision numbers were bumped.)
742 For the DISK_TREE verification, SINGLETON_HANDLER_A and
743 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
744 function's doc string for more details.
746 If CHECK_PROPS is set, then disk comparison will examine props.
748 Return if successful, raise on failure."""
750 # Update and make a tree of the output.
751 if len(args):
752 exit_code, output, errput = main.run_svn(error_re_string, 'up', *args)
753 else:
754 exit_code, output, errput = main.run_svn(error_re_string,
755 'up', wc_dir_name,
756 *args)
758 if error_re_string:
759 rm = re.compile(error_re_string)
760 for line in errput:
761 match = rm.search(line)
762 if match:
763 return
764 raise main.SVNUnmatchedError
766 actual = wc.State.from_checkout(output)
767 verify_update(actual, None, None, wc_dir_name,
768 output_tree, None, None, disk_tree, status_tree,
769 singleton_handler_a, a_baton,
770 singleton_handler_b, b_baton,
771 check_props)
774 def run_and_parse_info(*args):
775 """Run 'svn info' and parse its output into a list of dicts,
776 one dict per target."""
778 # the returned array
779 all_infos = []
781 # per-target variables
782 iter_info = {}
783 prev_key = None
784 lock_comment_lines = 0
785 lock_comments = []
787 exit_code, output, errput = main.run_svn(None, 'info', *args)
789 for line in output:
790 line = line[:-1] # trim '\n'
792 if lock_comment_lines > 0:
793 # mop up any lock comment lines
794 lock_comments.append(line)
795 lock_comment_lines = lock_comment_lines - 1
796 if lock_comment_lines == 0:
797 iter_info[prev_key] = lock_comments
798 elif len(line) == 0:
799 # separator line between items
800 all_infos.append(iter_info)
801 iter_info = {}
802 prev_key = None
803 lock_comment_lines = 0
804 lock_comments = []
805 elif line[0].isspace():
806 # continuation line (for tree conflicts)
807 iter_info[prev_key] += line[1:]
808 else:
809 # normal line
810 key, value = line.split(':', 1)
812 if re.search(' \(\d+ lines?\)$', key):
813 # numbered continuation lines
814 match = re.match('^(.*) \((\d+) lines?\)$', key)
815 key = match.group(1)
816 lock_comment_lines = int(match.group(2))
817 elif len(value) > 1:
818 # normal normal line
819 iter_info[key] = value[1:]
820 else:
821 ### originally added for "Tree conflict:\n" lines;
822 ### tree-conflicts output format has changed since then
823 # continuation lines are implicit (prefixed by whitespace)
824 iter_info[key] = ''
825 prev_key = key
827 return all_infos
829 def run_and_verify_info(expected_infos, *args):
830 """Run 'svn info' with the arguments in *ARGS and verify the results
831 against expected_infos. The latter should be a list of dicts (in the
832 same order as the targets).
834 In the dicts, each key is the before-the-colon part of the 'svn info' output,
835 and each value is either None (meaning that the key should *not* appear in
836 the 'svn info' output) or a regex matching the output value. Output lines
837 not matching a key in the dict are ignored.
839 Return if successful, raise on failure."""
841 actual_infos = run_and_parse_info(*args)
843 try:
844 # zip() won't complain, so check this manually
845 if len(actual_infos) != len(expected_infos):
846 raise verify.SVNUnexpectedStdout(
847 "Expected %d infos, found %d infos"
848 % (len(expected_infos), len(actual_infos)))
850 for actual, expected in zip(actual_infos, expected_infos):
851 # compare dicts
852 for key, value in expected.items():
853 assert ':' not in key # caller passed impossible expectations?
854 if value is None and key in actual:
855 raise main.SVNLineUnequal("Found unexpected key '%s' with value '%s'"
856 % (key, actual[key]))
857 if value is not None and key not in actual:
858 raise main.SVNLineUnequal("Expected key '%s' (with value '%s') "
859 "not found" % (key, value))
860 if value is not None and not re.search(value, actual[key]):
861 raise verify.SVNUnexpectedStdout("Values of key '%s' don't match:\n"
862 " Expected: '%s' (regex)\n"
863 " Found: '%s' (string)\n"
864 % (key, value, actual[key]))
866 except:
867 sys.stderr.write("Bad 'svn info' output:\n"
868 " Received: %s\n"
869 " Expected: %s\n"
870 % (actual_infos, expected_infos))
871 raise
873 def run_and_verify_merge(dir, rev1, rev2, url1, url2,
874 output_tree,
875 mergeinfo_output_tree,
876 elision_output_tree,
877 disk_tree, status_tree, skip_tree,
878 error_re_string = None,
879 singleton_handler_a = None,
880 a_baton = None,
881 singleton_handler_b = None,
882 b_baton = None,
883 check_props = False,
884 dry_run = True,
885 *args):
886 """Run 'svn merge URL1@REV1 URL2@REV2 DIR' if URL2 is not None
887 (for a three-way merge between URLs and WC).
889 If URL2 is None, run 'svn merge -rREV1:REV2 URL1 DIR'. If both REV1
890 and REV2 are None, leave off the '-r' argument.
892 If ERROR_RE_STRING, the merge must exit with error, and the error
893 message must match regular expression ERROR_RE_STRING.
895 Else if ERROR_RE_STRING is None, then:
897 The subcommand output will be verified against OUTPUT_TREE. Output
898 related to mergeinfo notifications will be verified against
899 MERGEINFO_OUTPUT_TREE if that is not None. Output related to mergeinfo
900 elision will be verified against ELISION_OUTPUT_TREE if that is not None.
901 The working copy itself will be verified against DISK_TREE. If optional
902 STATUS_TREE is given, then 'svn status' output will be compared. The
903 'skipped' merge output will be compared to SKIP_TREE.
905 For the DISK_TREE verification, SINGLETON_HANDLER_A and
906 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
907 function's doc string for more details.
909 If CHECK_PROPS is set, then disk comparison will examine props.
911 If DRY_RUN is set then a --dry-run merge will be carried out first and
912 the output compared with that of the full merge.
914 Return if successful, raise on failure.
916 *ARGS are any extra optional args to the merge subcommand.
917 NOTE: If *ARGS is specified at all, an explicit target path must be passed
918 in *ARGS as well. This allows the caller to merge into single items inside
919 the working copy, but still verify the entire working copy dir. """
921 merge_command = [ "merge" ]
922 if url2:
923 merge_command.extend((url1 + "@" + str(rev1), url2 + "@" + str(rev2)))
924 else:
925 if not (rev1 is None and rev2 is None):
926 merge_command.append("-r" + str(rev1) + ":" + str(rev2))
927 merge_command.append(url1)
928 if len(args) == 0:
929 merge_command.append(dir)
930 merge_command = tuple(merge_command)
932 if dry_run:
933 pre_disk = tree.build_tree_from_wc(dir)
934 dry_run_command = merge_command + ('--dry-run',)
935 dry_run_command = dry_run_command + args
936 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
937 *dry_run_command)
938 post_disk = tree.build_tree_from_wc(dir)
939 try:
940 tree.compare_trees("disk", post_disk, pre_disk)
941 except tree.SVNTreeError:
942 print("=============================================================")
943 print("Dry-run merge altered working copy")
944 print("=============================================================")
945 raise
948 # Update and make a tree of the output.
949 merge_command = merge_command + args
950 exit_code, out, err = main.run_svn(error_re_string, *merge_command)
952 if error_re_string:
953 if not error_re_string.startswith(".*"):
954 error_re_string = ".*(" + error_re_string + ")"
955 expected_err = verify.RegexOutput(error_re_string, match_all=False)
956 verify.verify_outputs(None, None, err, None, expected_err)
957 return
958 elif err:
959 raise verify.SVNUnexpectedStderr(err)
961 # Split the output into that related to application of the actual diff
962 # and that related to the recording of mergeinfo describing the merge.
963 merge_diff_out = []
964 mergeinfo_notification_out = []
965 mergeinfo_elision_out = []
966 mergeinfo_notifications = False
967 elision_notifications = False
968 for line in out:
969 if line.startswith('--- Recording'):
970 mergeinfo_notifications = True
971 elision_notifications = False
972 elif line.startswith('--- Eliding'):
973 mergeinfo_notifications = False
974 elision_notifications = True
975 elif line.startswith('--- Merging') or \
976 line.startswith('--- Reverse-merging') or \
977 line.startswith('Summary of conflicts') or \
978 line.startswith('Skipped missing target'):
979 mergeinfo_notifications = False
980 elision_notifications = False
982 if mergeinfo_notifications:
983 mergeinfo_notification_out.append(line)
984 elif elision_notifications:
985 mergeinfo_elision_out.append(line)
986 else:
987 merge_diff_out.append(line)
989 if dry_run and merge_diff_out != out_dry:
990 # Due to the way ra_serf works, it's possible that the dry-run and
991 # real merge operations did the same thing, but the output came in
992 # a different order. Let's see if maybe that's the case.
994 # NOTE: Would be nice to limit this dance to serf tests only, but...
995 out_copy = merge_diff_out[:]
996 out_dry_copy = out_dry[:]
997 out_copy.sort()
998 out_dry_copy.sort()
999 if out_copy != out_dry_copy:
1000 print("=============================================================")
1001 print("Merge outputs differ")
1002 print("The dry-run merge output:")
1003 for x in out_dry:
1004 sys.stdout.write(x)
1005 print("The full merge output:")
1006 for x in out:
1007 sys.stdout.write(x)
1008 print("=============================================================")
1009 raise main.SVNUnmatchedError
1011 def missing_skip(a, b):
1012 print("=============================================================")
1013 print("Merge failed to skip: " + a.path)
1014 print("=============================================================")
1015 raise Failure
1016 def extra_skip(a, b):
1017 print("=============================================================")
1018 print("Merge unexpectedly skipped: " + a.path)
1019 print("=============================================================")
1020 raise Failure
1022 myskiptree = tree.build_tree_from_skipped(out)
1023 if isinstance(skip_tree, wc.State):
1024 skip_tree = skip_tree.old_tree()
1025 try:
1026 tree.compare_trees("skip", myskiptree, skip_tree,
1027 extra_skip, None, missing_skip, None)
1028 except tree.SVNTreeUnequal:
1029 print("ACTUAL SKIP TREE:")
1030 tree.dump_tree_script(myskiptree, dir + os.sep)
1031 raise
1033 actual_diff = svntest.wc.State.from_checkout(merge_diff_out, False)
1034 actual_mergeinfo = svntest.wc.State.from_checkout(mergeinfo_notification_out,
1035 False)
1036 actual_elision = svntest.wc.State.from_checkout(mergeinfo_elision_out,
1037 False)
1038 verify_update(actual_diff, actual_mergeinfo, actual_elision, dir,
1039 output_tree, mergeinfo_output_tree, elision_output_tree,
1040 disk_tree, status_tree,
1041 singleton_handler_a, a_baton,
1042 singleton_handler_b, b_baton,
1043 check_props)
1046 def run_and_verify_patch(dir, patch_path,
1047 output_tree, disk_tree, status_tree, skip_tree,
1048 error_re_string=None,
1049 check_props=False,
1050 dry_run=True,
1051 *args):
1052 """Run 'svn patch patch_path DIR'.
1054 If ERROR_RE_STRING, 'svn patch' must exit with error, and the error
1055 message must match regular expression ERROR_RE_STRING.
1057 Else if ERROR_RE_STRING is None, then:
1059 The subcommand output will be verified against OUTPUT_TREE, and the
1060 working copy itself will be verified against DISK_TREE. If optional
1061 STATUS_TREE is given, then 'svn status' output will be compared.
1062 The 'skipped' merge output will be compared to SKIP_TREE.
1064 If CHECK_PROPS is set, then disk comparison will examine props.
1066 If DRY_RUN is set then a --dry-run patch will be carried out first and
1067 the output compared with that of the full patch application.
1069 Returns if successful, raises on failure."""
1071 patch_command = [ "patch" ]
1072 patch_command.append(patch_path)
1073 patch_command.append(dir)
1074 patch_command = tuple(patch_command)
1076 if dry_run:
1077 pre_disk = tree.build_tree_from_wc(dir)
1078 dry_run_command = patch_command + ('--dry-run',)
1079 dry_run_command = dry_run_command + args
1080 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
1081 *dry_run_command)
1082 post_disk = tree.build_tree_from_wc(dir)
1083 try:
1084 tree.compare_trees("disk", post_disk, pre_disk)
1085 except tree.SVNTreeError:
1086 print("=============================================================")
1087 print("'svn patch --dry-run' altered working copy")
1088 print("=============================================================")
1089 raise
1091 # Update and make a tree of the output.
1092 patch_command = patch_command + args
1093 exit_code, out, err = main.run_svn(True, *patch_command)
1095 if error_re_string:
1096 rm = re.compile(error_re_string)
1097 match = None
1098 for line in err:
1099 match = rm.search(line)
1100 if match:
1101 break
1102 if not match:
1103 raise main.SVNUnmatchedError
1104 elif err:
1105 print("UNEXPECTED STDERR:")
1106 for x in err:
1107 sys.stdout.write(x)
1108 raise verify.SVNUnexpectedStderr
1110 if dry_run and out != out_dry:
1111 print("=============================================================")
1112 print("Outputs differ")
1113 print("'svn patch --dry-run' output:")
1114 for x in out_dry:
1115 sys.stdout.write(x)
1116 print("'svn patch' output:")
1117 for x in out:
1118 sys.stdout.write(x)
1119 print("=============================================================")
1120 raise main.SVNUnmatchedError
1122 def missing_skip(a, b):
1123 print("=============================================================")
1124 print("'svn patch' failed to skip: " + a.path)
1125 print("=============================================================")
1126 raise Failure
1127 def extra_skip(a, b):
1128 print("=============================================================")
1129 print("'svn patch' unexpectedly skipped: " + a.path)
1130 print("=============================================================")
1131 raise Failure
1133 myskiptree = tree.build_tree_from_skipped(out)
1134 if isinstance(skip_tree, wc.State):
1135 skip_tree = skip_tree.old_tree()
1136 tree.compare_trees("skip", myskiptree, skip_tree,
1137 extra_skip, None, missing_skip, None)
1139 mytree = tree.build_tree_from_checkout(out, 0)
1141 # when the expected output is a list, we want a line-by-line
1142 # comparison to happen instead of a tree comparison
1143 if isinstance(output_tree, list):
1144 verify.verify_outputs(None, out, err, output_tree, error_re_string)
1145 output_tree = None
1147 verify_update(mytree, None, None, dir,
1148 output_tree, None, None, disk_tree, status_tree,
1149 check_props=check_props)
1152 def run_and_verify_mergeinfo(error_re_string = None,
1153 expected_output = [],
1154 *args):
1155 """Run 'svn mergeinfo ARGS', and compare the result against
1156 EXPECTED_OUTPUT, a list of string representations of revisions
1157 expected in the output. Raise an exception if an unexpected
1158 output is encountered."""
1160 mergeinfo_command = ["mergeinfo"]
1161 mergeinfo_command.extend(args)
1162 exit_code, out, err = main.run_svn(error_re_string, *mergeinfo_command)
1164 if error_re_string:
1165 if not error_re_string.startswith(".*"):
1166 error_re_string = ".*(" + error_re_string + ")"
1167 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1168 verify.verify_outputs(None, None, err, None, expected_err)
1169 return
1171 out = sorted([_f for _f in [x.rstrip()[1:] for x in out] if _f])
1172 expected_output.sort()
1173 extra_out = []
1174 if out != expected_output:
1175 exp_hash = dict.fromkeys(expected_output)
1176 for rev in out:
1177 if rev in exp_hash:
1178 del(exp_hash[rev])
1179 else:
1180 extra_out.append(rev)
1181 extra_exp = list(exp_hash.keys())
1182 raise Exception("Unexpected 'svn mergeinfo' output:\n"
1183 " expected but not found: %s\n"
1184 " found but not expected: %s"
1185 % (', '.join([str(x) for x in extra_exp]),
1186 ', '.join([str(x) for x in extra_out])))
1189 def run_and_verify_switch(wc_dir_name,
1190 wc_target,
1191 switch_url,
1192 output_tree, disk_tree, status_tree,
1193 error_re_string = None,
1194 singleton_handler_a = None,
1195 a_baton = None,
1196 singleton_handler_b = None,
1197 b_baton = None,
1198 check_props = False,
1199 *args):
1201 """Switch WC_TARGET (in working copy dir WC_DIR_NAME) to SWITCH_URL.
1203 If ERROR_RE_STRING, the switch must exit with error, and the error
1204 message must match regular expression ERROR_RE_STRING.
1206 Else if ERROR_RE_STRING is None, then:
1208 The subcommand output will be verified against OUTPUT_TREE, and the
1209 working copy itself will be verified against DISK_TREE. If optional
1210 STATUS_TREE is given, then 'svn status' output will be
1211 compared. (This is a good way to check that revision numbers were
1212 bumped.)
1214 For the DISK_TREE verification, SINGLETON_HANDLER_A and
1215 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
1216 function's doc string for more details.
1218 If CHECK_PROPS is set, then disk comparison will examine props.
1220 Return if successful, raise on failure."""
1222 # Update and make a tree of the output.
1223 exit_code, output, errput = main.run_svn(error_re_string, 'switch',
1224 switch_url, wc_target, *args)
1226 if error_re_string:
1227 if not error_re_string.startswith(".*"):
1228 error_re_string = ".*(" + error_re_string + ")"
1229 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1230 verify.verify_outputs(None, None, errput, None, expected_err)
1231 return
1232 elif errput:
1233 raise verify.SVNUnexpectedStderr(err)
1235 actual = wc.State.from_checkout(output)
1237 verify_update(actual, None, None, wc_dir_name,
1238 output_tree, None, None, disk_tree, status_tree,
1239 singleton_handler_a, a_baton,
1240 singleton_handler_b, b_baton,
1241 check_props)
1243 def process_output_for_commit(output):
1244 """Helper for run_and_verify_commit(), also used in the factory."""
1245 # Remove the final output line, and verify that the commit succeeded.
1246 lastline = ""
1247 if len(output):
1248 lastline = output.pop().strip()
1250 cm = re.compile("(Committed|Imported) revision [0-9]+.")
1251 match = cm.search(lastline)
1252 if not match:
1253 print("ERROR: commit did not succeed.")
1254 print("The final line from 'svn ci' was:")
1255 print(lastline)
1256 raise main.SVNCommitFailure
1258 # The new 'final' line in the output is either a regular line that
1259 # mentions {Adding, Deleting, Sending, ...}, or it could be a line
1260 # that says "Transmitting file data ...". If the latter case, we
1261 # want to remove the line from the output; it should be ignored when
1262 # building a tree.
1263 if len(output):
1264 lastline = output.pop()
1266 tm = re.compile("Transmitting file data.+")
1267 match = tm.search(lastline)
1268 if not match:
1269 # whoops, it was important output, put it back.
1270 output.append(lastline)
1272 return output
1275 def run_and_verify_commit(wc_dir_name, output_tree, status_tree,
1276 error_re_string = None,
1277 *args):
1278 """Commit and verify results within working copy WC_DIR_NAME,
1279 sending ARGS to the commit subcommand.
1281 The subcommand output will be verified against OUTPUT_TREE. If
1282 optional STATUS_TREE is given, then 'svn status' output will
1283 be compared. (This is a good way to check that revision numbers
1284 were bumped.)
1286 If ERROR_RE_STRING is None, the commit must not exit with error. If
1287 ERROR_RE_STRING is a string, the commit must exit with error, and
1288 the error message must match regular expression ERROR_RE_STRING.
1290 Return if successful, raise on failure."""
1292 if isinstance(output_tree, wc.State):
1293 output_tree = output_tree.old_tree()
1294 if isinstance(status_tree, wc.State):
1295 status_tree = status_tree.old_tree()
1297 # Commit.
1298 exit_code, output, errput = main.run_svn(error_re_string, 'ci',
1299 '-m', 'log msg',
1300 *args)
1302 if error_re_string:
1303 if not error_re_string.startswith(".*"):
1304 error_re_string = ".*(" + error_re_string + ")"
1305 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1306 verify.verify_outputs(None, None, errput, None, expected_err)
1307 return
1309 # Else not expecting error:
1311 # Convert the output into a tree.
1312 output = process_output_for_commit(output)
1313 actual = tree.build_tree_from_commit(output)
1315 # Verify actual output against expected output.
1316 try:
1317 tree.compare_trees("output", actual, output_tree)
1318 except tree.SVNTreeError:
1319 verify.display_trees("Output of commit is unexpected",
1320 "OUTPUT TREE", output_tree, actual)
1321 print("ACTUAL OUTPUT TREE:")
1322 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1323 raise
1325 # Verify via 'status' command too, if possible.
1326 if status_tree:
1327 run_and_verify_status(wc_dir_name, status_tree)
1330 # This function always passes '-q' to the status command, which
1331 # suppresses the printing of any unversioned or nonexistent items.
1332 def run_and_verify_status(wc_dir_name, output_tree,
1333 singleton_handler_a = None,
1334 a_baton = None,
1335 singleton_handler_b = None,
1336 b_baton = None):
1337 """Run 'status' on WC_DIR_NAME and compare it with the
1338 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1339 be passed to tree.compare_trees - see that function's doc string for
1340 more details.
1341 Returns on success, raises on failure."""
1343 if isinstance(output_tree, wc.State):
1344 output_state = output_tree
1345 output_tree = output_tree.old_tree()
1346 else:
1347 output_state = None
1349 exit_code, output, errput = main.run_svn(None, 'status', '-v', '-u', '-q',
1350 wc_dir_name)
1352 actual = tree.build_tree_from_status(output)
1354 # Verify actual output against expected output.
1355 try:
1356 tree.compare_trees("status", actual, output_tree,
1357 singleton_handler_a, a_baton,
1358 singleton_handler_b, b_baton)
1359 except tree.SVNTreeError:
1360 verify.display_trees(None, 'STATUS OUTPUT TREE', output_tree, actual)
1361 print("ACTUAL STATUS TREE:")
1362 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1363 raise
1365 # if we have an output State, and we can/are-allowed to create an
1366 # entries-based State, then compare the two.
1367 if output_state:
1368 entries_state = wc.State.from_entries(wc_dir_name)
1369 if entries_state:
1370 tweaked = output_state.copy()
1371 tweaked.tweak_for_entries_compare()
1372 try:
1373 tweaked.compare_and_display('entries', entries_state)
1374 except tree.SVNTreeUnequal:
1375 ### do something more
1376 raise
1379 # A variant of previous func, but doesn't pass '-q'. This allows us
1380 # to verify unversioned or nonexistent items in the list.
1381 def run_and_verify_unquiet_status(wc_dir_name, status_tree):
1382 """Run 'status' on WC_DIR_NAME and compare it with the
1383 expected STATUS_TREE.
1384 Returns on success, raises on failure."""
1386 if isinstance(status_tree, wc.State):
1387 status_tree = status_tree.old_tree()
1389 exit_code, output, errput = main.run_svn(None, 'status', '-v',
1390 '-u', wc_dir_name)
1392 actual = tree.build_tree_from_status(output)
1394 # Verify actual output against expected output.
1395 try:
1396 tree.compare_trees("UNQUIET STATUS", actual, status_tree)
1397 except tree.SVNTreeError:
1398 print("ACTUAL UNQUIET STATUS TREE:")
1399 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1400 raise
1402 def run_and_verify_diff_summarize_xml(error_re_string = [],
1403 expected_prefix = None,
1404 expected_paths = [],
1405 expected_items = [],
1406 expected_props = [],
1407 expected_kinds = [],
1408 *args):
1409 """Run 'diff --summarize --xml' with the arguments *ARGS, which should
1410 contain all arguments beyond for your 'diff --summarize --xml' omitting
1411 said arguments. EXPECTED_PREFIX will store a "common" path prefix
1412 expected to be at the beginning of each summarized path. If
1413 EXPECTED_PREFIX is None, then EXPECTED_PATHS will need to be exactly
1414 as 'svn diff --summarize --xml' will output. If ERROR_RE_STRING, the
1415 command must exit with error, and the error message must match regular
1416 expression ERROR_RE_STRING.
1418 Else if ERROR_RE_STRING is None, the subcommand output will be parsed
1419 into an XML document and will then be verified by comparing the parsed
1420 output to the contents in the EXPECTED_PATHS, EXPECTED_ITEMS,
1421 EXPECTED_PROPS and EXPECTED_KINDS. Returns on success, raises
1422 on failure."""
1424 exit_code, output, errput = run_and_verify_svn(None, None, error_re_string,
1425 'diff', '--summarize',
1426 '--xml', *args)
1429 # Return if errors are present since they were expected
1430 if len(errput) > 0:
1431 return
1433 doc = parseString(''.join(output))
1434 paths = doc.getElementsByTagName("path")
1435 items = expected_items
1436 kinds = expected_kinds
1438 for path in paths:
1439 modified_path = path.childNodes[0].data
1441 if (expected_prefix is not None
1442 and modified_path.find(expected_prefix) == 0):
1443 modified_path = modified_path.replace(expected_prefix, '')[1:].strip()
1445 # Workaround single-object diff
1446 if len(modified_path) == 0:
1447 modified_path = path.childNodes[0].data.split(os.sep)[-1]
1449 # From here on, we use '/' as path separator.
1450 if os.sep != "/":
1451 modified_path = modified_path.replace(os.sep, "/")
1453 if modified_path not in expected_paths:
1454 print("ERROR: %s not expected in the changed paths." % modified_path)
1455 raise Failure
1457 index = expected_paths.index(modified_path)
1458 expected_item = items[index]
1459 expected_kind = kinds[index]
1460 expected_prop = expected_props[index]
1461 actual_item = path.getAttribute('item')
1462 actual_kind = path.getAttribute('kind')
1463 actual_prop = path.getAttribute('props')
1465 if expected_item != actual_item:
1466 print("ERROR: expected: %s actual: %s" % (expected_item, actual_item))
1467 raise Failure
1469 if expected_kind != actual_kind:
1470 print("ERROR: expected: %s actual: %s" % (expected_kind, actual_kind))
1471 raise Failure
1473 if expected_prop != actual_prop:
1474 print("ERROR: expected: %s actual: %s" % (expected_prop, actual_prop))
1475 raise Failure
1477 def run_and_verify_diff_summarize(output_tree, *args):
1478 """Run 'diff --summarize' with the arguments *ARGS.
1480 The subcommand output will be verified against OUTPUT_TREE. Returns
1481 on success, raises on failure.
1484 if isinstance(output_tree, wc.State):
1485 output_tree = output_tree.old_tree()
1487 exit_code, output, errput = main.run_svn(None, 'diff', '--summarize',
1488 *args)
1490 actual = tree.build_tree_from_diff_summarize(output)
1492 # Verify actual output against expected output.
1493 try:
1494 tree.compare_trees("output", actual, output_tree)
1495 except tree.SVNTreeError:
1496 verify.display_trees(None, 'DIFF OUTPUT TREE', output_tree, actual)
1497 print("ACTUAL DIFF OUTPUT TREE:")
1498 tree.dump_tree_script(actual)
1499 raise
1501 def run_and_validate_lock(path, username):
1502 """`svn lock' the given path and validate the contents of the lock.
1503 Use the given username. This is important because locks are
1504 user specific."""
1506 comment = "Locking path:%s." % path
1508 # lock the path
1509 run_and_verify_svn(None, ".*locked by user", [], 'lock',
1510 '--username', username,
1511 '-m', comment, path)
1513 # Run info and check that we get the lock fields.
1514 exit_code, output, err = run_and_verify_svn(None, None, [],
1515 'info','-R',
1516 path)
1518 ### TODO: Leverage RegexOuput([...], match_all=True) here.
1519 # prepare the regexs to compare against
1520 token_re = re.compile(".*?Lock Token: opaquelocktoken:.*?", re.DOTALL)
1521 author_re = re.compile(".*?Lock Owner: %s\n.*?" % username, re.DOTALL)
1522 created_re = re.compile(".*?Lock Created:.*?", re.DOTALL)
1523 comment_re = re.compile(".*?%s\n.*?" % re.escape(comment), re.DOTALL)
1524 # join all output lines into one
1525 output = "".join(output)
1526 # Fail even if one regex does not match
1527 if ( not (token_re.match(output) and
1528 author_re.match(output) and
1529 created_re.match(output) and
1530 comment_re.match(output))):
1531 raise Failure
1533 def _run_and_verify_resolve(cmd, expected_paths, *args):
1534 """Run "svn CMD" (where CMD is 'resolve' or 'resolved') with arguments
1535 ARGS, and verify that it resolves the paths in EXPECTED_PATHS and no others.
1536 If no ARGS are specified, use the elements of EXPECTED_PATHS as the
1537 arguments."""
1538 # TODO: verify that the status of PATHS changes accordingly.
1539 if len(args) == 0:
1540 args = expected_paths
1541 expected_output = verify.UnorderedOutput([
1542 "Resolved conflicted state of '" + path + "'\n" for path in
1543 expected_paths])
1544 run_and_verify_svn(None, expected_output, [],
1545 cmd, *args)
1547 def run_and_verify_resolve(expected_paths, *args):
1548 """Run "svn resolve" with arguments ARGS, and verify that it resolves the
1549 paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the
1550 elements of EXPECTED_PATHS as the arguments."""
1551 _run_and_verify_resolve('resolve', expected_paths, *args)
1553 def run_and_verify_resolved(expected_paths, *args):
1554 """Run "svn resolved" with arguments ARGS, and verify that it resolves the
1555 paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the
1556 elements of EXPECTED_PATHS as the arguments."""
1557 _run_and_verify_resolve('resolved', expected_paths, *args)
1560 ######################################################################
1561 # Other general utilities
1564 # This allows a test to *quickly* bootstrap itself.
1565 def make_repo_and_wc(sbox, create_wc = True, read_only = False):
1566 """Create a fresh 'Greek Tree' repository and check out a WC from it.
1568 If READ_ONLY is False, a dedicated repository will be created, at the path
1569 SBOX.repo_dir. If READ_ONLY is True, the pristine repository will be used.
1570 In either case, SBOX.repo_url is assumed to point to the repository that
1571 will be used.
1573 If create_wc is True, a dedicated working copy will be checked out from
1574 the repository, at the path SBOX.wc_dir.
1576 Returns on success, raises on failure."""
1578 # Create (or copy afresh) a new repos with a greek tree in it.
1579 if not read_only:
1580 guarantee_greek_repository(sbox.repo_dir)
1582 if create_wc:
1583 # Generate the expected output tree.
1584 expected_output = main.greek_state.copy()
1585 expected_output.wc_dir = sbox.wc_dir
1586 expected_output.tweak(status='A ', contents=None)
1588 # Generate an expected wc tree.
1589 expected_wc = main.greek_state
1591 # Do a checkout, and verify the resulting output and disk contents.
1592 run_and_verify_checkout(sbox.repo_url,
1593 sbox.wc_dir,
1594 expected_output,
1595 expected_wc)
1596 else:
1597 # just make sure the parent folder of our working copy is created
1598 try:
1599 os.mkdir(main.general_wc_dir)
1600 except OSError, err:
1601 if err.errno != errno.EEXIST:
1602 raise
1604 # Duplicate a working copy or other dir.
1605 def duplicate_dir(wc_name, wc_copy_name):
1606 """Copy the working copy WC_NAME to WC_COPY_NAME. Overwrite any
1607 existing tree at that location."""
1609 main.safe_rmtree(wc_copy_name)
1610 shutil.copytree(wc_name, wc_copy_name)
1614 def get_virginal_state(wc_dir, rev):
1615 "Return a virginal greek tree state for a WC and repos at revision REV."
1617 rev = str(rev) ### maybe switch rev to an integer?
1619 # copy the greek tree, shift it to the new wc_dir, insert a root elem,
1620 # then tweak all values
1621 state = main.greek_state.copy()
1622 state.wc_dir = wc_dir
1623 state.desc[''] = wc.StateItem()
1624 state.tweak(contents=None, status=' ', wc_rev=rev)
1626 return state
1628 # Cheap administrative directory locking
1629 def lock_admin_dir(wc_dir):
1630 "Lock a SVN administrative directory"
1631 dot_svn = svntest.main.get_admin_name()
1632 root_path = wc_dir
1633 relpath = ''
1635 while True:
1636 db_path = os.path.join(root_path, dot_svn, 'wc.db')
1637 try:
1638 db = svntest.sqlite3.connect(db_path)
1639 break
1640 except: pass
1641 head, tail = os.path.split(root_path)
1642 if head == root_path:
1643 raise svntest.Failure("No DB for " + wc_dir)
1644 root_path = head
1645 relpath = os.path.join(tail, relpath).replace(os.path.sep, '/').rstrip('/')
1647 db.execute('insert into wc_lock (wc_id, local_dir_relpath, locked_levels) '
1648 + 'values (?, ?, ?)',
1649 (1, relpath, 0))
1650 db.commit()
1651 db.close()
1653 def get_wc_uuid(wc_dir):
1654 "Return the UUID of the working copy at WC_DIR."
1655 return run_and_parse_info(wc_dir)[0]['Repository UUID']
1657 def get_wc_base_rev(wc_dir):
1658 "Return the BASE revision of the working copy at WC_DIR."
1659 return run_and_parse_info(wc_dir)[0]['Revision']
1661 def hook_failure_message(hook_name):
1662 """Return the error message that the client prints for failure of the
1663 specified hook HOOK_NAME. The wording changed with Subversion 1.5."""
1664 if svntest.main.options.server_minor_version < 5:
1665 return "'%s' hook failed with error output:\n" % hook_name
1666 else:
1667 if hook_name in ["start-commit", "pre-commit"]:
1668 action = "Commit"
1669 elif hook_name == "pre-revprop-change":
1670 action = "Revprop change"
1671 elif hook_name == "pre-lock":
1672 action = "Lock"
1673 elif hook_name == "pre-unlock":
1674 action = "Unlock"
1675 else:
1676 action = None
1677 if action is None:
1678 message = "%s hook failed (exit code 1)" % (hook_name,)
1679 else:
1680 message = "%s blocked by %s hook (exit code 1)" % (action, hook_name)
1681 return message + " with output:\n"
1683 def create_failing_hook(repo_dir, hook_name, text):
1684 """Create a HOOK_NAME hook in the repository at REPO_DIR that prints
1685 TEXT to stderr and exits with an error."""
1687 hook_path = os.path.join(repo_dir, 'hooks', hook_name)
1688 # Embed the text carefully: it might include characters like "%" and "'".
1689 main.create_python_hook_script(hook_path, 'import sys\n'
1690 'sys.stderr.write(' + repr(text) + ')\n'
1691 'sys.exit(1)\n')
1693 def enable_revprop_changes(repo_dir):
1694 """Enable revprop changes in the repository at REPO_DIR by creating a
1695 pre-revprop-change hook script and (if appropriate) making it executable."""
1697 hook_path = main.get_pre_revprop_change_hook_path(repo_dir)
1698 main.create_python_hook_script(hook_path, 'import sys; sys.exit(0)')
1700 def disable_revprop_changes(repo_dir):
1701 """Disable revprop changes in the repository at REPO_DIR by creating a
1702 pre-revprop-change hook script that prints "pre-revprop-change" followed
1703 by its arguments, and returns an error."""
1705 hook_path = main.get_pre_revprop_change_hook_path(repo_dir)
1706 main.create_python_hook_script(hook_path,
1707 'import sys\n'
1708 'sys.stderr.write("pre-revprop-change %s" % " ".join(sys.argv[1:6]))\n'
1709 'sys.exit(1)\n')
1711 def create_failing_post_commit_hook(repo_dir):
1712 """Create a post-commit hook script in the repository at REPO_DIR that always
1713 reports an error."""
1715 hook_path = main.get_post_commit_hook_path(repo_dir)
1716 main.create_python_hook_script(hook_path, 'import sys\n'
1717 'sys.stderr.write("Post-commit hook failed")\n'
1718 'sys.exit(1)')
1720 # set_prop can be used for properties with NULL characters which are not
1721 # handled correctly when passed to subprocess.Popen() and values like "*"
1722 # which are not handled correctly on Windows.
1723 def set_prop(name, value, path, expected_err=None):
1724 """Set a property with specified value"""
1725 if value and (value[0] == '-' or '\x00' in value or sys.platform == 'win32'):
1726 from tempfile import mkstemp
1727 (fd, value_file_path) = mkstemp()
1728 value_file = open(value_file_path, 'wb')
1729 value_file.write(value)
1730 value_file.flush()
1731 value_file.close()
1732 main.run_svn(expected_err, 'propset', '-F', value_file_path, name, path)
1733 os.close(fd)
1734 os.remove(value_file_path)
1735 else:
1736 main.run_svn(expected_err, 'propset', name, value, path)
1738 def check_prop(name, path, exp_out):
1739 """Verify that property NAME on PATH has a value of EXP_OUT"""
1740 # Not using run_svn because binary_mode must be set
1741 exit_code, out, err = main.run_command(main.svn_binary, None, 1, 'pg',
1742 '--strict', name, path,
1743 '--config-dir',
1744 main.default_config_dir,
1745 '--username', main.wc_author,
1746 '--password', main.wc_passwd)
1747 if out != exp_out:
1748 print("svn pg --strict %s output does not match expected." % name)
1749 print("Expected standard output: %s\n" % exp_out)
1750 print("Actual standard output: %s\n" % out)
1751 raise Failure
1753 def fill_file_with_lines(wc_path, line_nbr, line_descrip=None,
1754 append=True):
1755 """Change the file at WC_PATH (adding some lines), and return its
1756 new contents. LINE_NBR indicates the line number at which the new
1757 contents should assume that it's being appended. LINE_DESCRIP is
1758 something like 'This is line' (the default) or 'Conflicting line'."""
1760 if line_descrip is None:
1761 line_descrip = "This is line"
1763 # Generate the new contents for the file.
1764 contents = ""
1765 for n in range(line_nbr, line_nbr + 3):
1766 contents = contents + line_descrip + " " + repr(n) + " in '" + \
1767 os.path.basename(wc_path) + "'.\n"
1769 # Write the new contents to the file.
1770 if append:
1771 main.file_append(wc_path, contents)
1772 else:
1773 main.file_write(wc_path, contents)
1775 return contents
1777 def inject_conflict_into_wc(sbox, state_path, file_path,
1778 expected_disk, expected_status, merged_rev):
1779 """Create a conflict at FILE_PATH by replacing its contents,
1780 committing the change, backdating it to its previous revision,
1781 changing its contents again, then updating it to merge in the
1782 previous change."""
1784 wc_dir = sbox.wc_dir
1786 # Make a change to the file.
1787 contents = fill_file_with_lines(file_path, 1, "This is line", append=False)
1789 # Commit the changed file, first taking note of the current revision.
1790 prev_rev = expected_status.desc[state_path].wc_rev
1791 expected_output = wc.State(wc_dir, {
1792 state_path : wc.StateItem(verb='Sending'),
1794 if expected_status:
1795 expected_status.tweak(state_path, wc_rev=merged_rev)
1796 run_and_verify_commit(wc_dir, expected_output, expected_status,
1797 None, file_path)
1799 # Backdate the file.
1800 exit_code, output, errput = main.run_svn(None, "up", "-r", str(prev_rev),
1801 file_path)
1802 if expected_status:
1803 expected_status.tweak(state_path, wc_rev=prev_rev)
1805 # Make a conflicting change to the file, and backdate the file.
1806 conflicting_contents = fill_file_with_lines(file_path, 1, "Conflicting line",
1807 append=False)
1809 # Merge the previous change into the file to produce a conflict.
1810 if expected_disk:
1811 expected_disk.tweak(state_path, contents="")
1812 expected_output = wc.State(wc_dir, {
1813 state_path : wc.StateItem(status='C '),
1815 inject_conflict_into_expected_state(state_path,
1816 expected_disk, expected_status,
1817 conflicting_contents, contents,
1818 merged_rev)
1819 exit_code, output, errput = main.run_svn(None, "up", "-r", str(merged_rev),
1820 sbox.repo_url + "/" + state_path,
1821 file_path)
1822 if expected_status:
1823 expected_status.tweak(state_path, wc_rev=merged_rev)
1825 def inject_conflict_into_expected_state(state_path,
1826 expected_disk, expected_status,
1827 wc_text, merged_text, merged_rev):
1828 """Update the EXPECTED_DISK and EXPECTED_STATUS trees for the
1829 conflict at STATE_PATH (ignored if None). WC_TEXT, MERGED_TEXT, and
1830 MERGED_REV are used to determine the contents of the conflict (the
1831 text parameters should be newline-terminated)."""
1832 if expected_disk:
1833 conflict_marker = make_conflict_marker_text(wc_text, merged_text,
1834 merged_rev)
1835 existing_text = expected_disk.desc[state_path].contents or ""
1836 expected_disk.tweak(state_path, contents=existing_text + conflict_marker)
1838 if expected_status:
1839 expected_status.tweak(state_path, status='C ')
1841 def make_conflict_marker_text(wc_text, merged_text, merged_rev):
1842 """Return the conflict marker text described by WC_TEXT (the current
1843 text in the working copy, MERGED_TEXT (the conflicting text merged
1844 in), and MERGED_REV (the revision from whence the conflicting text
1845 came)."""
1846 return "<<<<<<< .working\n" + wc_text + "=======\n" + \
1847 merged_text + ">>>>>>> .merge-right.r" + str(merged_rev) + "\n"
1850 def build_greek_tree_conflicts(sbox):
1851 """Create a working copy that has tree-conflict markings.
1852 After this function has been called, sbox.wc_dir is a working
1853 copy that has specific tree-conflict markings.
1855 In particular, this does two conflicting sets of edits and performs an
1856 update so that tree conflicts appear.
1858 Note that this function calls sbox.build() because it needs a clean sbox.
1859 So, there is no need to call sbox.build() before this.
1861 The conflicts are the result of an 'update' on the following changes:
1863 Incoming Local
1865 A/D/G/pi text-mod del
1866 A/D/G/rho del text-mod
1867 A/D/G/tau del del
1869 This function is useful for testing that tree-conflicts are handled
1870 properly once they have appeared, e.g. that commits are blocked, that the
1871 info output is correct, etc.
1873 See also the tree-conflicts tests using deep_trees in various other
1874 .py files, and tree_conflict_tests.py.
1877 sbox.build()
1878 wc_dir = sbox.wc_dir
1879 j = os.path.join
1880 G = j(wc_dir, 'A', 'D', 'G')
1881 pi = j(G, 'pi')
1882 rho = j(G, 'rho')
1883 tau = j(G, 'tau')
1885 # Make incoming changes and "store them away" with a commit.
1886 main.file_append(pi, "Incoming edit.\n")
1887 main.run_svn(None, 'del', rho)
1888 main.run_svn(None, 'del', tau)
1890 expected_output = wc.State(wc_dir, {
1891 'A/D/G/pi' : Item(verb='Sending'),
1892 'A/D/G/rho' : Item(verb='Deleting'),
1893 'A/D/G/tau' : Item(verb='Deleting'),
1895 expected_status = get_virginal_state(wc_dir, 1)
1896 expected_status.tweak('A/D/G/pi', wc_rev='2')
1897 expected_status.remove('A/D/G/rho', 'A/D/G/tau')
1898 run_and_verify_commit(wc_dir, expected_output, expected_status, None,
1899 '-m', 'Incoming changes.', wc_dir )
1901 # Update back to the pristine state ("time-warp").
1902 expected_output = wc.State(wc_dir, {
1903 'A/D/G/pi' : Item(status='U '),
1904 'A/D/G/rho' : Item(status='A '),
1905 'A/D/G/tau' : Item(status='A '),
1907 expected_disk = main.greek_state
1908 expected_status = get_virginal_state(wc_dir, 1)
1909 run_and_verify_update(wc_dir, expected_output, expected_disk,
1910 expected_status, None, None, None, None, None, False,
1911 '-r', '1', wc_dir)
1913 # Make local changes
1914 main.run_svn(None, 'del', pi)
1915 main.file_append(rho, "Local edit.\n")
1916 main.run_svn(None, 'del', tau)
1918 # Update, receiving the incoming changes on top of the local changes,
1919 # causing tree conflicts. Don't check for any particular result: that is
1920 # the job of other tests.
1921 run_and_verify_svn(None, verify.AnyOutput, [], 'update', wc_dir)
1924 def make_deep_trees(base):
1925 """Helper function for deep trees conflicts. Create a set of trees,
1926 each in its own "container" dir. Any conflicts can be tested separately
1927 in each container.
1929 j = os.path.join
1930 # Create the container dirs.
1931 F = j(base, 'F')
1932 D = j(base, 'D')
1933 DF = j(base, 'DF')
1934 DD = j(base, 'DD')
1935 DDF = j(base, 'DDF')
1936 DDD = j(base, 'DDD')
1937 os.makedirs(F)
1938 os.makedirs(j(D, 'D1'))
1939 os.makedirs(j(DF, 'D1'))
1940 os.makedirs(j(DD, 'D1', 'D2'))
1941 os.makedirs(j(DDF, 'D1', 'D2'))
1942 os.makedirs(j(DDD, 'D1', 'D2', 'D3'))
1944 # Create their files.
1945 alpha = j(F, 'alpha')
1946 beta = j(DF, 'D1', 'beta')
1947 gamma = j(DDF, 'D1', 'D2', 'gamma')
1948 main.file_append(alpha, "This is the file 'alpha'.\n")
1949 main.file_append(beta, "This is the file 'beta'.\n")
1950 main.file_append(gamma, "This is the file 'gamma'.\n")
1953 def add_deep_trees(sbox, base_dir_name):
1954 """Prepare a "deep_trees" within a given directory.
1956 The directory <sbox.wc_dir>/<base_dir_name> is created and a deep_tree
1957 is created within. The items are only added, a commit has to be
1958 called separately, if needed.
1960 <base_dir_name> will thus be a container for the set of containers
1961 mentioned in make_deep_trees().
1963 j = os.path.join
1964 base = j(sbox.wc_dir, base_dir_name)
1965 make_deep_trees(base)
1966 main.run_svn(None, 'add', base)
1969 Item = wc.StateItem
1971 # initial deep trees state
1972 deep_trees_virginal_state = wc.State('', {
1973 'F' : Item(),
1974 'F/alpha' : Item("This is the file 'alpha'.\n"),
1975 'D' : Item(),
1976 'D/D1' : Item(),
1977 'DF' : Item(),
1978 'DF/D1' : Item(),
1979 'DF/D1/beta' : Item("This is the file 'beta'.\n"),
1980 'DD' : Item(),
1981 'DD/D1' : Item(),
1982 'DD/D1/D2' : Item(),
1983 'DDF' : Item(),
1984 'DDF/D1' : Item(),
1985 'DDF/D1/D2' : Item(),
1986 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\n"),
1987 'DDD' : Item(),
1988 'DDD/D1' : Item(),
1989 'DDD/D1/D2' : Item(),
1990 'DDD/D1/D2/D3' : Item(),
1994 # Many actions on deep trees and their resulting states...
1996 def deep_trees_leaf_edit(base):
1997 """Helper function for deep trees test cases. Append text to files,
1998 create new files in empty directories, and change leaf node properties."""
1999 j = os.path.join
2000 F = j(base, 'F', 'alpha')
2001 DF = j(base, 'DF', 'D1', 'beta')
2002 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
2003 main.file_append(F, "More text for file alpha.\n")
2004 main.file_append(DF, "More text for file beta.\n")
2005 main.file_append(DDF, "More text for file gamma.\n")
2006 run_and_verify_svn(None, verify.AnyOutput, [],
2007 'propset', 'prop1', '1', F, DF, DDF)
2009 D = j(base, 'D', 'D1')
2010 DD = j(base, 'DD', 'D1', 'D2')
2011 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
2012 run_and_verify_svn(None, verify.AnyOutput, [],
2013 'propset', 'prop1', '1', D, DD, DDD)
2014 D = j(base, 'D', 'D1', 'delta')
2015 DD = j(base, 'DD', 'D1', 'D2', 'epsilon')
2016 DDD = j(base, 'DDD', 'D1', 'D2', 'D3', 'zeta')
2017 main.file_append(D, "This is the file 'delta'.\n")
2018 main.file_append(DD, "This is the file 'epsilon'.\n")
2019 main.file_append(DDD, "This is the file 'zeta'.\n")
2020 run_and_verify_svn(None, verify.AnyOutput, [],
2021 'add', D, DD, DDD)
2023 # deep trees state after a call to deep_trees_leaf_edit
2024 deep_trees_after_leaf_edit = wc.State('', {
2025 'F' : Item(),
2026 'F/alpha' : Item("This is the file 'alpha'.\nMore text for file alpha.\n"),
2027 'D' : Item(),
2028 'D/D1' : Item(),
2029 'D/D1/delta' : Item("This is the file 'delta'.\n"),
2030 'DF' : Item(),
2031 'DF/D1' : Item(),
2032 'DF/D1/beta' : Item("This is the file 'beta'.\nMore text for file beta.\n"),
2033 'DD' : Item(),
2034 'DD/D1' : Item(),
2035 'DD/D1/D2' : Item(),
2036 'DD/D1/D2/epsilon' : Item("This is the file 'epsilon'.\n"),
2037 'DDF' : Item(),
2038 'DDF/D1' : Item(),
2039 'DDF/D1/D2' : Item(),
2040 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\nMore text for file gamma.\n"),
2041 'DDD' : Item(),
2042 'DDD/D1' : Item(),
2043 'DDD/D1/D2' : Item(),
2044 'DDD/D1/D2/D3' : Item(),
2045 'DDD/D1/D2/D3/zeta' : Item("This is the file 'zeta'.\n"),
2049 def deep_trees_leaf_del(base):
2050 """Helper function for deep trees test cases. Delete files and empty
2051 dirs."""
2052 j = os.path.join
2053 F = j(base, 'F', 'alpha')
2054 D = j(base, 'D', 'D1')
2055 DF = j(base, 'DF', 'D1', 'beta')
2056 DD = j(base, 'DD', 'D1', 'D2')
2057 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
2058 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
2059 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
2061 # deep trees state after a call to deep_trees_leaf_del
2062 deep_trees_after_leaf_del = wc.State('', {
2063 'F' : Item(),
2064 'D' : Item(),
2065 'DF' : Item(),
2066 'DF/D1' : Item(),
2067 'DD' : Item(),
2068 'DD/D1' : Item(),
2069 'DDF' : Item(),
2070 'DDF/D1' : Item(),
2071 'DDF/D1/D2' : Item(),
2072 'DDD' : Item(),
2073 'DDD/D1' : Item(),
2074 'DDD/D1/D2' : Item(),
2077 # deep trees state after a call to deep_trees_leaf_del with no commit
2078 def deep_trees_after_leaf_del_no_ci(wc_dir):
2079 if svntest.main.wc_is_singledb(wc_dir):
2080 return deep_trees_after_leaf_del
2081 else:
2082 return deep_trees_empty_dirs
2085 def deep_trees_tree_del(base):
2086 """Helper function for deep trees test cases. Delete top-level dirs."""
2087 j = os.path.join
2088 F = j(base, 'F', 'alpha')
2089 D = j(base, 'D', 'D1')
2090 DF = j(base, 'DF', 'D1')
2091 DD = j(base, 'DD', 'D1')
2092 DDF = j(base, 'DDF', 'D1')
2093 DDD = j(base, 'DDD', 'D1')
2094 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
2096 def deep_trees_rmtree(base):
2097 """Helper function for deep trees test cases. Delete top-level dirs
2098 with rmtree instead of svn del."""
2099 j = os.path.join
2100 F = j(base, 'F', 'alpha')
2101 D = j(base, 'D', 'D1')
2102 DF = j(base, 'DF', 'D1')
2103 DD = j(base, 'DD', 'D1')
2104 DDF = j(base, 'DDF', 'D1')
2105 DDD = j(base, 'DDD', 'D1')
2106 os.unlink(F)
2107 main.safe_rmtree(D)
2108 main.safe_rmtree(DF)
2109 main.safe_rmtree(DD)
2110 main.safe_rmtree(DDF)
2111 main.safe_rmtree(DDD)
2113 # deep trees state after a call to deep_trees_tree_del
2114 deep_trees_after_tree_del = wc.State('', {
2115 'F' : Item(),
2116 'D' : Item(),
2117 'DF' : Item(),
2118 'DD' : Item(),
2119 'DDF' : Item(),
2120 'DDD' : Item(),
2123 # deep trees state without any files
2124 deep_trees_empty_dirs = wc.State('', {
2125 'F' : Item(),
2126 'D' : Item(),
2127 'D/D1' : Item(),
2128 'DF' : Item(),
2129 'DF/D1' : Item(),
2130 'DD' : Item(),
2131 'DD/D1' : Item(),
2132 'DD/D1/D2' : Item(),
2133 'DDF' : Item(),
2134 'DDF/D1' : Item(),
2135 'DDF/D1/D2' : Item(),
2136 'DDD' : Item(),
2137 'DDD/D1' : Item(),
2138 'DDD/D1/D2' : Item(),
2139 'DDD/D1/D2/D3' : Item(),
2142 # deep trees state after a call to deep_trees_tree_del with no commit
2143 def deep_trees_after_tree_del_no_ci(wc_dir):
2144 if svntest.main.wc_is_singledb(wc_dir):
2145 return deep_trees_after_tree_del
2146 else:
2147 return deep_trees_empty_dirs
2149 def deep_trees_tree_del_repos(base):
2150 """Helper function for deep trees test cases. Delete top-level dirs,
2151 directly in the repository."""
2152 j = '/'.join
2153 F = j([base, 'F', 'alpha'])
2154 D = j([base, 'D', 'D1'])
2155 DF = j([base, 'DF', 'D1'])
2156 DD = j([base, 'DD', 'D1'])
2157 DDF = j([base, 'DDF', 'D1'])
2158 DDD = j([base, 'DDD', 'D1'])
2159 main.run_svn(None, 'mkdir', '-m', '', F, D, DF, DD, DDF, DDD)
2161 # Expected merge/update/switch output.
2163 deep_trees_conflict_output = wc.State('', {
2164 'F/alpha' : Item(status=' ', treeconflict='C'),
2165 'D/D1' : Item(status=' ', treeconflict='C'),
2166 'DF/D1' : Item(status=' ', treeconflict='C'),
2167 'DD/D1' : Item(status=' ', treeconflict='C'),
2168 'DDF/D1' : Item(status=' ', treeconflict='C'),
2169 'DDD/D1' : Item(status=' ', treeconflict='C'),
2172 deep_trees_conflict_output_skipped = wc.State('', {
2173 'D/D1' : Item(verb='Skipped'),
2174 'F/alpha' : Item(verb='Skipped'),
2175 'DD/D1' : Item(verb='Skipped'),
2176 'DF/D1' : Item(verb='Skipped'),
2177 'DDD/D1' : Item(verb='Skipped'),
2178 'DDF/D1' : Item(verb='Skipped'),
2181 # Expected status output after merge/update/switch.
2183 deep_trees_status_local_tree_del = wc.State('', {
2184 '' : Item(status=' ', wc_rev=3),
2185 'D' : Item(status=' ', wc_rev=3),
2186 'D/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2187 'DD' : Item(status=' ', wc_rev=3),
2188 'DD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2189 'DD/D1/D2' : Item(status='D ', wc_rev=2),
2190 'DDD' : Item(status=' ', wc_rev=3),
2191 'DDD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2192 'DDD/D1/D2' : Item(status='D ', wc_rev=2),
2193 'DDD/D1/D2/D3' : Item(status='D ', wc_rev=2),
2194 'DDF' : Item(status=' ', wc_rev=3),
2195 'DDF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2196 'DDF/D1/D2' : Item(status='D ', wc_rev=2),
2197 'DDF/D1/D2/gamma' : Item(status='D ', wc_rev=2),
2198 'DF' : Item(status=' ', wc_rev=3),
2199 'DF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2200 'DF/D1/beta' : Item(status='D ', wc_rev=2),
2201 'F' : Item(status=' ', wc_rev=3),
2202 'F/alpha' : Item(status='D ', wc_rev=2, treeconflict='C'),
2205 deep_trees_status_local_leaf_edit = wc.State('', {
2206 '' : Item(status=' ', wc_rev=3),
2207 'D' : Item(status=' ', wc_rev=3),
2208 'D/D1' : Item(status=' M', wc_rev=2, treeconflict='C'),
2209 'D/D1/delta' : Item(status='A ', wc_rev=0),
2210 'DD' : Item(status=' ', wc_rev=3),
2211 'DD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2212 'DD/D1/D2' : Item(status=' M', wc_rev=2),
2213 'DD/D1/D2/epsilon' : Item(status='A ', wc_rev=0),
2214 'DDD' : Item(status=' ', wc_rev=3),
2215 'DDD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2216 'DDD/D1/D2' : Item(status=' ', wc_rev=2),
2217 'DDD/D1/D2/D3' : Item(status=' M', wc_rev=2),
2218 'DDD/D1/D2/D3/zeta' : Item(status='A ', wc_rev=0),
2219 'DDF' : Item(status=' ', wc_rev=3),
2220 'DDF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2221 'DDF/D1/D2' : Item(status=' ', wc_rev=2),
2222 'DDF/D1/D2/gamma' : Item(status='MM', wc_rev=2),
2223 'DF' : Item(status=' ', wc_rev=3),
2224 'DF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2225 'DF/D1/beta' : Item(status='MM', wc_rev=2),
2226 'F' : Item(status=' ', wc_rev=3),
2227 'F/alpha' : Item(status='MM', wc_rev=2, treeconflict='C'),
2231 class DeepTreesTestCase:
2232 """Describes one tree-conflicts test case.
2233 See deep_trees_run_tests_scheme_for_update(), ..._switch(), ..._merge().
2235 The name field is the subdirectory name in which the test should be run.
2237 The local_action and incoming_action are the functions to run
2238 to construct the local changes and incoming changes, respectively.
2239 See deep_trees_leaf_edit, deep_trees_tree_del, etc.
2241 The expected_* and error_re_string arguments are described in functions
2242 run_and_verify_[update|switch|merge]
2243 except expected_info, which is a dict that has path keys with values
2244 that are dicts as passed to run_and_verify_info():
2245 expected_info = {
2246 'F/alpha' : {
2247 'Revision' : '3',
2248 'Tree conflict' :
2249 '^local delete, incoming edit upon update'
2250 + ' Source left: .file.*/F/alpha@2'
2251 + ' Source right: .file.*/F/alpha@3$',
2253 'DF/D1' : {
2254 'Tree conflict' :
2255 '^local delete, incoming edit upon update'
2256 + ' Source left: .dir.*/DF/D1@2'
2257 + ' Source right: .dir.*/DF/D1@3$',
2262 Note: expected_skip is only used in merge, i.e. using
2263 deep_trees_run_tests_scheme_for_merge.
2266 def __init__(self, name, local_action, incoming_action,
2267 expected_output = None, expected_disk = None,
2268 expected_status = None, expected_skip = None,
2269 error_re_string = None,
2270 commit_block_string = ".*remains in conflict.*",
2271 expected_info = None):
2272 self.name = name
2273 self.local_action = local_action
2274 self.incoming_action = incoming_action
2275 self.expected_output = expected_output
2276 self.expected_disk = expected_disk
2277 self.expected_status = expected_status
2278 self.expected_skip = expected_skip
2279 self.error_re_string = error_re_string
2280 self.commit_block_string = commit_block_string
2281 self.expected_info = expected_info
2285 def deep_trees_run_tests_scheme_for_update(sbox, greater_scheme):
2287 Runs a given list of tests for conflicts occuring at an update operation.
2289 This function wants to save time and perform a number of different
2290 test cases using just a single repository and performing just one commit
2291 for all test cases instead of one for each test case.
2293 1) Each test case is initialized in a separate subdir. Each subdir
2294 again contains one set of "deep_trees", being separate container
2295 dirs for different depths of trees (F, D, DF, DD, DDF, DDD).
2297 2) A commit is performed across all test cases and depths.
2298 (our initial state, -r2)
2300 3) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2301 its *incoming* action is performed (e.g. "deep_trees_leaf_edit"), in
2302 each of the different depth trees (F, D, DF, ... DDD).
2304 4) A commit is performed across all test cases and depths:
2305 our "incoming" state is "stored away in the repository for now",
2306 -r3.
2308 5) All test case dirs and contained deep_trees are time-warped
2309 (updated) back to -r2, the initial state containing deep_trees.
2311 6) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2312 its *local* action is performed (e.g. "deep_trees_leaf_del"), in
2313 each of the different depth trees (F, D, DF, ... DDD).
2315 7) An update to -r3 is performed across all test cases and depths.
2316 This causes tree-conflicts between the "local" state in the working
2317 copy and the "incoming" state from the repository, -r3.
2319 8) A commit is performed in each separate container, to verify
2320 that each tree-conflict indeed blocks a commit.
2322 The sbox parameter is just the sbox passed to a test function. No need
2323 to call sbox.build(), since it is called (once) within this function.
2325 The "table" greater_scheme models all of the different test cases
2326 that should be run using a single repository.
2328 greater_scheme is a list of DeepTreesTestCase items, which define complete
2329 test setups, so that they can be performed as described above.
2332 j = os.path.join
2334 if not sbox.is_built():
2335 sbox.build()
2336 wc_dir = sbox.wc_dir
2339 # 1) create directories
2341 for test_case in greater_scheme:
2342 try:
2343 add_deep_trees(sbox, test_case.name)
2344 except:
2345 print("ERROR IN: Tests scheme for update: "
2346 + "while setting up deep trees in '%s'" % test_case.name)
2347 raise
2350 # 2) commit initial state
2352 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2355 # 3) apply incoming changes
2357 for test_case in greater_scheme:
2358 try:
2359 test_case.incoming_action(j(sbox.wc_dir, test_case.name))
2360 except:
2361 print("ERROR IN: Tests scheme for update: "
2362 + "while performing incoming action in '%s'" % test_case.name)
2363 raise
2366 # 4) commit incoming changes
2368 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2371 # 5) time-warp back to -r2
2373 main.run_svn(None, 'update', '-r2', wc_dir)
2376 # 6) apply local changes
2378 for test_case in greater_scheme:
2379 try:
2380 test_case.local_action(j(wc_dir, test_case.name))
2381 except:
2382 print("ERROR IN: Tests scheme for update: "
2383 + "while performing local action in '%s'" % test_case.name)
2384 raise
2387 # 7) update to -r3, conflicting with incoming changes.
2388 # A lot of different things are expected.
2389 # Do separate update operations for each test case.
2391 for test_case in greater_scheme:
2392 try:
2393 base = j(wc_dir, test_case.name)
2395 x_out = test_case.expected_output
2396 if x_out != None:
2397 x_out = x_out.copy()
2398 x_out.wc_dir = base
2400 x_disk = test_case.expected_disk
2402 x_status = test_case.expected_status
2403 if x_status != None:
2404 x_status.copy()
2405 x_status.wc_dir = base
2407 run_and_verify_update(base, x_out, x_disk, None,
2408 error_re_string = test_case.error_re_string)
2409 if x_status:
2410 run_and_verify_unquiet_status(base, x_status)
2412 x_info = test_case.expected_info or {}
2413 for path in x_info:
2414 run_and_verify_info([x_info[path]], j(base, path))
2416 except:
2417 print("ERROR IN: Tests scheme for update: "
2418 + "while verifying in '%s'" % test_case.name)
2419 raise
2422 # 8) Verify that commit fails.
2424 for test_case in greater_scheme:
2425 try:
2426 base = j(wc_dir, test_case.name)
2428 x_status = test_case.expected_status
2429 if x_status != None:
2430 x_status.copy()
2431 x_status.wc_dir = base
2433 run_and_verify_commit(base, None, x_status,
2434 test_case.commit_block_string,
2435 base)
2436 except:
2437 print("ERROR IN: Tests scheme for update: "
2438 + "while checking commit-blocking in '%s'" % test_case.name)
2439 raise
2443 def deep_trees_skipping_on_update(sbox, test_case, skip_paths,
2444 chdir_skip_paths):
2446 Create tree conflicts, then update again, expecting the existing tree
2447 conflicts to be skipped.
2448 SKIP_PATHS is a list of paths, relative to the "base dir", for which
2449 "update" on the "base dir" should report as skipped.
2450 CHDIR_SKIP_PATHS is a list of (target-path, skipped-path) pairs for which
2451 an update of "target-path" (relative to the "base dir") should result in
2452 "skipped-path" (relative to "target-path") being reported as skipped.
2455 """FURTHER_ACTION is a function that will make a further modification to
2456 each target, this being the modification that we expect to be skipped. The
2457 function takes the "base dir" (the WC path to the test case directory) as
2458 its only argument."""
2459 further_action = deep_trees_tree_del_repos
2461 j = os.path.join
2462 wc_dir = sbox.wc_dir
2463 base = j(wc_dir, test_case.name)
2465 # Initialize: generate conflicts. (We do not check anything here.)
2466 setup_case = DeepTreesTestCase(test_case.name,
2467 test_case.local_action,
2468 test_case.incoming_action,
2469 None,
2470 None,
2471 None)
2472 deep_trees_run_tests_scheme_for_update(sbox, [setup_case])
2474 # Make a further change to each target in the repository so there is a new
2475 # revision to update to. (This is r4.)
2476 further_action(sbox.repo_url + '/' + test_case.name)
2478 # Update whole working copy, expecting the nodes still in conflict to be
2479 # skipped.
2481 x_out = test_case.expected_output
2482 if x_out != None:
2483 x_out = x_out.copy()
2484 x_out.wc_dir = base
2486 x_disk = test_case.expected_disk
2488 x_status = test_case.expected_status
2489 if x_status != None:
2490 x_status = x_status.copy()
2491 x_status.wc_dir = base
2492 # Account for nodes that were updated by further_action
2493 x_status.tweak('', 'D', 'F', 'DD', 'DF', 'DDD', 'DDF', wc_rev=4)
2495 run_and_verify_update(base, x_out, x_disk, None,
2496 error_re_string = test_case.error_re_string)
2498 run_and_verify_unquiet_status(base, x_status)
2500 # Try to update each in-conflict subtree. Expect a 'Skipped' output for
2501 # each, and the WC status to be unchanged.
2502 for path in skip_paths:
2503 run_and_verify_update(j(base, path),
2504 wc.State(base, {path : Item(verb='Skipped')}),
2505 None, None)
2507 run_and_verify_unquiet_status(base, x_status)
2509 # Try to update each in-conflict subtree. Expect a 'Skipped' output for
2510 # each, and the WC status to be unchanged.
2511 # This time, cd to the subdir before updating it.
2512 was_cwd = os.getcwd()
2513 for path, skipped in chdir_skip_paths:
2514 #print("CHDIR TO: %s" % j(base, path))
2515 os.chdir(j(base, path))
2516 run_and_verify_update('',
2517 wc.State('', {skipped : Item(verb='Skipped')}),
2518 None, None)
2519 os.chdir(was_cwd)
2521 run_and_verify_unquiet_status(base, x_status)
2523 # Verify that commit still fails.
2524 for path, skipped in chdir_skip_paths:
2526 run_and_verify_commit(j(base, path), None, None,
2527 test_case.commit_block_string,
2528 base)
2530 run_and_verify_unquiet_status(base, x_status)
2533 def deep_trees_run_tests_scheme_for_switch(sbox, greater_scheme):
2535 Runs a given list of tests for conflicts occuring at a switch operation.
2537 This function wants to save time and perform a number of different
2538 test cases using just a single repository and performing just one commit
2539 for all test cases instead of one for each test case.
2541 1) Each test case is initialized in a separate subdir. Each subdir
2542 again contains two subdirs: one "local" and one "incoming" for
2543 the switch operation. These contain a set of deep_trees each.
2545 2) A commit is performed across all test cases and depths.
2546 (our initial state, -r2)
2548 3) In each test case subdir's incoming subdir, the
2549 incoming actions are performed.
2551 4) A commit is performed across all test cases and depths. (-r3)
2553 5) In each test case subdir's local subdir, the local actions are
2554 performed. They remain uncommitted in the working copy.
2556 6) In each test case subdir's local dir, a switch is performed to its
2557 corresponding incoming dir.
2558 This causes conflicts between the "local" state in the working
2559 copy and the "incoming" state from the incoming subdir (still -r3).
2561 7) A commit is performed in each separate container, to verify
2562 that each tree-conflict indeed blocks a commit.
2564 The sbox parameter is just the sbox passed to a test function. No need
2565 to call sbox.build(), since it is called (once) within this function.
2567 The "table" greater_scheme models all of the different test cases
2568 that should be run using a single repository.
2570 greater_scheme is a list of DeepTreesTestCase items, which define complete
2571 test setups, so that they can be performed as described above.
2574 j = os.path.join
2576 if not sbox.is_built():
2577 sbox.build()
2578 wc_dir = sbox.wc_dir
2581 # 1) Create directories.
2583 for test_case in greater_scheme:
2584 try:
2585 base = j(sbox.wc_dir, test_case.name)
2586 os.makedirs(base)
2587 make_deep_trees(j(base, "local"))
2588 make_deep_trees(j(base, "incoming"))
2589 main.run_svn(None, 'add', base)
2590 except:
2591 print("ERROR IN: Tests scheme for switch: "
2592 + "while setting up deep trees in '%s'" % test_case.name)
2593 raise
2596 # 2) Commit initial state (-r2).
2598 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2601 # 3) Apply incoming changes
2603 for test_case in greater_scheme:
2604 try:
2605 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2606 except:
2607 print("ERROR IN: Tests scheme for switch: "
2608 + "while performing incoming action in '%s'" % test_case.name)
2609 raise
2612 # 4) Commit all changes (-r3).
2614 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2617 # 5) Apply local changes in their according subdirs.
2619 for test_case in greater_scheme:
2620 try:
2621 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
2622 except:
2623 print("ERROR IN: Tests scheme for switch: "
2624 + "while performing local action in '%s'" % test_case.name)
2625 raise
2628 # 6) switch the local dir to the incoming url, conflicting with incoming
2629 # changes. A lot of different things are expected.
2630 # Do separate switch operations for each test case.
2632 for test_case in greater_scheme:
2633 try:
2634 local = j(wc_dir, test_case.name, "local")
2635 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
2637 x_out = test_case.expected_output
2638 if x_out != None:
2639 x_out = x_out.copy()
2640 x_out.wc_dir = local
2642 x_disk = test_case.expected_disk
2644 x_status = test_case.expected_status
2645 if x_status != None:
2646 x_status.copy()
2647 x_status.wc_dir = local
2649 run_and_verify_switch(local, local, incoming, x_out, x_disk, None,
2650 error_re_string = test_case.error_re_string)
2651 run_and_verify_unquiet_status(local, x_status)
2653 x_info = test_case.expected_info or {}
2654 for path in x_info:
2655 run_and_verify_info([x_info[path]], j(local, path))
2656 except:
2657 print("ERROR IN: Tests scheme for switch: "
2658 + "while verifying in '%s'" % test_case.name)
2659 raise
2662 # 7) Verify that commit fails.
2664 for test_case in greater_scheme:
2665 try:
2666 local = j(wc_dir, test_case.name, 'local')
2668 x_status = test_case.expected_status
2669 if x_status != None:
2670 x_status.copy()
2671 x_status.wc_dir = local
2673 run_and_verify_commit(local, None, x_status,
2674 test_case.commit_block_string,
2675 local)
2676 except:
2677 print("ERROR IN: Tests scheme for switch: "
2678 + "while checking commit-blocking in '%s'" % test_case.name)
2679 raise
2682 def deep_trees_run_tests_scheme_for_merge(sbox, greater_scheme,
2683 do_commit_local_changes,
2684 do_commit_conflicts=True,
2685 ignore_ancestry=False):
2687 Runs a given list of tests for conflicts occuring at a merge operation.
2689 This function wants to save time and perform a number of different
2690 test cases using just a single repository and performing just one commit
2691 for all test cases instead of one for each test case.
2693 1) Each test case is initialized in a separate subdir. Each subdir
2694 initially contains another subdir, called "incoming", which
2695 contains a set of deep_trees.
2697 2) A commit is performed across all test cases and depths.
2698 (a pre-initial state)
2700 3) In each test case subdir, the "incoming" subdir is copied to "local",
2701 via the `svn copy' command. Each test case's subdir now has two sub-
2702 dirs: "local" and "incoming", initial states for the merge operation.
2704 4) An update is performed across all test cases and depths, so that the
2705 copies made in 3) are pulled into the wc.
2707 5) In each test case's "incoming" subdir, the incoming action is
2708 performed.
2710 6) A commit is performed across all test cases and depths, to commit
2711 the incoming changes.
2712 If do_commit_local_changes is True, this becomes step 7 (swap steps).
2714 7) In each test case's "local" subdir, the local_action is performed.
2715 If do_commit_local_changes is True, this becomes step 6 (swap steps).
2716 Then, in effect, the local changes are committed as well.
2718 8) In each test case subdir, the "incoming" subdir is merged into the
2719 "local" subdir. If ignore_ancestry is True, then the merge is done
2720 with the --ignore-ancestry option, so mergeinfo is neither considered
2721 nor recorded. This causes conflicts between the "local" state in the
2722 working copy and the "incoming" state from the incoming subdir.
2724 9) If do_commit_conflicts is True, then a commit is performed in each
2725 separate container, to verify that each tree-conflict indeed blocks
2726 a commit.
2728 The sbox parameter is just the sbox passed to a test function. No need
2729 to call sbox.build(), since it is called (once) within this function.
2731 The "table" greater_scheme models all of the different test cases
2732 that should be run using a single repository.
2734 greater_scheme is a list of DeepTreesTestCase items, which define complete
2735 test setups, so that they can be performed as described above.
2738 j = os.path.join
2740 if not sbox.is_built():
2741 sbox.build()
2742 wc_dir = sbox.wc_dir
2744 # 1) Create directories.
2745 for test_case in greater_scheme:
2746 try:
2747 base = j(sbox.wc_dir, test_case.name)
2748 os.makedirs(base)
2749 make_deep_trees(j(base, "incoming"))
2750 main.run_svn(None, 'add', base)
2751 except:
2752 print("ERROR IN: Tests scheme for merge: "
2753 + "while setting up deep trees in '%s'" % test_case.name)
2754 raise
2757 # 2) Commit pre-initial state (-r2).
2759 main.run_svn(None, 'commit', '-m', 'pre-initial state', wc_dir)
2762 # 3) Copy "incoming" to "local".
2764 for test_case in greater_scheme:
2765 try:
2766 base_url = sbox.repo_url + "/" + test_case.name
2767 incoming_url = base_url + "/incoming"
2768 local_url = base_url + "/local"
2769 main.run_svn(None, 'cp', incoming_url, local_url, '-m',
2770 'copy incoming to local')
2771 except:
2772 print("ERROR IN: Tests scheme for merge: "
2773 + "while copying deep trees in '%s'" % test_case.name)
2774 raise
2776 # 4) Update to load all of the "/local" subdirs into the working copies.
2778 try:
2779 main.run_svn(None, 'up', sbox.wc_dir)
2780 except:
2781 print("ERROR IN: Tests scheme for merge: "
2782 + "while updating local subdirs")
2783 raise
2786 # 5) Perform incoming actions
2788 for test_case in greater_scheme:
2789 try:
2790 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2791 except:
2792 print("ERROR IN: Tests scheme for merge: "
2793 + "while performing incoming action in '%s'" % test_case.name)
2794 raise
2797 # 6) or 7) Commit all incoming actions
2799 if not do_commit_local_changes:
2800 try:
2801 main.run_svn(None, 'ci', '-m', 'Committing incoming actions',
2802 sbox.wc_dir)
2803 except:
2804 print("ERROR IN: Tests scheme for merge: "
2805 + "while committing incoming actions")
2806 raise
2809 # 7) or 6) Perform all local actions.
2811 for test_case in greater_scheme:
2812 try:
2813 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
2814 except:
2815 print("ERROR IN: Tests scheme for merge: "
2816 + "while performing local action in '%s'" % test_case.name)
2817 raise
2820 # 6) or 7) Commit all incoming actions
2822 if do_commit_local_changes:
2823 try:
2824 main.run_svn(None, 'ci', '-m', 'Committing incoming and local actions',
2825 sbox.wc_dir)
2826 except:
2827 print("ERROR IN: Tests scheme for merge: "
2828 + "while committing incoming and local actions")
2829 raise
2832 # 8) Merge all "incoming" subdirs to their respective "local" subdirs.
2833 # This creates conflicts between the local changes in the "local" wc
2834 # subdirs and the incoming states committed in the "incoming" subdirs.
2836 for test_case in greater_scheme:
2837 try:
2838 local = j(sbox.wc_dir, test_case.name, "local")
2839 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
2841 x_out = test_case.expected_output
2842 if x_out != None:
2843 x_out = x_out.copy()
2844 x_out.wc_dir = local
2846 x_disk = test_case.expected_disk
2848 x_status = test_case.expected_status
2849 if x_status != None:
2850 x_status.copy()
2851 x_status.wc_dir = local
2853 x_skip = test_case.expected_skip
2854 if x_skip != None:
2855 x_skip.copy()
2856 x_skip.wc_dir = local
2858 varargs = (local,)
2859 if ignore_ancestry:
2860 varargs = varargs + ('--ignore-ancestry',)
2862 run_and_verify_merge(local, None, None, incoming, None,
2863 x_out, None, None, x_disk, None, x_skip,
2864 test_case.error_re_string,
2865 None, None, None, None,
2866 False, False, *varargs)
2867 run_and_verify_unquiet_status(local, x_status)
2868 except:
2869 print("ERROR IN: Tests scheme for merge: "
2870 + "while verifying in '%s'" % test_case.name)
2871 raise
2874 # 9) Verify that commit fails.
2876 if do_commit_conflicts:
2877 for test_case in greater_scheme:
2878 try:
2879 local = j(wc_dir, test_case.name, 'local')
2881 x_status = test_case.expected_status
2882 if x_status != None:
2883 x_status.copy()
2884 x_status.wc_dir = local
2886 run_and_verify_commit(local, None, x_status,
2887 test_case.commit_block_string,
2888 local)
2889 except:
2890 print("ERROR IN: Tests scheme for merge: "
2891 + "while checking commit-blocking in '%s'" % test_case.name)
2892 raise