Get rid of member RepositoryDelegate.target.
[cvs2svn.git] / svntest / actions.py
blob866aac5d6c632284b22dadd0e8fd27af717f9e7b
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 setup_pristine_repository():
42 """Create the pristine repository and 'svn import' the greek tree"""
44 # these directories don't exist out of the box, so we may have to create them
45 if not os.path.exists(main.general_wc_dir):
46 os.makedirs(main.general_wc_dir)
48 if not os.path.exists(main.general_repo_dir):
49 os.makedirs(main.general_repo_dir) # this also creates all the intermediate dirs
51 # If there's no pristine repos, create one.
52 if not os.path.exists(main.pristine_dir):
53 main.create_repos(main.pristine_dir)
55 # if this is dav, gives us access rights to import the greek tree.
56 if main.is_ra_type_dav():
57 authz_file = os.path.join(main.work_dir, "authz")
58 main.file_write(authz_file, "[/]\n* = rw\n")
60 # dump the greek tree to disk.
61 main.greek_state.write_to_disk(main.greek_dump_dir)
63 # import the greek tree, using l:foo/p:bar
64 ### todo: svn should not be prompting for auth info when using
65 ### repositories with no auth/auth requirements
66 exit_code, output, errput = main.run_svn(None, 'import', '-m',
67 'Log message for revision 1.',
68 main.greek_dump_dir,
69 main.pristine_url)
71 # check for any errors from the import
72 if len(errput):
73 display_lines("Errors during initial 'svn import':",
74 'STDERR', None, errput)
75 sys.exit(1)
77 # verify the printed output of 'svn import'.
78 lastline = output.pop().strip()
79 match = re.search("(Committed|Imported) revision [0-9]+.", lastline)
80 if not match:
81 print("ERROR: import did not succeed, while creating greek repos.")
82 print("The final line from 'svn import' was:")
83 print(lastline)
84 sys.exit(1)
85 output_tree = wc.State.from_commit(output)
87 expected_output_tree = main.greek_state.copy(main.greek_dump_dir)
88 expected_output_tree.tweak(verb='Adding',
89 contents=None)
91 try:
92 expected_output_tree.compare_and_display('output', output_tree)
93 except tree.SVNTreeUnequal:
94 verify.display_trees("ERROR: output of import command is unexpected.",
95 "OUTPUT TREE",
96 expected_output_tree.old_tree(),
97 output_tree.old_tree())
98 sys.exit(1)
100 # Finally, disallow any changes to the "pristine" repos.
101 error_msg = "Don't modify the pristine repository"
102 create_failing_hook(main.pristine_dir, 'start-commit', error_msg)
103 create_failing_hook(main.pristine_dir, 'pre-lock', error_msg)
104 create_failing_hook(main.pristine_dir, 'pre-revprop-change', error_msg)
107 ######################################################################
109 def guarantee_empty_repository(path):
110 """Guarantee that a local svn repository exists at PATH, containing
111 nothing."""
113 if path == main.pristine_dir:
114 print("ERROR: attempt to overwrite the pristine repos! Aborting.")
115 sys.exit(1)
117 # create an empty repository at PATH.
118 main.safe_rmtree(path)
119 main.create_repos(path)
121 # Used by every test, so that they can run independently of one
122 # another. Every time this routine is called, it recursively copies
123 # the `pristine repos' to a new location.
124 # Note: make sure setup_pristine_repository was called once before
125 # using this function.
126 def guarantee_greek_repository(path):
127 """Guarantee that a local svn repository exists at PATH, containing
128 nothing but the greek-tree at revision 1."""
130 if path == main.pristine_dir:
131 print("ERROR: attempt to overwrite the pristine repos! Aborting.")
132 sys.exit(1)
134 # copy the pristine repository to PATH.
135 main.safe_rmtree(path)
136 if main.copy_repos(main.pristine_dir, path, 1):
137 print("ERROR: copying repository failed.")
138 sys.exit(1)
140 # make the repos world-writeable, for mod_dav_svn's sake.
141 main.chmod_tree(path, 0666, 0666)
144 def run_and_verify_svnlook(message, expected_stdout,
145 expected_stderr, *varargs):
146 """Like run_and_verify_svnlook2, but the expected exit code is
147 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
149 expected_exit = 0
150 if expected_stderr is not None and expected_stderr != []:
151 expected_exit = 1
152 return run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
153 expected_exit, *varargs)
155 def run_and_verify_svnlook2(message, expected_stdout, expected_stderr,
156 expected_exit, *varargs):
157 """Run svnlook command and check its output and exit code."""
159 exit_code, out, err = main.run_svnlook(*varargs)
160 verify.verify_outputs("Unexpected output", out, err,
161 expected_stdout, expected_stderr)
162 verify.verify_exit_code(message, exit_code, expected_exit)
163 return exit_code, out, err
166 def run_and_verify_svnadmin(message, expected_stdout,
167 expected_stderr, *varargs):
168 """Like run_and_verify_svnadmin2, but the expected exit code is
169 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
171 expected_exit = 0
172 if expected_stderr is not None and expected_stderr != []:
173 expected_exit = 1
174 return run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
175 expected_exit, *varargs)
177 def run_and_verify_svnadmin2(message, expected_stdout, expected_stderr,
178 expected_exit, *varargs):
179 """Run svnadmin command and check its output and exit code."""
181 exit_code, out, err = main.run_svnadmin(*varargs)
182 verify.verify_outputs("Unexpected output", out, err,
183 expected_stdout, expected_stderr)
184 verify.verify_exit_code(message, exit_code, expected_exit)
185 return exit_code, out, err
188 def run_and_verify_svnversion(message, wc_dir, repo_url,
189 expected_stdout, expected_stderr):
190 """like run_and_verify_svnversion2, but the expected exit code is
191 assumed to be 0 if no output is expected on stderr, and 1 otherwise."""
193 expected_exit = 0
194 if expected_stderr is not None and expected_stderr != []:
195 expected_exit = 1
196 return run_and_verify_svnversion2(message, wc_dir, repo_url,
197 expected_stdout, expected_stderr,
198 expected_exit)
200 def run_and_verify_svnversion2(message, wc_dir, repo_url,
201 expected_stdout, expected_stderr,
202 expected_exit):
203 """Run svnversion command and check its output and exit code."""
205 exit_code, out, err = main.run_svnversion(wc_dir, repo_url)
206 verify.verify_outputs("Unexpected output", out, err,
207 expected_stdout, expected_stderr)
208 verify.verify_exit_code(message, exit_code, expected_exit)
209 return exit_code, out, err
211 def run_and_verify_svn(message, expected_stdout, expected_stderr, *varargs):
212 """like run_and_verify_svn2, but the expected exit code is assumed to
213 be 0 if no output is expected on stderr, and 1 otherwise."""
215 expected_exit = 0
216 if expected_stderr is not None:
217 if isinstance(expected_stderr, verify.ExpectedOutput):
218 if not expected_stderr.matches([]):
219 expected_exit = 1
220 elif expected_stderr != []:
221 expected_exit = 1
222 return run_and_verify_svn2(message, expected_stdout, expected_stderr,
223 expected_exit, *varargs)
225 def run_and_verify_svn2(message, expected_stdout, expected_stderr,
226 expected_exit, *varargs):
227 """Invoke main.run_svn() with *VARARGS. Return exit code as int; stdout,
228 stderr as lists of lines (including line terminators). For both
229 EXPECTED_STDOUT and EXPECTED_STDERR, create an appropriate instance of
230 verify.ExpectedOutput (if necessary):
232 - If it is an array of strings, create a vanilla ExpectedOutput.
234 - If it is a single string, create a RegexOutput that must match every
235 line (for stdout) or any line (for stderr) of the expected output.
237 - If it is already an instance of ExpectedOutput
238 (e.g. UnorderedOutput), leave it alone.
240 ...and invoke compare_and_display_lines() on MESSAGE, a label based
241 on the name of the stream being compared (e.g. STDOUT), the
242 ExpectedOutput instance, and the actual output.
244 If EXPECTED_STDOUT is None, do not check stdout.
245 EXPECTED_STDERR may not be None.
247 If output checks pass, the expected and actual codes are compared.
249 If a comparison fails, a Failure will be raised."""
251 if expected_stderr is None:
252 raise verify.SVNIncorrectDatatype("expected_stderr must not be None")
254 want_err = None
255 if isinstance(expected_stderr, verify.ExpectedOutput):
256 if not expected_stderr.matches([]):
257 want_err = True
258 elif expected_stderr != []:
259 want_err = True
261 exit_code, out, err = main.run_svn(want_err, *varargs)
262 verify.verify_outputs(message, out, err, expected_stdout, expected_stderr)
263 verify.verify_exit_code(message, exit_code, expected_exit)
264 return exit_code, out, err
266 def run_and_verify_load(repo_dir, dump_file_content):
267 "Runs 'svnadmin load' and reports any errors."
268 if not isinstance(dump_file_content, list):
269 raise TypeError("dump_file_content argument should have list type")
270 expected_stderr = []
271 exit_code, output, errput = main.run_command_stdin(
272 main.svnadmin_binary, expected_stderr, 0, 1, dump_file_content,
273 'load', '--force-uuid', '--quiet', repo_dir)
275 verify.verify_outputs("Unexpected stderr output", None, errput,
276 None, expected_stderr)
279 def run_and_verify_dump(repo_dir):
280 "Runs 'svnadmin dump' and reports any errors, returning the dump content."
281 exit_code, output, errput = main.run_svnadmin('dump', repo_dir)
282 verify.verify_outputs("Missing expected output(s)", output, errput,
283 verify.AnyOutput, verify.AnyOutput)
284 return output
287 def load_repo(sbox, dumpfile_path = None, dump_str = None):
288 "Loads the dumpfile into sbox"
289 if not dump_str:
290 dump_str = open(dumpfile_path, "rb").read()
292 # Create a virgin repos and working copy
293 main.safe_rmtree(sbox.repo_dir, 1)
294 main.safe_rmtree(sbox.wc_dir, 1)
295 main.create_repos(sbox.repo_dir)
297 # Load the mergetracking dumpfile into the repos, and check it out the repo
298 run_and_verify_load(sbox.repo_dir, dump_str.splitlines(True))
299 run_and_verify_svn(None, None, [], "co", sbox.repo_url, sbox.wc_dir)
301 return dump_str
304 ######################################################################
305 # Subversion Actions
307 # These are all routines that invoke 'svn' in particular ways, and
308 # then verify the results by comparing expected trees with actual
309 # trees.
313 def run_and_verify_checkout(URL, wc_dir_name, output_tree, disk_tree,
314 singleton_handler_a = None,
315 a_baton = None,
316 singleton_handler_b = None,
317 b_baton = None,
318 *args):
319 """Checkout the URL into a new directory WC_DIR_NAME. *ARGS are any
320 extra optional args to the checkout subcommand.
322 The subcommand output will be verified against OUTPUT_TREE,
323 and the working copy itself will be verified against DISK_TREE.
324 For the latter comparison, SINGLETON_HANDLER_A and
325 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
326 function's doc string for more details. Return if successful, raise
327 on failure.
329 WC_DIR_NAME is deleted if present unless the '--force' option is passed
330 in *ARGS."""
332 if isinstance(output_tree, wc.State):
333 output_tree = output_tree.old_tree()
334 if isinstance(disk_tree, wc.State):
335 disk_tree = disk_tree.old_tree()
337 # Remove dir if it's already there, unless this is a forced checkout.
338 # In that case assume we want to test a forced checkout's toleration
339 # of obstructing paths.
340 if '--force' not in args:
341 main.safe_rmtree(wc_dir_name)
343 # Checkout and make a tree of the output, using l:foo/p:bar
344 ### todo: svn should not be prompting for auth info when using
345 ### repositories with no auth/auth requirements
346 exit_code, output, errput = main.run_svn(None, 'co',
347 URL, wc_dir_name, *args)
348 actual = tree.build_tree_from_checkout(output)
350 # Verify actual output against expected output.
351 try:
352 tree.compare_trees("output", actual, output_tree)
353 except tree.SVNTreeUnequal:
354 print("ACTUAL OUTPUT TREE:")
355 tree.dump_tree_script(actual, wc_dir_name + os.sep)
356 raise
358 # Create a tree by scanning the working copy
359 actual = tree.build_tree_from_wc(wc_dir_name)
361 # Verify expected disk against actual disk.
362 try:
363 tree.compare_trees("disk", actual, disk_tree,
364 singleton_handler_a, a_baton,
365 singleton_handler_b, b_baton)
366 except tree.SVNTreeUnequal:
367 print("ACTUAL DISK TREE:")
368 tree.dump_tree_script(actual, wc_dir_name + os.sep)
369 raise
372 def run_and_verify_export(URL, export_dir_name, output_tree, disk_tree,
373 *args):
374 """Export the URL into a new directory WC_DIR_NAME.
376 The subcommand output will be verified against OUTPUT_TREE,
377 and the exported copy itself will be verified against DISK_TREE.
378 Return if successful, raise on failure.
380 assert isinstance(output_tree, wc.State)
381 assert isinstance(disk_tree, wc.State)
383 disk_tree = disk_tree.old_tree()
384 output_tree = output_tree.old_tree()
386 # Export and make a tree of the output, using l:foo/p:bar
387 ### todo: svn should not be prompting for auth info when using
388 ### repositories with no auth/auth requirements
389 exit_code, output, errput = main.run_svn(None, 'export',
390 URL, export_dir_name, *args)
391 actual = tree.build_tree_from_checkout(output)
393 # Verify actual output against expected output.
394 try:
395 tree.compare_trees("output", actual, output_tree)
396 except tree.SVNTreeUnequal:
397 print("ACTUAL OUTPUT TREE:")
398 tree.dump_tree_script(actual, export_dir_name + os.sep)
399 raise
401 # Create a tree by scanning the working copy. Don't ignore
402 # the .svn directories so that we generate an error if they
403 # happen to show up.
404 actual = tree.build_tree_from_wc(export_dir_name, ignore_svn=False)
406 # Verify expected disk against actual disk.
407 try:
408 tree.compare_trees("disk", actual, disk_tree)
409 except tree.SVNTreeUnequal:
410 print("ACTUAL DISK TREE:")
411 tree.dump_tree_script(actual, export_dir_name + os.sep)
412 raise
415 # run_and_verify_log_xml
417 class LogEntry:
418 def __init__(self, revision, changed_paths=None, revprops=None):
419 self.revision = revision
420 if changed_paths == None:
421 self.changed_paths = {}
422 else:
423 self.changed_paths = changed_paths
424 if revprops == None:
425 self.revprops = {}
426 else:
427 self.revprops = revprops
429 def assert_changed_paths(self, changed_paths):
430 """Not implemented, so just raises svntest.Failure.
432 raise Failure('NOT IMPLEMENTED')
434 def assert_revprops(self, revprops):
435 """Assert that the dict revprops is the same as this entry's revprops.
437 Raises svntest.Failure if not.
439 if self.revprops != revprops:
440 raise Failure('\n' + '\n'.join(difflib.ndiff(
441 pprint.pformat(revprops).splitlines(),
442 pprint.pformat(self.revprops).splitlines())))
444 class LogParser:
445 def parse(self, data):
446 """Return a list of LogEntrys parsed from the sequence of strings data.
448 This is the only method of interest to callers.
450 try:
451 for i in data:
452 self.parser.Parse(i)
453 self.parser.Parse('', True)
454 except xml.parsers.expat.ExpatError, e:
455 raise verify.SVNUnexpectedStdout('%s\n%s\n' % (e, ''.join(data),))
456 return self.entries
458 def __init__(self):
459 # for expat
460 self.parser = xml.parsers.expat.ParserCreate()
461 self.parser.StartElementHandler = self.handle_start_element
462 self.parser.EndElementHandler = self.handle_end_element
463 self.parser.CharacterDataHandler = self.handle_character_data
464 # Ignore some things.
465 self.ignore_elements('log', 'paths', 'path', 'revprops')
466 self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start')
467 # internal state
468 self.cdata = []
469 self.property = None
470 # the result
471 self.entries = []
473 def ignore(self, *args, **kwargs):
474 del self.cdata[:]
475 def ignore_tags(self, *args):
476 for tag in args:
477 setattr(self, tag, self.ignore)
478 def ignore_elements(self, *args):
479 for element in args:
480 self.ignore_tags(element + '_start', element + '_end')
482 # expat handlers
483 def handle_start_element(self, name, attrs):
484 getattr(self, name + '_start')(attrs)
485 def handle_end_element(self, name):
486 getattr(self, name + '_end')()
487 def handle_character_data(self, data):
488 self.cdata.append(data)
490 # element handler utilities
491 def use_cdata(self):
492 result = ''.join(self.cdata).strip()
493 del self.cdata[:]
494 return result
495 def svn_prop(self, name):
496 self.entries[-1].revprops['svn:' + name] = self.use_cdata()
498 # element handlers
499 def logentry_start(self, attrs):
500 self.entries.append(LogEntry(int(attrs['revision'])))
501 def author_end(self):
502 self.svn_prop('author')
503 def msg_end(self):
504 self.svn_prop('log')
505 def date_end(self):
506 # svn:date could be anything, so just note its presence.
507 self.cdata[:] = ['']
508 self.svn_prop('date')
509 def property_start(self, attrs):
510 self.property = attrs['name']
511 def property_end(self):
512 self.entries[-1].revprops[self.property] = self.use_cdata()
514 def run_and_verify_log_xml(message=None, expected_paths=None,
515 expected_revprops=None, expected_stdout=None,
516 expected_stderr=None, args=[]):
517 """Call run_and_verify_svn with log --xml and args (optional) as command
518 arguments, and pass along message, expected_stdout, and expected_stderr.
520 If message is None, pass the svn log command as message.
522 expected_paths checking is not yet implemented.
524 expected_revprops is an optional list of dicts, compared to each
525 revision's revprops. The list must be in the same order the log entries
526 come in. Any svn:date revprops in the dicts must be '' in order to
527 match, as the actual dates could be anything.
529 expected_paths and expected_revprops are ignored if expected_stdout or
530 expected_stderr is specified.
532 if message == None:
533 message = ' '.join(args)
535 # We'll parse the output unless the caller specifies expected_stderr or
536 # expected_stdout for run_and_verify_svn.
537 parse = True
538 if expected_stderr == None:
539 expected_stderr = []
540 else:
541 parse = False
542 if expected_stdout != None:
543 parse = False
545 log_args = list(args)
546 if expected_paths != None:
547 log_args.append('-v')
549 (exit_code, stdout, stderr) = run_and_verify_svn(
550 message, expected_stdout, expected_stderr,
551 'log', '--xml', *log_args)
552 if not parse:
553 return
555 entries = LogParser().parse(stdout)
556 for index in range(len(entries)):
557 entry = entries[index]
558 if expected_revprops != None:
559 entry.assert_revprops(expected_revprops[index])
560 if expected_paths != None:
561 entry.assert_changed_paths(expected_paths[index])
564 def verify_update(actual_output,
565 actual_mergeinfo_output,
566 actual_elision_output,
567 wc_dir_name,
568 output_tree,
569 mergeinfo_output_tree,
570 elision_output_tree,
571 disk_tree,
572 status_tree,
573 singleton_handler_a=None,
574 a_baton=None,
575 singleton_handler_b=None,
576 b_baton=None,
577 check_props=False):
578 """Verify update of WC_DIR_NAME.
580 The subcommand output (found in ACTUAL_OUTPUT, ACTUAL_MERGEINFO_OUTPUT,
581 and ACTUAL_ELISION_OUTPUT) will be verified against OUTPUT_TREE,
582 MERGEINFO_OUTPUT_TREE, and ELISION_OUTPUT_TREE respectively (if any of
583 these is provided, they may be None in which case a comparison is not
584 done). The working copy itself will be verified against DISK_TREE (if
585 provided), and the working copy's 'svn status' output will be verified
586 against STATUS_TREE (if provided). (This is a good way to check that
587 revision numbers were bumped.)
589 Return if successful, raise on failure.
591 For the comparison with DISK_TREE, pass SINGLETON_HANDLER_A and
592 SINGLETON_HANDLER_B to tree.compare_trees -- see that function's doc
593 string for more details. If CHECK_PROPS is set, then disk
594 comparison will examine props."""
596 if isinstance(actual_output, wc.State):
597 actual_output = actual_output.old_tree()
598 if isinstance(actual_mergeinfo_output, wc.State):
599 actual_mergeinfo_output = actual_mergeinfo_output.old_tree()
600 if isinstance(actual_elision_output, wc.State):
601 actual_elision_output = actual_elision_output.old_tree()
602 if isinstance(output_tree, wc.State):
603 output_tree = output_tree.old_tree()
604 if isinstance(mergeinfo_output_tree, wc.State):
605 mergeinfo_output_tree = mergeinfo_output_tree.old_tree()
606 if isinstance(elision_output_tree, wc.State):
607 elision_output_tree = elision_output_tree.old_tree()
608 if isinstance(disk_tree, wc.State):
609 disk_tree = disk_tree.old_tree()
610 if isinstance(status_tree, wc.State):
611 status_tree = status_tree.old_tree()
613 # Verify actual output against expected output.
614 if output_tree:
615 try:
616 tree.compare_trees("output", actual_output, output_tree)
617 except tree.SVNTreeUnequal:
618 print("ACTUAL OUTPUT TREE:")
619 tree.dump_tree_script(actual_output, wc_dir_name + os.sep)
620 raise
622 # Verify actual mergeinfo recording output against expected output.
623 if mergeinfo_output_tree:
624 try:
625 tree.compare_trees("mergeinfo_output", actual_mergeinfo_output,
626 mergeinfo_output_tree)
627 except tree.SVNTreeUnequal:
628 print("ACTUAL MERGEINFO OUTPUT TREE:")
629 tree.dump_tree_script(actual_mergeinfo_output,
630 wc_dir_name + os.sep)
631 raise
633 # Verify actual mergeinfo elision output against expected output.
634 if elision_output_tree:
635 try:
636 tree.compare_trees("elision_output", actual_elision_output,
637 elision_output_tree)
638 except tree.SVNTreeUnequal:
639 print("ACTUAL ELISION OUTPUT TREE:")
640 tree.dump_tree_script(actual_elision_output,
641 wc_dir_name + os.sep)
642 raise
644 # Create a tree by scanning the working copy, and verify it
645 if disk_tree:
646 actual_disk = tree.build_tree_from_wc(wc_dir_name, check_props)
647 try:
648 tree.compare_trees("disk", actual_disk, disk_tree,
649 singleton_handler_a, a_baton,
650 singleton_handler_b, b_baton)
651 except tree.SVNTreeUnequal:
652 print("ACTUAL DISK TREE:")
653 tree.dump_tree_script(actual_disk)
654 raise
656 # Verify via 'status' command too, if possible.
657 if status_tree:
658 run_and_verify_status(wc_dir_name, status_tree)
661 def verify_disk(wc_dir_name, disk_tree, check_props=False):
662 """Verify WC_DIR_NAME against DISK_TREE. If CHECK_PROPS is set,
663 the comparison will examin props. Returns if successful, raises on
664 failure."""
665 verify_update(None, None, None, wc_dir_name, None, None, None, disk_tree,
666 None, check_props=check_props)
670 def run_and_verify_update(wc_dir_name,
671 output_tree, disk_tree, status_tree,
672 error_re_string = None,
673 singleton_handler_a = None,
674 a_baton = None,
675 singleton_handler_b = None,
676 b_baton = None,
677 check_props = False,
678 *args):
680 """Update WC_DIR_NAME. *ARGS are any extra optional args to the
681 update subcommand. NOTE: If *ARGS is specified at all, explicit
682 target paths must be passed in *ARGS as well (or a default `.' will
683 be chosen by the 'svn' binary). This allows the caller to update
684 many items in a single working copy dir, but still verify the entire
685 working copy dir.
687 If ERROR_RE_STRING, the update must exit with error, and the error
688 message must match regular expression ERROR_RE_STRING.
690 Else if ERROR_RE_STRING is None, then:
692 If OUTPUT_TREE is not None, the subcommand output will be verified
693 against OUTPUT_TREE. If DISK_TREE is not None, the working copy
694 itself will be verified against DISK_TREE. If STATUS_TREE is not
695 None, the 'svn status' output will be verified against STATUS_TREE.
696 (This is a good way to check that revision numbers were bumped.)
698 For the DISK_TREE verification, SINGLETON_HANDLER_A and
699 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
700 function's doc string for more details.
702 If CHECK_PROPS is set, then disk comparison will examine props.
704 Return if successful, raise on failure."""
706 # Update and make a tree of the output.
707 if len(args):
708 exit_code, output, errput = main.run_svn(error_re_string, 'up', *args)
709 else:
710 exit_code, output, errput = main.run_svn(error_re_string,
711 'up', wc_dir_name,
712 *args)
714 if error_re_string:
715 rm = re.compile(error_re_string)
716 for line in errput:
717 match = rm.search(line)
718 if match:
719 return
720 raise main.SVNUnmatchedError
722 actual = wc.State.from_checkout(output)
723 verify_update(actual, None, None, wc_dir_name,
724 output_tree, None, None, disk_tree, status_tree,
725 singleton_handler_a, a_baton,
726 singleton_handler_b, b_baton,
727 check_props)
730 def run_and_parse_info(*args):
731 """Run 'svn info' and parse its output into a list of dicts,
732 one dict per target."""
734 # the returned array
735 all_infos = []
737 # per-target variables
738 iter_info = {}
739 prev_key = None
740 lock_comment_lines = 0
741 lock_comments = []
743 exit_code, output, errput = main.run_svn(None, 'info', *args)
745 for line in output:
746 line = line[:-1] # trim '\n'
748 if lock_comment_lines > 0:
749 # mop up any lock comment lines
750 lock_comments.append(line)
751 lock_comment_lines = lock_comment_lines - 1
752 if lock_comment_lines == 0:
753 iter_info[prev_key] = lock_comments
754 elif len(line) == 0:
755 # separator line between items
756 all_infos.append(iter_info)
757 iter_info = {}
758 prev_key = None
759 lock_comment_lines = 0
760 lock_comments = []
761 elif line[0].isspace():
762 # continuation line (for tree conflicts)
763 iter_info[prev_key] += line[1:]
764 else:
765 # normal line
766 key, value = line.split(':', 1)
768 if re.search(' \(\d+ lines?\)$', key):
769 # numbered continuation lines
770 match = re.match('^(.*) \((\d+) lines?\)$', key)
771 key = match.group(1)
772 lock_comment_lines = int(match.group(2))
773 elif len(value) > 1:
774 # normal normal line
775 iter_info[key] = value[1:]
776 else:
777 ### originally added for "Tree conflict:\n" lines;
778 ### tree-conflicts output format has changed since then
779 # continuation lines are implicit (prefixed by whitespace)
780 iter_info[key] = ''
781 prev_key = key
783 return all_infos
785 def run_and_verify_info(expected_infos, *args):
786 """Run 'svn info' with the arguments in *ARGS and verify the results
787 against expected_infos. The latter should be a list of dicts (in the
788 same order as the targets).
790 In the dicts, each key is the before-the-colon part of the 'svn info' output,
791 and each value is either None (meaning that the key should *not* appear in
792 the 'svn info' output) or a regex matching the output value. Output lines
793 not matching a key in the dict are ignored.
795 Return if successful, raise on failure."""
797 actual_infos = run_and_parse_info(*args)
799 try:
800 # zip() won't complain, so check this manually
801 if len(actual_infos) != len(expected_infos):
802 raise verify.SVNUnexpectedStdout(
803 "Expected %d infos, found %d infos"
804 % (len(expected_infos), len(actual_infos)))
806 for actual, expected in zip(actual_infos, expected_infos):
807 # compare dicts
808 for key, value in expected.items():
809 assert ':' not in key # caller passed impossible expectations?
810 if value is None and key in actual:
811 raise main.SVNLineUnequal("Found unexpected key '%s' with value '%s'"
812 % (key, actual[key]))
813 if value is not None and key not in actual:
814 raise main.SVNLineUnequal("Expected key '%s' (with value '%s') "
815 "not found" % (key, value))
816 if value is not None and not re.search(value, actual[key]):
817 raise verify.SVNUnexpectedStdout("Values of key '%s' don't match:\n"
818 " Expected: '%s' (regex)\n"
819 " Found: '%s' (string)\n"
820 % (key, value, actual[key]))
822 except:
823 sys.stderr.write("Bad 'svn info' output:\n"
824 " Received: %s\n"
825 " Expected: %s\n"
826 % (actual_infos, expected_infos))
827 raise
829 def run_and_verify_merge(dir, rev1, rev2, url1, url2,
830 output_tree,
831 mergeinfo_output_tree,
832 elision_output_tree,
833 disk_tree, status_tree, skip_tree,
834 error_re_string = None,
835 singleton_handler_a = None,
836 a_baton = None,
837 singleton_handler_b = None,
838 b_baton = None,
839 check_props = False,
840 dry_run = True,
841 *args):
842 """Run 'svn merge URL1@REV1 URL2@REV2 DIR' if URL2 is not None
843 (for a three-way merge between URLs and WC).
845 If URL2 is None, run 'svn merge -rREV1:REV2 URL1 DIR'. If both REV1
846 and REV2 are None, leave off the '-r' argument.
848 If ERROR_RE_STRING, the merge must exit with error, and the error
849 message must match regular expression ERROR_RE_STRING.
851 Else if ERROR_RE_STRING is None, then:
853 The subcommand output will be verified against OUTPUT_TREE. Output
854 related to mergeinfo notifications will be verified against
855 MERGEINFO_OUTPUT_TREE if that is not None. Output related to mergeinfo
856 elision will be verified against ELISION_OUTPUT_TREE if that is not None.
857 The working copy itself will be verified against DISK_TREE. If optional
858 STATUS_TREE is given, then 'svn status' output will be compared. The
859 'skipped' merge output will be compared to SKIP_TREE.
861 For the DISK_TREE verification, SINGLETON_HANDLER_A and
862 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
863 function's doc string for more details.
865 If CHECK_PROPS is set, then disk comparison will examine props.
867 If DRY_RUN is set then a --dry-run merge will be carried out first and
868 the output compared with that of the full merge.
870 Return if successful, raise on failure."""
872 merge_command = [ "merge" ]
873 if url2:
874 merge_command.extend((url1 + "@" + str(rev1), url2 + "@" + str(rev2)))
875 else:
876 if not (rev1 is None and rev2 is None):
877 merge_command.append("-r" + str(rev1) + ":" + str(rev2))
878 merge_command.append(url1)
879 merge_command.append(dir)
880 merge_command = tuple(merge_command)
882 if dry_run:
883 pre_disk = tree.build_tree_from_wc(dir)
884 dry_run_command = merge_command + ('--dry-run',)
885 dry_run_command = dry_run_command + args
886 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
887 *dry_run_command)
888 post_disk = tree.build_tree_from_wc(dir)
889 try:
890 tree.compare_trees("disk", post_disk, pre_disk)
891 except tree.SVNTreeError:
892 print("=============================================================")
893 print("Dry-run merge altered working copy")
894 print("=============================================================")
895 raise
898 # Update and make a tree of the output.
899 merge_command = merge_command + args
900 exit_code, out, err = main.run_svn(error_re_string, *merge_command)
902 if error_re_string:
903 if not error_re_string.startswith(".*"):
904 error_re_string = ".*(" + error_re_string + ")"
905 expected_err = verify.RegexOutput(error_re_string, match_all=False)
906 verify.verify_outputs(None, None, err, None, expected_err)
907 return
908 elif err:
909 raise verify.SVNUnexpectedStderr(err)
911 # Split the output into that related to application of the actual diff
912 # and that related to the recording of mergeinfo describing the merge.
913 merge_diff_out = []
914 mergeinfo_notification_out = []
915 mergeinfo_elision_out = []
916 mergeinfo_notifications = False
917 elision_notifications = False
918 for line in out:
919 if line.startswith('--- Recording'):
920 mergeinfo_notifications = True
921 elision_notifications = False
922 elif line.startswith('--- Eliding'):
923 mergeinfo_notifications = False
924 elision_notifications = True
925 elif line.startswith('--- Merging') or \
926 line.startswith('--- Reverse-merging') or \
927 line.startswith('Summary of conflicts') or \
928 line.startswith('Skipped missing target'):
929 mergeinfo_notifications = False
930 elision_notifications = False
932 if mergeinfo_notifications:
933 mergeinfo_notification_out.append(line)
934 elif elision_notifications:
935 mergeinfo_elision_out.append(line)
936 else:
937 merge_diff_out.append(line)
939 if dry_run and merge_diff_out != out_dry:
940 # Due to the way ra_serf works, it's possible that the dry-run and
941 # real merge operations did the same thing, but the output came in
942 # a different order. Let's see if maybe that's the case.
944 # NOTE: Would be nice to limit this dance to serf tests only, but...
945 out_copy = merge_diff_out[:]
946 out_dry_copy = out_dry[:]
947 out_copy.sort()
948 out_dry_copy.sort()
949 if out_copy != out_dry_copy:
950 print("=============================================================")
951 print("Merge outputs differ")
952 print("The dry-run merge output:")
953 for x in out_dry:
954 sys.stdout.write(x)
955 print("The full merge output:")
956 for x in out:
957 sys.stdout.write(x)
958 print("=============================================================")
959 raise main.SVNUnmatchedError
961 def missing_skip(a, b):
962 print("=============================================================")
963 print("Merge failed to skip: " + a.path)
964 print("=============================================================")
965 raise Failure
966 def extra_skip(a, b):
967 print("=============================================================")
968 print("Merge unexpectedly skipped: " + a.path)
969 print("=============================================================")
970 raise Failure
972 myskiptree = tree.build_tree_from_skipped(out)
973 if isinstance(skip_tree, wc.State):
974 skip_tree = skip_tree.old_tree()
975 try:
976 tree.compare_trees("skip", myskiptree, skip_tree,
977 extra_skip, None, missing_skip, None)
978 except tree.SVNTreeUnequal:
979 print("ACTUAL SKIP TREE:")
980 tree.dump_tree_script(myskiptree, dir + os.sep)
981 raise
983 actual_diff = svntest.wc.State.from_checkout(merge_diff_out, False)
984 actual_mergeinfo = svntest.wc.State.from_checkout(mergeinfo_notification_out,
985 False)
986 actual_elision = svntest.wc.State.from_checkout(mergeinfo_elision_out,
987 False)
988 verify_update(actual_diff, actual_mergeinfo, actual_elision, dir,
989 output_tree, mergeinfo_output_tree, elision_output_tree,
990 disk_tree, status_tree,
991 singleton_handler_a, a_baton,
992 singleton_handler_b, b_baton,
993 check_props)
996 def run_and_verify_patch(dir, patch_path,
997 output_tree, disk_tree, status_tree, skip_tree,
998 error_re_string=None,
999 check_props=False,
1000 dry_run=True,
1001 *args):
1002 """Run 'svn patch patch_path DIR'.
1004 If ERROR_RE_STRING, 'svn patch' must exit with error, and the error
1005 message must match regular expression ERROR_RE_STRING.
1007 Else if ERROR_RE_STRING is None, then:
1009 The subcommand output will be verified against OUTPUT_TREE, and the
1010 working copy itself will be verified against DISK_TREE. If optional
1011 STATUS_TREE is given, then 'svn status' output will be compared.
1012 The 'skipped' merge output will be compared to SKIP_TREE.
1014 If CHECK_PROPS is set, then disk comparison will examine props.
1016 If DRY_RUN is set then a --dry-run patch will be carried out first and
1017 the output compared with that of the full patch application.
1019 Returns if successful, raises on failure."""
1021 patch_command = [ "patch" ]
1022 patch_command.append(patch_path)
1023 patch_command.append(dir)
1024 patch_command = tuple(patch_command)
1026 if dry_run:
1027 pre_disk = tree.build_tree_from_wc(dir)
1028 dry_run_command = patch_command + ('--dry-run',)
1029 dry_run_command = dry_run_command + args
1030 exit_code, out_dry, err_dry = main.run_svn(error_re_string,
1031 *dry_run_command)
1032 post_disk = tree.build_tree_from_wc(dir)
1033 try:
1034 tree.compare_trees("disk", post_disk, pre_disk)
1035 except tree.SVNTreeError:
1036 print("=============================================================")
1037 print("'svn patch --dry-run' altered working copy")
1038 print("=============================================================")
1039 raise
1041 # Update and make a tree of the output.
1042 patch_command = patch_command + args
1043 exit_code, out, err = main.run_svn(True, *patch_command)
1045 if error_re_string:
1046 rm = re.compile(error_re_string)
1047 match = None
1048 for line in err:
1049 match = rm.search(line)
1050 if match:
1051 break
1052 if not match:
1053 raise main.SVNUnmatchedError
1054 elif err:
1055 print("UNEXPECTED STDERR:")
1056 for x in err:
1057 sys.stdout.write(x)
1058 raise verify.SVNUnexpectedStderr
1060 if dry_run and out != out_dry:
1061 print("=============================================================")
1062 print("Outputs differ")
1063 print("'svn patch --dry-run' output:")
1064 for x in out_dry:
1065 sys.stdout.write(x)
1066 print("'svn patch' output:")
1067 for x in out:
1068 sys.stdout.write(x)
1069 print("=============================================================")
1070 raise main.SVNUnmatchedError
1072 def missing_skip(a, b):
1073 print("=============================================================")
1074 print("'svn patch' failed to skip: " + a.path)
1075 print("=============================================================")
1076 raise Failure
1077 def extra_skip(a, b):
1078 print("=============================================================")
1079 print("'svn patch' unexpectedly skipped: " + a.path)
1080 print("=============================================================")
1081 raise Failure
1083 myskiptree = tree.build_tree_from_skipped(out)
1084 if isinstance(skip_tree, wc.State):
1085 skip_tree = skip_tree.old_tree()
1086 tree.compare_trees("skip", myskiptree, skip_tree,
1087 extra_skip, None, missing_skip, None)
1089 mytree = tree.build_tree_from_checkout(out, 0)
1091 # when the expected output is a list, we want a line-by-line
1092 # comparison to happen instead of a tree comparison
1093 if isinstance(output_tree, list):
1094 verify.verify_outputs(None, out, err, output_tree, error_re_string)
1095 output_tree = None
1097 verify_update(mytree, None, None, dir,
1098 output_tree, None, None, disk_tree, status_tree,
1099 check_props=check_props)
1102 def run_and_verify_mergeinfo(error_re_string = None,
1103 expected_output = [],
1104 *args):
1105 """Run 'svn mergeinfo ARGS', and compare the result against
1106 EXPECTED_OUTPUT, a list of string representations of revisions
1107 expected in the output. Raise an exception if an unexpected
1108 output is encountered."""
1110 mergeinfo_command = ["mergeinfo"]
1111 mergeinfo_command.extend(args)
1112 exit_code, out, err = main.run_svn(error_re_string, *mergeinfo_command)
1114 if error_re_string:
1115 if not error_re_string.startswith(".*"):
1116 error_re_string = ".*(" + error_re_string + ")"
1117 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1118 verify.verify_outputs(None, None, err, None, expected_err)
1119 return
1121 out = sorted([_f for _f in [x.rstrip()[1:] for x in out] if _f])
1122 expected_output.sort()
1123 extra_out = []
1124 if out != expected_output:
1125 exp_hash = dict.fromkeys(expected_output)
1126 for rev in out:
1127 if rev in exp_hash:
1128 del(exp_hash[rev])
1129 else:
1130 extra_out.append(rev)
1131 extra_exp = list(exp_hash.keys())
1132 raise Exception("Unexpected 'svn mergeinfo' output:\n"
1133 " expected but not found: %s\n"
1134 " found but not expected: %s"
1135 % (', '.join([str(x) for x in extra_exp]),
1136 ', '.join([str(x) for x in extra_out])))
1139 def run_and_verify_switch(wc_dir_name,
1140 wc_target,
1141 switch_url,
1142 output_tree, disk_tree, status_tree,
1143 error_re_string = None,
1144 singleton_handler_a = None,
1145 a_baton = None,
1146 singleton_handler_b = None,
1147 b_baton = None,
1148 check_props = False,
1149 *args):
1151 """Switch WC_TARGET (in working copy dir WC_DIR_NAME) to SWITCH_URL.
1153 If ERROR_RE_STRING, the switch must exit with error, and the error
1154 message must match regular expression ERROR_RE_STRING.
1156 Else if ERROR_RE_STRING is None, then:
1158 The subcommand output will be verified against OUTPUT_TREE, and the
1159 working copy itself will be verified against DISK_TREE. If optional
1160 STATUS_TREE is given, then 'svn status' output will be
1161 compared. (This is a good way to check that revision numbers were
1162 bumped.)
1164 For the DISK_TREE verification, SINGLETON_HANDLER_A and
1165 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
1166 function's doc string for more details.
1168 If CHECK_PROPS is set, then disk comparison will examine props.
1170 Return if successful, raise on failure."""
1172 # Update and make a tree of the output.
1173 exit_code, output, errput = main.run_svn(error_re_string, 'switch',
1174 switch_url, wc_target, *args)
1176 if error_re_string:
1177 if not error_re_string.startswith(".*"):
1178 error_re_string = ".*(" + error_re_string + ")"
1179 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1180 verify.verify_outputs(None, None, errput, None, expected_err)
1181 return
1182 elif errput:
1183 raise verify.SVNUnexpectedStderr(err)
1185 actual = wc.State.from_checkout(output)
1187 verify_update(actual, None, None, wc_dir_name,
1188 output_tree, None, None, disk_tree, status_tree,
1189 singleton_handler_a, a_baton,
1190 singleton_handler_b, b_baton,
1191 check_props)
1193 def process_output_for_commit(output):
1194 """Helper for run_and_verify_commit(), also used in the factory."""
1195 # Remove the final output line, and verify that the commit succeeded.
1196 lastline = ""
1197 if len(output):
1198 lastline = output.pop().strip()
1200 cm = re.compile("(Committed|Imported) revision [0-9]+.")
1201 match = cm.search(lastline)
1202 if not match:
1203 print("ERROR: commit did not succeed.")
1204 print("The final line from 'svn ci' was:")
1205 print(lastline)
1206 raise main.SVNCommitFailure
1208 # The new 'final' line in the output is either a regular line that
1209 # mentions {Adding, Deleting, Sending, ...}, or it could be a line
1210 # that says "Transmitting file data ...". If the latter case, we
1211 # want to remove the line from the output; it should be ignored when
1212 # building a tree.
1213 if len(output):
1214 lastline = output.pop()
1216 tm = re.compile("Transmitting file data.+")
1217 match = tm.search(lastline)
1218 if not match:
1219 # whoops, it was important output, put it back.
1220 output.append(lastline)
1222 return output
1225 def run_and_verify_commit(wc_dir_name, output_tree, status_tree,
1226 error_re_string = None,
1227 *args):
1228 """Commit and verify results within working copy WC_DIR_NAME,
1229 sending ARGS to the commit subcommand.
1231 The subcommand output will be verified against OUTPUT_TREE. If
1232 optional STATUS_TREE is given, then 'svn status' output will
1233 be compared. (This is a good way to check that revision numbers
1234 were bumped.)
1236 If ERROR_RE_STRING is None, the commit must not exit with error. If
1237 ERROR_RE_STRING is a string, the commit must exit with error, and
1238 the error message must match regular expression ERROR_RE_STRING.
1240 Return if successful, raise on failure."""
1242 if isinstance(output_tree, wc.State):
1243 output_tree = output_tree.old_tree()
1244 if isinstance(status_tree, wc.State):
1245 status_tree = status_tree.old_tree()
1247 # Commit.
1248 exit_code, output, errput = main.run_svn(error_re_string, 'ci',
1249 '-m', 'log msg',
1250 *args)
1252 if error_re_string:
1253 if not error_re_string.startswith(".*"):
1254 error_re_string = ".*(" + error_re_string + ")"
1255 expected_err = verify.RegexOutput(error_re_string, match_all=False)
1256 verify.verify_outputs(None, None, errput, None, expected_err)
1257 return
1259 # Else not expecting error:
1261 # Convert the output into a tree.
1262 output = process_output_for_commit(output)
1263 actual = tree.build_tree_from_commit(output)
1265 # Verify actual output against expected output.
1266 try:
1267 tree.compare_trees("output", actual, output_tree)
1268 except tree.SVNTreeError:
1269 verify.display_trees("Output of commit is unexpected",
1270 "OUTPUT TREE", output_tree, actual)
1271 print("ACTUAL OUTPUT TREE:")
1272 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1273 raise
1275 # Verify via 'status' command too, if possible.
1276 if status_tree:
1277 run_and_verify_status(wc_dir_name, status_tree)
1280 # This function always passes '-q' to the status command, which
1281 # suppresses the printing of any unversioned or nonexistent items.
1282 def run_and_verify_status(wc_dir_name, output_tree,
1283 singleton_handler_a = None,
1284 a_baton = None,
1285 singleton_handler_b = None,
1286 b_baton = None):
1287 """Run 'status' on WC_DIR_NAME and compare it with the
1288 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1289 be passed to tree.compare_trees - see that function's doc string for
1290 more details.
1291 Returns on success, raises on failure."""
1293 if isinstance(output_tree, wc.State):
1294 output_state = output_tree
1295 output_tree = output_tree.old_tree()
1296 else:
1297 output_state = None
1299 exit_code, output, errput = main.run_svn(None, 'status', '-v', '-u', '-q',
1300 wc_dir_name)
1302 actual = tree.build_tree_from_status(output)
1304 # Verify actual output against expected output.
1305 try:
1306 tree.compare_trees("status", actual, output_tree,
1307 singleton_handler_a, a_baton,
1308 singleton_handler_b, b_baton)
1309 except tree.SVNTreeError:
1310 verify.display_trees(None, 'STATUS OUTPUT TREE', output_tree, actual)
1311 print("ACTUAL STATUS TREE:")
1312 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1313 raise
1315 # if we have an output State, and we can/are-allowed to create an
1316 # entries-based State, then compare the two.
1317 if output_state:
1318 entries_state = wc.State.from_entries(wc_dir_name)
1319 if entries_state:
1320 tweaked = output_state.copy()
1321 tweaked.tweak_for_entries_compare()
1322 try:
1323 tweaked.compare_and_display('entries', entries_state)
1324 except tree.SVNTreeUnequal:
1325 ### do something more
1326 raise
1329 # A variant of previous func, but doesn't pass '-q'. This allows us
1330 # to verify unversioned or nonexistent items in the list.
1331 def run_and_verify_unquiet_status(wc_dir_name, status_tree):
1332 """Run 'status' on WC_DIR_NAME and compare it with the
1333 expected STATUS_TREE.
1334 Returns on success, raises on failure."""
1336 if isinstance(status_tree, wc.State):
1337 status_tree = status_tree.old_tree()
1339 exit_code, output, errput = main.run_svn(None, 'status', '-v',
1340 '-u', wc_dir_name)
1342 actual = tree.build_tree_from_status(output)
1344 # Verify actual output against expected output.
1345 try:
1346 tree.compare_trees("UNQUIET STATUS", actual, status_tree)
1347 except tree.SVNTreeError:
1348 print("ACTUAL UNQUIET STATUS TREE:")
1349 tree.dump_tree_script(actual, wc_dir_name + os.sep)
1350 raise
1352 def run_and_verify_diff_summarize_xml(error_re_string = [],
1353 expected_prefix = None,
1354 expected_paths = [],
1355 expected_items = [],
1356 expected_props = [],
1357 expected_kinds = [],
1358 *args):
1359 """Run 'diff --summarize --xml' with the arguments *ARGS, which should
1360 contain all arguments beyond for your 'diff --summarize --xml' omitting
1361 said arguments. EXPECTED_PREFIX will store a "common" path prefix
1362 expected to be at the beginning of each summarized path. If
1363 EXPECTED_PREFIX is None, then EXPECTED_PATHS will need to be exactly
1364 as 'svn diff --summarize --xml' will output. If ERROR_RE_STRING, the
1365 command must exit with error, and the error message must match regular
1366 expression ERROR_RE_STRING.
1368 Else if ERROR_RE_STRING is None, the subcommand output will be parsed
1369 into an XML document and will then be verified by comparing the parsed
1370 output to the contents in the EXPECTED_PATHS, EXPECTED_ITEMS,
1371 EXPECTED_PROPS and EXPECTED_KINDS. Returns on success, raises
1372 on failure."""
1374 exit_code, output, errput = run_and_verify_svn(None, None, error_re_string,
1375 'diff', '--summarize',
1376 '--xml', *args)
1379 # Return if errors are present since they were expected
1380 if len(errput) > 0:
1381 return
1383 doc = parseString(''.join(output))
1384 paths = doc.getElementsByTagName("path")
1385 items = expected_items
1386 kinds = expected_kinds
1388 for path in paths:
1389 modified_path = path.childNodes[0].data
1391 if (expected_prefix is not None
1392 and modified_path.find(expected_prefix) == 0):
1393 modified_path = modified_path.replace(expected_prefix, '')[1:].strip()
1395 # Workaround single-object diff
1396 if len(modified_path) == 0:
1397 modified_path = path.childNodes[0].data.split(os.sep)[-1]
1399 # From here on, we use '/' as path separator.
1400 if os.sep != "/":
1401 modified_path = modified_path.replace(os.sep, "/")
1403 if modified_path not in expected_paths:
1404 print("ERROR: %s not expected in the changed paths." % modified_path)
1405 raise Failure
1407 index = expected_paths.index(modified_path)
1408 expected_item = items[index]
1409 expected_kind = kinds[index]
1410 expected_prop = expected_props[index]
1411 actual_item = path.getAttribute('item')
1412 actual_kind = path.getAttribute('kind')
1413 actual_prop = path.getAttribute('props')
1415 if expected_item != actual_item:
1416 print("ERROR: expected: %s actual: %s" % (expected_item, actual_item))
1417 raise Failure
1419 if expected_kind != actual_kind:
1420 print("ERROR: expected: %s actual: %s" % (expected_kind, actual_kind))
1421 raise Failure
1423 if expected_prop != actual_prop:
1424 print("ERROR: expected: %s actual: %s" % (expected_prop, actual_prop))
1425 raise Failure
1427 def run_and_verify_diff_summarize(output_tree, *args):
1428 """Run 'diff --summarize' with the arguments *ARGS.
1430 The subcommand output will be verified against OUTPUT_TREE. Returns
1431 on success, raises on failure.
1434 if isinstance(output_tree, wc.State):
1435 output_tree = output_tree.old_tree()
1437 exit_code, output, errput = main.run_svn(None, 'diff', '--summarize',
1438 *args)
1440 actual = tree.build_tree_from_diff_summarize(output)
1442 # Verify actual output against expected output.
1443 try:
1444 tree.compare_trees("output", actual, output_tree)
1445 except tree.SVNTreeError:
1446 verify.display_trees(None, 'DIFF OUTPUT TREE', output_tree, actual)
1447 print("ACTUAL DIFF OUTPUT TREE:")
1448 tree.dump_tree_script(actual)
1449 raise
1451 def run_and_validate_lock(path, username):
1452 """`svn lock' the given path and validate the contents of the lock.
1453 Use the given username. This is important because locks are
1454 user specific."""
1456 comment = "Locking path:%s." % path
1458 # lock the path
1459 run_and_verify_svn(None, ".*locked by user", [], 'lock',
1460 '--username', username,
1461 '-m', comment, path)
1463 # Run info and check that we get the lock fields.
1464 exit_code, output, err = run_and_verify_svn(None, None, [],
1465 'info','-R',
1466 path)
1468 ### TODO: Leverage RegexOuput([...], match_all=True) here.
1469 # prepare the regexs to compare against
1470 token_re = re.compile(".*?Lock Token: opaquelocktoken:.*?", re.DOTALL)
1471 author_re = re.compile(".*?Lock Owner: %s\n.*?" % username, re.DOTALL)
1472 created_re = re.compile(".*?Lock Created:.*?", re.DOTALL)
1473 comment_re = re.compile(".*?%s\n.*?" % re.escape(comment), re.DOTALL)
1474 # join all output lines into one
1475 output = "".join(output)
1476 # Fail even if one regex does not match
1477 if ( not (token_re.match(output) and
1478 author_re.match(output) and
1479 created_re.match(output) and
1480 comment_re.match(output))):
1481 raise Failure
1483 def _run_and_verify_resolve(cmd, expected_paths, *args):
1484 """Run "svn CMD" (where CMD is 'resolve' or 'resolved') with arguments
1485 ARGS, and verify that it resolves the paths in EXPECTED_PATHS and no others.
1486 If no ARGS are specified, use the elements of EXPECTED_PATHS as the
1487 arguments."""
1488 # TODO: verify that the status of PATHS changes accordingly.
1489 if len(args) == 0:
1490 args = expected_paths
1491 expected_output = verify.UnorderedOutput([
1492 "Resolved conflicted state of '" + path + "'\n" for path in
1493 expected_paths])
1494 run_and_verify_svn(None, expected_output, [],
1495 cmd, *args)
1497 def run_and_verify_resolve(expected_paths, *args):
1498 """Run "svn resolve" with arguments ARGS, and verify that it resolves the
1499 paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the
1500 elements of EXPECTED_PATHS as the arguments."""
1501 _run_and_verify_resolve('resolve', expected_paths, *args)
1503 def run_and_verify_resolved(expected_paths, *args):
1504 """Run "svn resolved" with arguments ARGS, and verify that it resolves the
1505 paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the
1506 elements of EXPECTED_PATHS as the arguments."""
1507 _run_and_verify_resolve('resolved', expected_paths, *args)
1510 ######################################################################
1511 # Other general utilities
1514 # This allows a test to *quickly* bootstrap itself.
1515 def make_repo_and_wc(sbox, create_wc = True, read_only = False):
1516 """Create a fresh 'Greek Tree' repository and check out a WC from it.
1518 If read_only is False, a dedicated repository will be created, named
1519 TEST_NAME. The repository will live in the global dir 'general_repo_dir'.
1520 If read_only is True the pristine repository will be used.
1522 If create_wc is True, a dedicated working copy will be checked out from
1523 the repository, named TEST_NAME. The wc directory will live in the global
1524 dir 'general_wc_dir'.
1526 Both variables 'general_repo_dir' and 'general_wc_dir' are defined at the
1527 top of this test suite.) Returns on success, raises on failure."""
1529 # Create (or copy afresh) a new repos with a greek tree in it.
1530 if not read_only:
1531 guarantee_greek_repository(sbox.repo_dir)
1533 if create_wc:
1534 # Generate the expected output tree.
1535 expected_output = main.greek_state.copy()
1536 expected_output.wc_dir = sbox.wc_dir
1537 expected_output.tweak(status='A ', contents=None)
1539 # Generate an expected wc tree.
1540 expected_wc = main.greek_state
1542 # Do a checkout, and verify the resulting output and disk contents.
1543 run_and_verify_checkout(sbox.repo_url,
1544 sbox.wc_dir,
1545 expected_output,
1546 expected_wc)
1547 else:
1548 # just make sure the parent folder of our working copy is created
1549 try:
1550 os.mkdir(main.general_wc_dir)
1551 except OSError, err:
1552 if err.errno != errno.EEXIST:
1553 raise
1555 # Duplicate a working copy or other dir.
1556 def duplicate_dir(wc_name, wc_copy_name):
1557 """Copy the working copy WC_NAME to WC_COPY_NAME. Overwrite any
1558 existing tree at that location."""
1560 main.safe_rmtree(wc_copy_name)
1561 shutil.copytree(wc_name, wc_copy_name)
1565 def get_virginal_state(wc_dir, rev):
1566 "Return a virginal greek tree state for a WC and repos at revision REV."
1568 rev = str(rev) ### maybe switch rev to an integer?
1570 # copy the greek tree, shift it to the new wc_dir, insert a root elem,
1571 # then tweak all values
1572 state = main.greek_state.copy()
1573 state.wc_dir = wc_dir
1574 state.desc[''] = wc.StateItem()
1575 state.tweak(contents=None, status=' ', wc_rev=rev)
1577 return state
1579 def remove_admin_tmp_dir(wc_dir):
1580 "Remove the tmp directory within the administrative directory."
1582 tmp_path = os.path.join(wc_dir, main.get_admin_name(), 'tmp')
1583 ### Any reason not to use main.safe_rmtree()?
1584 os.rmdir(os.path.join(tmp_path, 'prop-base'))
1585 os.rmdir(os.path.join(tmp_path, 'props'))
1586 os.rmdir(os.path.join(tmp_path, 'text-base'))
1587 os.rmdir(tmp_path)
1589 # Cheap administrative directory locking
1590 def lock_admin_dir(wc_dir):
1591 "Lock a SVN administrative directory"
1593 db = svntest.sqlite3.connect(os.path.join(wc_dir, main.get_admin_name(),
1594 'wc.db'))
1595 db.execute('insert into wc_lock (wc_id, local_dir_relpath, locked_levels) '
1596 + 'values (?, ?, ?)',
1597 (1, '', 0))
1598 db.commit()
1599 db.close()
1601 def get_wc_uuid(wc_dir):
1602 "Return the UUID of the working copy at WC_DIR."
1603 return run_and_parse_info(wc_dir)[0]['Repository UUID']
1605 def get_wc_base_rev(wc_dir):
1606 "Return the BASE revision of the working copy at WC_DIR."
1607 return run_and_parse_info(wc_dir)[0]['Revision']
1609 def hook_failure_message(hook_name):
1610 """Return the error message that the client prints for failure of the
1611 specified hook HOOK_NAME. The wording changed with Subversion 1.5."""
1612 if svntest.main.options.server_minor_version < 5:
1613 return "'%s' hook failed with error output:\n" % hook_name
1614 else:
1615 if hook_name in ["start-commit", "pre-commit"]:
1616 action = "Commit"
1617 elif hook_name == "pre-revprop-change":
1618 action = "Revprop change"
1619 elif hook_name == "pre-lock":
1620 action = "Lock"
1621 elif hook_name == "pre-unlock":
1622 action = "Unlock"
1623 else:
1624 action = None
1625 if action is None:
1626 message = "%s hook failed (exit code 1)" % (hook_name,)
1627 else:
1628 message = "%s blocked by %s hook (exit code 1)" % (action, hook_name)
1629 return message + " with output:\n"
1631 def create_failing_hook(repo_dir, hook_name, text):
1632 """Create a HOOK_NAME hook in the repository at REPO_DIR that prints
1633 TEXT to stderr and exits with an error."""
1635 hook_path = os.path.join(repo_dir, 'hooks', hook_name)
1636 # Embed the text carefully: it might include characters like "%" and "'".
1637 main.create_python_hook_script(hook_path, 'import sys\n'
1638 'sys.stderr.write(' + repr(text) + ')\n'
1639 'sys.exit(1)\n')
1641 def enable_revprop_changes(repo_dir):
1642 """Enable revprop changes in the repository at REPO_DIR by creating a
1643 pre-revprop-change hook script and (if appropriate) making it executable."""
1645 hook_path = main.get_pre_revprop_change_hook_path(repo_dir)
1646 main.create_python_hook_script(hook_path, 'import sys; sys.exit(0)')
1648 def disable_revprop_changes(repo_dir):
1649 """Disable revprop changes in the repository at REPO_DIR by creating a
1650 pre-revprop-change hook script that prints "pre-revprop-change" followed
1651 by its arguments, and returns an error."""
1653 hook_path = main.get_pre_revprop_change_hook_path(repo_dir)
1654 main.create_python_hook_script(hook_path,
1655 'import sys\n'
1656 'sys.stderr.write("pre-revprop-change %s" % " ".join(sys.argv[1:6]))\n'
1657 'sys.exit(1)\n')
1659 def create_failing_post_commit_hook(repo_dir):
1660 """Create a post-commit hook script in the repository at REPO_DIR that always
1661 reports an error."""
1663 hook_path = main.get_post_commit_hook_path(repo_dir)
1664 main.create_python_hook_script(hook_path, 'import sys\n'
1665 'sys.stderr.write("Post-commit hook failed")\n'
1666 'sys.exit(1)')
1668 # set_prop can be used for properties with NULL characters which are not
1669 # handled correctly when passed to subprocess.Popen() and values like "*"
1670 # which are not handled correctly on Windows.
1671 def set_prop(name, value, path, expected_err=None):
1672 """Set a property with specified value"""
1673 if value and (value[0] == '-' or '\x00' in value or sys.platform == 'win32'):
1674 from tempfile import mkstemp
1675 (fd, value_file_path) = mkstemp()
1676 value_file = open(value_file_path, 'wb')
1677 value_file.write(value)
1678 value_file.flush()
1679 value_file.close()
1680 main.run_svn(expected_err, 'propset', '-F', value_file_path, name, path)
1681 os.close(fd)
1682 os.remove(value_file_path)
1683 else:
1684 main.run_svn(expected_err, 'propset', name, value, path)
1686 def check_prop(name, path, exp_out):
1687 """Verify that property NAME on PATH has a value of EXP_OUT"""
1688 # Not using run_svn because binary_mode must be set
1689 exit_code, out, err = main.run_command(main.svn_binary, None, 1, 'pg',
1690 '--strict', name, path,
1691 '--config-dir',
1692 main.default_config_dir,
1693 '--username', main.wc_author,
1694 '--password', main.wc_passwd)
1695 if out != exp_out:
1696 print("svn pg --strict %s output does not match expected." % name)
1697 print("Expected standard output: %s\n" % exp_out)
1698 print("Actual standard output: %s\n" % out)
1699 raise Failure
1701 def fill_file_with_lines(wc_path, line_nbr, line_descrip=None,
1702 append=True):
1703 """Change the file at WC_PATH (adding some lines), and return its
1704 new contents. LINE_NBR indicates the line number at which the new
1705 contents should assume that it's being appended. LINE_DESCRIP is
1706 something like 'This is line' (the default) or 'Conflicting line'."""
1708 if line_descrip is None:
1709 line_descrip = "This is line"
1711 # Generate the new contents for the file.
1712 contents = ""
1713 for n in range(line_nbr, line_nbr + 3):
1714 contents = contents + line_descrip + " " + repr(n) + " in '" + \
1715 os.path.basename(wc_path) + "'.\n"
1717 # Write the new contents to the file.
1718 if append:
1719 main.file_append(wc_path, contents)
1720 else:
1721 main.file_write(wc_path, contents)
1723 return contents
1725 def inject_conflict_into_wc(sbox, state_path, file_path,
1726 expected_disk, expected_status, merged_rev):
1727 """Create a conflict at FILE_PATH by replacing its contents,
1728 committing the change, backdating it to its previous revision,
1729 changing its contents again, then updating it to merge in the
1730 previous change."""
1732 wc_dir = sbox.wc_dir
1734 # Make a change to the file.
1735 contents = fill_file_with_lines(file_path, 1, "This is line", append=False)
1737 # Commit the changed file, first taking note of the current revision.
1738 prev_rev = expected_status.desc[state_path].wc_rev
1739 expected_output = wc.State(wc_dir, {
1740 state_path : wc.StateItem(verb='Sending'),
1742 if expected_status:
1743 expected_status.tweak(state_path, wc_rev=merged_rev)
1744 run_and_verify_commit(wc_dir, expected_output, expected_status,
1745 None, file_path)
1747 # Backdate the file.
1748 exit_code, output, errput = main.run_svn(None, "up", "-r", str(prev_rev),
1749 file_path)
1750 if expected_status:
1751 expected_status.tweak(state_path, wc_rev=prev_rev)
1753 # Make a conflicting change to the file, and backdate the file.
1754 conflicting_contents = fill_file_with_lines(file_path, 1, "Conflicting line",
1755 append=False)
1757 # Merge the previous change into the file to produce a conflict.
1758 if expected_disk:
1759 expected_disk.tweak(state_path, contents="")
1760 expected_output = wc.State(wc_dir, {
1761 state_path : wc.StateItem(status='C '),
1763 inject_conflict_into_expected_state(state_path,
1764 expected_disk, expected_status,
1765 conflicting_contents, contents,
1766 merged_rev)
1767 exit_code, output, errput = main.run_svn(None, "up", "-r", str(merged_rev),
1768 sbox.repo_url + "/" + state_path,
1769 file_path)
1770 if expected_status:
1771 expected_status.tweak(state_path, wc_rev=merged_rev)
1773 def inject_conflict_into_expected_state(state_path,
1774 expected_disk, expected_status,
1775 wc_text, merged_text, merged_rev):
1776 """Update the EXPECTED_DISK and EXPECTED_STATUS trees for the
1777 conflict at STATE_PATH (ignored if None). WC_TEXT, MERGED_TEXT, and
1778 MERGED_REV are used to determine the contents of the conflict (the
1779 text parameters should be newline-terminated)."""
1780 if expected_disk:
1781 conflict_marker = make_conflict_marker_text(wc_text, merged_text,
1782 merged_rev)
1783 existing_text = expected_disk.desc[state_path].contents or ""
1784 expected_disk.tweak(state_path, contents=existing_text + conflict_marker)
1786 if expected_status:
1787 expected_status.tweak(state_path, status='C ')
1789 def make_conflict_marker_text(wc_text, merged_text, merged_rev):
1790 """Return the conflict marker text described by WC_TEXT (the current
1791 text in the working copy, MERGED_TEXT (the conflicting text merged
1792 in), and MERGED_REV (the revision from whence the conflicting text
1793 came)."""
1794 return "<<<<<<< .working\n" + wc_text + "=======\n" + \
1795 merged_text + ">>>>>>> .merge-right.r" + str(merged_rev) + "\n"
1798 def build_greek_tree_conflicts(sbox):
1799 """Create a working copy that has tree-conflict markings.
1800 After this function has been called, sbox.wc_dir is a working
1801 copy that has specific tree-conflict markings.
1803 In particular, this does two conflicting sets of edits and performs an
1804 update so that tree conflicts appear.
1806 Note that this function calls sbox.build() because it needs a clean sbox.
1807 So, there is no need to call sbox.build() before this.
1809 The conflicts are the result of an 'update' on the following changes:
1811 Incoming Local
1813 A/D/G/pi text-mod del
1814 A/D/G/rho del text-mod
1815 A/D/G/tau del del
1817 This function is useful for testing that tree-conflicts are handled
1818 properly once they have appeared, e.g. that commits are blocked, that the
1819 info output is correct, etc.
1821 See also the tree-conflicts tests using deep_trees in various other
1822 .py files, and tree_conflict_tests.py.
1825 sbox.build()
1826 wc_dir = sbox.wc_dir
1827 j = os.path.join
1828 G = j(wc_dir, 'A', 'D', 'G')
1829 pi = j(G, 'pi')
1830 rho = j(G, 'rho')
1831 tau = j(G, 'tau')
1833 # Make incoming changes and "store them away" with a commit.
1834 main.file_append(pi, "Incoming edit.\n")
1835 main.run_svn(None, 'del', rho)
1836 main.run_svn(None, 'del', tau)
1838 expected_output = wc.State(wc_dir, {
1839 'A/D/G/pi' : Item(verb='Sending'),
1840 'A/D/G/rho' : Item(verb='Deleting'),
1841 'A/D/G/tau' : Item(verb='Deleting'),
1843 expected_status = get_virginal_state(wc_dir, 1)
1844 expected_status.tweak('A/D/G/pi', wc_rev='2')
1845 expected_status.remove('A/D/G/rho', 'A/D/G/tau')
1846 run_and_verify_commit(wc_dir, expected_output, expected_status, None,
1847 '-m', 'Incoming changes.', wc_dir )
1849 # Update back to the pristine state ("time-warp").
1850 expected_output = wc.State(wc_dir, {
1851 'A/D/G/pi' : Item(status='U '),
1852 'A/D/G/rho' : Item(status='A '),
1853 'A/D/G/tau' : Item(status='A '),
1855 expected_disk = main.greek_state
1856 expected_status = get_virginal_state(wc_dir, 1)
1857 run_and_verify_update(wc_dir, expected_output, expected_disk,
1858 expected_status, None, None, None, None, None, False,
1859 '-r', '1', wc_dir)
1861 # Make local changes
1862 main.run_svn(None, 'del', pi)
1863 main.file_append(rho, "Local edit.\n")
1864 main.run_svn(None, 'del', tau)
1866 # Update, receiving the incoming changes on top of the local changes,
1867 # causing tree conflicts. Don't check for any particular result: that is
1868 # the job of other tests.
1869 run_and_verify_svn(None, verify.AnyOutput, [], 'update', wc_dir)
1872 def make_deep_trees(base):
1873 """Helper function for deep trees conflicts. Create a set of trees,
1874 each in its own "container" dir. Any conflicts can be tested separately
1875 in each container.
1877 j = os.path.join
1878 # Create the container dirs.
1879 F = j(base, 'F')
1880 D = j(base, 'D')
1881 DF = j(base, 'DF')
1882 DD = j(base, 'DD')
1883 DDF = j(base, 'DDF')
1884 DDD = j(base, 'DDD')
1885 os.makedirs(F)
1886 os.makedirs(j(D, 'D1'))
1887 os.makedirs(j(DF, 'D1'))
1888 os.makedirs(j(DD, 'D1', 'D2'))
1889 os.makedirs(j(DDF, 'D1', 'D2'))
1890 os.makedirs(j(DDD, 'D1', 'D2', 'D3'))
1892 # Create their files.
1893 alpha = j(F, 'alpha')
1894 beta = j(DF, 'D1', 'beta')
1895 gamma = j(DDF, 'D1', 'D2', 'gamma')
1896 main.file_append(alpha, "This is the file 'alpha'.\n")
1897 main.file_append(beta, "This is the file 'beta'.\n")
1898 main.file_append(gamma, "This is the file 'gamma'.\n")
1901 def add_deep_trees(sbox, base_dir_name):
1902 """Prepare a "deep_trees" within a given directory.
1904 The directory <sbox.wc_dir>/<base_dir_name> is created and a deep_tree
1905 is created within. The items are only added, a commit has to be
1906 called separately, if needed.
1908 <base_dir_name> will thus be a container for the set of containers
1909 mentioned in make_deep_trees().
1911 j = os.path.join
1912 base = j(sbox.wc_dir, base_dir_name)
1913 make_deep_trees(base)
1914 main.run_svn(None, 'add', base)
1917 Item = wc.StateItem
1919 # initial deep trees state
1920 deep_trees_virginal_state = wc.State('', {
1921 'F' : Item(),
1922 'F/alpha' : Item("This is the file 'alpha'.\n"),
1923 'D' : Item(),
1924 'D/D1' : Item(),
1925 'DF' : Item(),
1926 'DF/D1' : Item(),
1927 'DF/D1/beta' : Item("This is the file 'beta'.\n"),
1928 'DD' : Item(),
1929 'DD/D1' : Item(),
1930 'DD/D1/D2' : Item(),
1931 'DDF' : Item(),
1932 'DDF/D1' : Item(),
1933 'DDF/D1/D2' : Item(),
1934 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\n"),
1935 'DDD' : Item(),
1936 'DDD/D1' : Item(),
1937 'DDD/D1/D2' : Item(),
1938 'DDD/D1/D2/D3' : Item(),
1942 # Many actions on deep trees and their resulting states...
1944 def deep_trees_leaf_edit(base):
1945 """Helper function for deep trees test cases. Append text to files,
1946 create new files in empty directories, and change leaf node properties."""
1947 j = os.path.join
1948 F = j(base, 'F', 'alpha')
1949 DF = j(base, 'DF', 'D1', 'beta')
1950 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
1951 main.file_append(F, "More text for file alpha.\n")
1952 main.file_append(DF, "More text for file beta.\n")
1953 main.file_append(DDF, "More text for file gamma.\n")
1954 run_and_verify_svn(None, verify.AnyOutput, [],
1955 'propset', 'prop1', '1', F, DF, DDF)
1957 D = j(base, 'D', 'D1')
1958 DD = j(base, 'DD', 'D1', 'D2')
1959 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
1960 run_and_verify_svn(None, verify.AnyOutput, [],
1961 'propset', 'prop1', '1', D, DD, DDD)
1962 D = j(base, 'D', 'D1', 'delta')
1963 DD = j(base, 'DD', 'D1', 'D2', 'epsilon')
1964 DDD = j(base, 'DDD', 'D1', 'D2', 'D3', 'zeta')
1965 main.file_append(D, "This is the file 'delta'.\n")
1966 main.file_append(DD, "This is the file 'epsilon'.\n")
1967 main.file_append(DDD, "This is the file 'zeta'.\n")
1968 run_and_verify_svn(None, verify.AnyOutput, [],
1969 'add', D, DD, DDD)
1971 # deep trees state after a call to deep_trees_leaf_edit
1972 deep_trees_after_leaf_edit = wc.State('', {
1973 'F' : Item(),
1974 'F/alpha' : Item("This is the file 'alpha'.\nMore text for file alpha.\n"),
1975 'D' : Item(),
1976 'D/D1' : Item(),
1977 'D/D1/delta' : Item("This is the file 'delta'.\n"),
1978 'DF' : Item(),
1979 'DF/D1' : Item(),
1980 'DF/D1/beta' : Item("This is the file 'beta'.\nMore text for file beta.\n"),
1981 'DD' : Item(),
1982 'DD/D1' : Item(),
1983 'DD/D1/D2' : Item(),
1984 'DD/D1/D2/epsilon' : Item("This is the file 'epsilon'.\n"),
1985 'DDF' : Item(),
1986 'DDF/D1' : Item(),
1987 'DDF/D1/D2' : Item(),
1988 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\nMore text for file gamma.\n"),
1989 'DDD' : Item(),
1990 'DDD/D1' : Item(),
1991 'DDD/D1/D2' : Item(),
1992 'DDD/D1/D2/D3' : Item(),
1993 'DDD/D1/D2/D3/zeta' : Item("This is the file 'zeta'.\n"),
1997 def deep_trees_leaf_del(base):
1998 """Helper function for deep trees test cases. Delete files and empty
1999 dirs."""
2000 j = os.path.join
2001 F = j(base, 'F', 'alpha')
2002 D = j(base, 'D', 'D1')
2003 DF = j(base, 'DF', 'D1', 'beta')
2004 DD = j(base, 'DD', 'D1', 'D2')
2005 DDF = j(base, 'DDF', 'D1', 'D2', 'gamma')
2006 DDD = j(base, 'DDD', 'D1', 'D2', 'D3')
2007 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
2009 # deep trees state after a call to deep_trees_leaf_del
2010 deep_trees_after_leaf_del = wc.State('', {
2011 'F' : Item(),
2012 'D' : Item(),
2013 'DF' : Item(),
2014 'DF/D1' : Item(),
2015 'DD' : Item(),
2016 'DD/D1' : Item(),
2017 'DDF' : Item(),
2018 'DDF/D1' : Item(),
2019 'DDF/D1/D2' : Item(),
2020 'DDD' : Item(),
2021 'DDD/D1' : Item(),
2022 'DDD/D1/D2' : Item(),
2026 def deep_trees_tree_del(base):
2027 """Helper function for deep trees test cases. Delete top-level dirs."""
2028 j = os.path.join
2029 F = j(base, 'F', 'alpha')
2030 D = j(base, 'D', 'D1')
2031 DF = j(base, 'DF', 'D1')
2032 DD = j(base, 'DD', 'D1')
2033 DDF = j(base, 'DDF', 'D1')
2034 DDD = j(base, 'DDD', 'D1')
2035 main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD)
2037 def deep_trees_rmtree(base):
2038 """Helper function for deep trees test cases. Delete top-level dirs
2039 with rmtree instead of svn del."""
2040 j = os.path.join
2041 F = j(base, 'F', 'alpha')
2042 D = j(base, 'D', 'D1')
2043 DF = j(base, 'DF', 'D1')
2044 DD = j(base, 'DD', 'D1')
2045 DDF = j(base, 'DDF', 'D1')
2046 DDD = j(base, 'DDD', 'D1')
2047 os.unlink(F)
2048 main.safe_rmtree(D)
2049 main.safe_rmtree(DF)
2050 main.safe_rmtree(DD)
2051 main.safe_rmtree(DDF)
2052 main.safe_rmtree(DDD)
2054 # deep trees state after a call to deep_trees_tree_del
2055 deep_trees_after_tree_del = wc.State('', {
2056 'F' : Item(),
2057 'D' : Item(),
2058 'DF' : Item(),
2059 'DD' : Item(),
2060 'DDF' : Item(),
2061 'DDD' : Item(),
2064 # deep trees state without any files
2065 deep_trees_empty_dirs = wc.State('', {
2066 'F' : Item(),
2067 'D' : Item(),
2068 'D/D1' : Item(),
2069 'DF' : Item(),
2070 'DF/D1' : Item(),
2071 'DD' : Item(),
2072 'DD/D1' : Item(),
2073 'DD/D1/D2' : Item(),
2074 'DDF' : Item(),
2075 'DDF/D1' : Item(),
2076 'DDF/D1/D2' : Item(),
2077 'DDD' : Item(),
2078 'DDD/D1' : Item(),
2079 'DDD/D1/D2' : Item(),
2080 'DDD/D1/D2/D3' : Item(),
2083 def deep_trees_tree_del_repos(base):
2084 """Helper function for deep trees test cases. Delete top-level dirs,
2085 directly in the repository."""
2086 j = '/'.join
2087 F = j([base, 'F', 'alpha'])
2088 D = j([base, 'D', 'D1'])
2089 DF = j([base, 'DF', 'D1'])
2090 DD = j([base, 'DD', 'D1'])
2091 DDF = j([base, 'DDF', 'D1'])
2092 DDD = j([base, 'DDD', 'D1'])
2093 main.run_svn(None, 'mkdir', '-m', '', F, D, DF, DD, DDF, DDD)
2095 # Expected merge/update/switch output.
2097 deep_trees_conflict_output = wc.State('', {
2098 'F/alpha' : Item(status=' ', treeconflict='C'),
2099 'D/D1' : Item(status=' ', treeconflict='C'),
2100 'DF/D1' : Item(status=' ', treeconflict='C'),
2101 'DD/D1' : Item(status=' ', treeconflict='C'),
2102 'DDF/D1' : Item(status=' ', treeconflict='C'),
2103 'DDD/D1' : Item(status=' ', treeconflict='C'),
2106 deep_trees_conflict_output_skipped = wc.State('', {
2107 'D/D1' : Item(verb='Skipped'),
2108 'F/alpha' : Item(verb='Skipped'),
2109 'DD/D1' : Item(verb='Skipped'),
2110 'DF/D1' : Item(verb='Skipped'),
2111 'DDD/D1' : Item(verb='Skipped'),
2112 'DDF/D1' : Item(verb='Skipped'),
2115 # Expected status output after merge/update/switch.
2117 deep_trees_status_local_tree_del = wc.State('', {
2118 '' : Item(status=' ', wc_rev=3),
2119 'D' : Item(status=' ', wc_rev=3),
2120 'D/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2121 'DD' : Item(status=' ', wc_rev=3),
2122 'DD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2123 'DD/D1/D2' : Item(status='D ', wc_rev=2),
2124 'DDD' : Item(status=' ', wc_rev=3),
2125 'DDD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2126 'DDD/D1/D2' : Item(status='D ', wc_rev=2),
2127 'DDD/D1/D2/D3' : Item(status='D ', wc_rev=2),
2128 'DDF' : Item(status=' ', wc_rev=3),
2129 'DDF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2130 'DDF/D1/D2' : Item(status='D ', wc_rev=2),
2131 'DDF/D1/D2/gamma' : Item(status='D ', wc_rev=2),
2132 'DF' : Item(status=' ', wc_rev=3),
2133 'DF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'),
2134 'DF/D1/beta' : Item(status='D ', wc_rev=2),
2135 'F' : Item(status=' ', wc_rev=3),
2136 'F/alpha' : Item(status='D ', wc_rev=2, treeconflict='C'),
2139 deep_trees_status_local_leaf_edit = wc.State('', {
2140 '' : Item(status=' ', wc_rev=3),
2141 'D' : Item(status=' ', wc_rev=3),
2142 'D/D1' : Item(status=' M', wc_rev=2, treeconflict='C'),
2143 'D/D1/delta' : Item(status='A ', wc_rev=0),
2144 'DD' : Item(status=' ', wc_rev=3),
2145 'DD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2146 'DD/D1/D2' : Item(status=' M', wc_rev=2),
2147 'DD/D1/D2/epsilon' : Item(status='A ', wc_rev=0),
2148 'DDD' : Item(status=' ', wc_rev=3),
2149 'DDD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2150 'DDD/D1/D2' : Item(status=' ', wc_rev=2),
2151 'DDD/D1/D2/D3' : Item(status=' M', wc_rev=2),
2152 'DDD/D1/D2/D3/zeta' : Item(status='A ', wc_rev=0),
2153 'DDF' : Item(status=' ', wc_rev=3),
2154 'DDF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2155 'DDF/D1/D2' : Item(status=' ', wc_rev=2),
2156 'DDF/D1/D2/gamma' : Item(status='MM', wc_rev=2),
2157 'DF' : Item(status=' ', wc_rev=3),
2158 'DF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'),
2159 'DF/D1/beta' : Item(status='MM', wc_rev=2),
2160 'F' : Item(status=' ', wc_rev=3),
2161 'F/alpha' : Item(status='MM', wc_rev=2, treeconflict='C'),
2165 class DeepTreesTestCase:
2166 """Describes one tree-conflicts test case.
2167 See deep_trees_run_tests_scheme_for_update(), ..._switch(), ..._merge().
2169 The name field is the subdirectory name in which the test should be run.
2171 The local_action and incoming_action are the functions to run
2172 to construct the local changes and incoming changes, respectively.
2173 See deep_trees_leaf_edit, deep_trees_tree_del, etc.
2175 The expected_* and error_re_string arguments are described in functions
2176 run_and_verify_[update|switch|merge]
2177 except expected_info, which is a dict that has path keys with values
2178 that are dicts as passed to run_and_verify_info():
2179 expected_info = {
2180 'F/alpha' : {
2181 'Revision' : '3',
2182 'Tree conflict' :
2183 '^local delete, incoming edit upon update'
2184 + ' Source left: .file.*/F/alpha@2'
2185 + ' Source right: .file.*/F/alpha@3$',
2187 'DF/D1' : {
2188 'Tree conflict' :
2189 '^local delete, incoming edit upon update'
2190 + ' Source left: .dir.*/DF/D1@2'
2191 + ' Source right: .dir.*/DF/D1@3$',
2196 Note: expected_skip is only used in merge, i.e. using
2197 deep_trees_run_tests_scheme_for_merge.
2200 def __init__(self, name, local_action, incoming_action,
2201 expected_output = None, expected_disk = None,
2202 expected_status = None, expected_skip = None,
2203 error_re_string = None,
2204 commit_block_string = ".*remains in conflict.*",
2205 expected_info = None):
2206 self.name = name
2207 self.local_action = local_action
2208 self.incoming_action = incoming_action
2209 self.expected_output = expected_output
2210 self.expected_disk = expected_disk
2211 self.expected_status = expected_status
2212 self.expected_skip = expected_skip
2213 self.error_re_string = error_re_string
2214 self.commit_block_string = commit_block_string
2215 self.expected_info = expected_info
2219 def deep_trees_run_tests_scheme_for_update(sbox, greater_scheme):
2221 Runs a given list of tests for conflicts occuring at an update operation.
2223 This function wants to save time and perform a number of different
2224 test cases using just a single repository and performing just one commit
2225 for all test cases instead of one for each test case.
2227 1) Each test case is initialized in a separate subdir. Each subdir
2228 again contains one set of "deep_trees", being separate container
2229 dirs for different depths of trees (F, D, DF, DD, DDF, DDD).
2231 2) A commit is performed across all test cases and depths.
2232 (our initial state, -r2)
2234 3) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2235 its *incoming* action is performed (e.g. "deep_trees_leaf_edit"), in
2236 each of the different depth trees (F, D, DF, ... DDD).
2238 4) A commit is performed across all test cases and depths:
2239 our "incoming" state is "stored away in the repository for now",
2240 -r3.
2242 5) All test case dirs and contained deep_trees are time-warped
2243 (updated) back to -r2, the initial state containing deep_trees.
2245 6) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2246 its *local* action is performed (e.g. "deep_trees_leaf_del"), in
2247 each of the different depth trees (F, D, DF, ... DDD).
2249 7) An update to -r3 is performed across all test cases and depths.
2250 This causes tree-conflicts between the "local" state in the working
2251 copy and the "incoming" state from the repository, -r3.
2253 8) A commit is performed in each separate container, to verify
2254 that each tree-conflict indeed blocks a commit.
2256 The sbox parameter is just the sbox passed to a test function. No need
2257 to call sbox.build(), since it is called (once) within this function.
2259 The "table" greater_scheme models all of the different test cases
2260 that should be run using a single repository.
2262 greater_scheme is a list of DeepTreesTestCase items, which define complete
2263 test setups, so that they can be performed as described above.
2266 j = os.path.join
2268 sbox.build()
2269 wc_dir = sbox.wc_dir
2272 # 1) create directories
2274 for test_case in greater_scheme:
2275 try:
2276 add_deep_trees(sbox, test_case.name)
2277 except:
2278 print("ERROR IN: Tests scheme for update: "
2279 + "while setting up deep trees in '%s'" % test_case.name)
2280 raise
2283 # 2) commit initial state
2285 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2288 # 3) apply incoming changes
2290 for test_case in greater_scheme:
2291 try:
2292 test_case.incoming_action(j(sbox.wc_dir, test_case.name))
2293 except:
2294 print("ERROR IN: Tests scheme for update: "
2295 + "while performing incoming action in '%s'" % test_case.name)
2296 raise
2299 # 4) commit incoming changes
2301 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2304 # 5) time-warp back to -r2
2306 main.run_svn(None, 'update', '-r2', wc_dir)
2309 # 6) apply local changes
2311 for test_case in greater_scheme:
2312 try:
2313 test_case.local_action(j(wc_dir, test_case.name))
2314 except:
2315 print("ERROR IN: Tests scheme for update: "
2316 + "while performing local action in '%s'" % test_case.name)
2317 raise
2320 # 7) update to -r3, conflicting with incoming changes.
2321 # A lot of different things are expected.
2322 # Do separate update operations for each test case.
2324 for test_case in greater_scheme:
2325 try:
2326 base = j(wc_dir, test_case.name)
2328 x_out = test_case.expected_output
2329 if x_out != None:
2330 x_out = x_out.copy()
2331 x_out.wc_dir = base
2333 x_disk = test_case.expected_disk
2335 x_status = test_case.expected_status
2336 if x_status != None:
2337 x_status.copy()
2338 x_status.wc_dir = base
2340 run_and_verify_update(base, x_out, x_disk, None,
2341 error_re_string = test_case.error_re_string)
2342 if x_status:
2343 run_and_verify_unquiet_status(base, x_status)
2345 x_info = test_case.expected_info or {}
2346 for path in x_info:
2347 run_and_verify_info([x_info[path]], j(base, path))
2349 except:
2350 print("ERROR IN: Tests scheme for update: "
2351 + "while verifying in '%s'" % test_case.name)
2352 raise
2355 # 8) Verify that commit fails.
2357 for test_case in greater_scheme:
2358 try:
2359 base = j(wc_dir, test_case.name)
2361 x_status = test_case.expected_status
2362 if x_status != None:
2363 x_status.copy()
2364 x_status.wc_dir = base
2366 run_and_verify_commit(base, None, x_status,
2367 test_case.commit_block_string,
2368 base)
2369 except:
2370 print("ERROR IN: Tests scheme for update: "
2371 + "while checking commit-blocking in '%s'" % test_case.name)
2372 raise
2376 def deep_trees_skipping_on_update(sbox, test_case, skip_paths,
2377 chdir_skip_paths):
2379 Create tree conflicts, then update again, expecting the existing tree
2380 conflicts to be skipped.
2381 SKIP_PATHS is a list of paths, relative to the "base dir", for which
2382 "update" on the "base dir" should report as skipped.
2383 CHDIR_SKIP_PATHS is a list of (target-path, skipped-path) pairs for which
2384 an update of "target-path" (relative to the "base dir") should result in
2385 "skipped-path" (relative to "target-path") being reported as skipped.
2388 """FURTHER_ACTION is a function that will make a further modification to
2389 each target, this being the modification that we expect to be skipped. The
2390 function takes the "base dir" (the WC path to the test case directory) as
2391 its only argument."""
2392 further_action = deep_trees_tree_del_repos
2394 j = os.path.join
2395 wc_dir = sbox.wc_dir
2396 base = j(wc_dir, test_case.name)
2398 # Initialize: generate conflicts. (We do not check anything here.)
2399 setup_case = DeepTreesTestCase(test_case.name,
2400 test_case.local_action,
2401 test_case.incoming_action,
2402 None,
2403 None,
2404 None)
2405 deep_trees_run_tests_scheme_for_update(sbox, [setup_case])
2407 # Make a further change to each target in the repository so there is a new
2408 # revision to update to. (This is r4.)
2409 further_action(sbox.repo_url + '/' + test_case.name)
2411 # Update whole working copy, expecting the nodes still in conflict to be
2412 # skipped.
2414 x_out = test_case.expected_output
2415 if x_out != None:
2416 x_out = x_out.copy()
2417 x_out.wc_dir = base
2419 x_disk = test_case.expected_disk
2421 x_status = test_case.expected_status
2422 if x_status != None:
2423 x_status = x_status.copy()
2424 x_status.wc_dir = base
2425 # Account for nodes that were updated by further_action
2426 x_status.tweak('', 'D', 'F', 'DD', 'DF', 'DDD', 'DDF', wc_rev=4)
2428 run_and_verify_update(base, x_out, x_disk, None,
2429 error_re_string = test_case.error_re_string)
2431 run_and_verify_unquiet_status(base, x_status)
2433 # Try to update each in-conflict subtree. Expect a 'Skipped' output for
2434 # each, and the WC status to be unchanged.
2435 for path in skip_paths:
2436 run_and_verify_update(j(base, path),
2437 wc.State(base, {path : Item(verb='Skipped')}),
2438 None, None)
2440 run_and_verify_unquiet_status(base, x_status)
2442 # Try to update each in-conflict subtree. Expect a 'Skipped' output for
2443 # each, and the WC status to be unchanged.
2444 # This time, cd to the subdir before updating it.
2445 was_cwd = os.getcwd()
2446 for path, skipped in chdir_skip_paths:
2447 #print("CHDIR TO: %s" % j(base, path))
2448 os.chdir(j(base, path))
2449 run_and_verify_update('',
2450 wc.State('', {skipped : Item(verb='Skipped')}),
2451 None, None)
2452 os.chdir(was_cwd)
2454 run_and_verify_unquiet_status(base, x_status)
2456 # Verify that commit still fails.
2457 for path, skipped in chdir_skip_paths:
2459 run_and_verify_commit(j(base, path), None, None,
2460 test_case.commit_block_string,
2461 base)
2463 run_and_verify_unquiet_status(base, x_status)
2466 def deep_trees_run_tests_scheme_for_switch(sbox, greater_scheme):
2468 Runs a given list of tests for conflicts occuring at a switch operation.
2470 This function wants to save time and perform a number of different
2471 test cases using just a single repository and performing just one commit
2472 for all test cases instead of one for each test case.
2474 1) Each test case is initialized in a separate subdir. Each subdir
2475 again contains two subdirs: one "local" and one "incoming" for
2476 the switch operation. These contain a set of deep_trees each.
2478 2) A commit is performed across all test cases and depths.
2479 (our initial state, -r2)
2481 3) In each test case subdir's incoming subdir, the
2482 incoming actions are performed.
2484 4) A commit is performed across all test cases and depths. (-r3)
2486 5) In each test case subdir's local subdir, the local actions are
2487 performed. They remain uncommitted in the working copy.
2489 6) In each test case subdir's local dir, a switch is performed to its
2490 corresponding incoming dir.
2491 This causes conflicts between the "local" state in the working
2492 copy and the "incoming" state from the incoming subdir (still -r3).
2494 7) A commit is performed in each separate container, to verify
2495 that each tree-conflict indeed blocks a commit.
2497 The sbox parameter is just the sbox passed to a test function. No need
2498 to call sbox.build(), since it is called (once) within this function.
2500 The "table" greater_scheme models all of the different test cases
2501 that should be run using a single repository.
2503 greater_scheme is a list of DeepTreesTestCase items, which define complete
2504 test setups, so that they can be performed as described above.
2507 j = os.path.join
2509 sbox.build()
2510 wc_dir = sbox.wc_dir
2513 # 1) Create directories.
2515 for test_case in greater_scheme:
2516 try:
2517 base = j(sbox.wc_dir, test_case.name)
2518 os.makedirs(base)
2519 make_deep_trees(j(base, "local"))
2520 make_deep_trees(j(base, "incoming"))
2521 main.run_svn(None, 'add', base)
2522 except:
2523 print("ERROR IN: Tests scheme for switch: "
2524 + "while setting up deep trees in '%s'" % test_case.name)
2525 raise
2528 # 2) Commit initial state (-r2).
2530 main.run_svn(None, 'commit', '-m', 'initial state', wc_dir)
2533 # 3) Apply incoming changes
2535 for test_case in greater_scheme:
2536 try:
2537 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2538 except:
2539 print("ERROR IN: Tests scheme for switch: "
2540 + "while performing incoming action in '%s'" % test_case.name)
2541 raise
2544 # 4) Commit all changes (-r3).
2546 main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir)
2549 # 5) Apply local changes in their according subdirs.
2551 for test_case in greater_scheme:
2552 try:
2553 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
2554 except:
2555 print("ERROR IN: Tests scheme for switch: "
2556 + "while performing local action in '%s'" % test_case.name)
2557 raise
2560 # 6) switch the local dir to the incoming url, conflicting with incoming
2561 # changes. A lot of different things are expected.
2562 # Do separate switch operations for each test case.
2564 for test_case in greater_scheme:
2565 try:
2566 local = j(wc_dir, test_case.name, "local")
2567 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
2569 x_out = test_case.expected_output
2570 if x_out != None:
2571 x_out = x_out.copy()
2572 x_out.wc_dir = local
2574 x_disk = test_case.expected_disk
2576 x_status = test_case.expected_status
2577 if x_status != None:
2578 x_status.copy()
2579 x_status.wc_dir = local
2581 run_and_verify_switch(local, local, incoming, x_out, x_disk, None,
2582 error_re_string = test_case.error_re_string)
2583 run_and_verify_unquiet_status(local, x_status)
2585 x_info = test_case.expected_info or {}
2586 for path in x_info:
2587 run_and_verify_info([x_info[path]], j(local, path))
2588 except:
2589 print("ERROR IN: Tests scheme for switch: "
2590 + "while verifying in '%s'" % test_case.name)
2591 raise
2594 # 7) Verify that commit fails.
2596 for test_case in greater_scheme:
2597 try:
2598 local = j(wc_dir, test_case.name, 'local')
2600 x_status = test_case.expected_status
2601 if x_status != None:
2602 x_status.copy()
2603 x_status.wc_dir = local
2605 run_and_verify_commit(local, None, x_status,
2606 test_case.commit_block_string,
2607 local)
2608 except:
2609 print("ERROR IN: Tests scheme for switch: "
2610 + "while checking commit-blocking in '%s'" % test_case.name)
2611 raise
2614 def deep_trees_run_tests_scheme_for_merge(sbox, greater_scheme,
2615 do_commit_local_changes):
2617 Runs a given list of tests for conflicts occuring at a merge operation.
2619 This function wants to save time and perform a number of different
2620 test cases using just a single repository and performing just one commit
2621 for all test cases instead of one for each test case.
2623 1) Each test case is initialized in a separate subdir. Each subdir
2624 initially contains another subdir, called "incoming", which
2625 contains a set of deep_trees.
2627 2) A commit is performed across all test cases and depths.
2628 (a pre-initial state)
2630 3) In each test case subdir, the "incoming" subdir is copied to "local",
2631 via the `svn copy' command. Each test case's subdir now has two sub-
2632 dirs: "local" and "incoming", initial states for the merge operation.
2634 4) An update is performed across all test cases and depths, so that the
2635 copies made in 3) are pulled into the wc.
2637 5) In each test case's "incoming" subdir, the incoming action is
2638 performed.
2640 6) A commit is performed across all test cases and depths, to commit
2641 the incoming changes.
2642 If do_commit_local_changes is True, this becomes step 7 (swap steps).
2644 7) In each test case's "local" subdir, the local_action is performed.
2645 If do_commit_local_changes is True, this becomes step 6 (swap steps).
2646 Then, in effect, the local changes are committed as well.
2648 8) In each test case subdir, the "incoming" subdir is merged into the
2649 "local" subdir.
2650 This causes conflicts between the "local" state in the working
2651 copy and the "incoming" state from the incoming subdir.
2653 9) A commit is performed in each separate container, to verify
2654 that each tree-conflict indeed blocks a commit.
2656 The sbox parameter is just the sbox passed to a test function. No need
2657 to call sbox.build(), since it is called (once) within this function.
2659 The "table" greater_scheme models all of the different test cases
2660 that should be run using a single repository.
2662 greater_scheme is a list of DeepTreesTestCase items, which define complete
2663 test setups, so that they can be performed as described above.
2666 j = os.path.join
2668 sbox.build()
2669 wc_dir = sbox.wc_dir
2671 # 1) Create directories.
2672 for test_case in greater_scheme:
2673 try:
2674 base = j(sbox.wc_dir, test_case.name)
2675 os.makedirs(base)
2676 make_deep_trees(j(base, "incoming"))
2677 main.run_svn(None, 'add', base)
2678 except:
2679 print("ERROR IN: Tests scheme for merge: "
2680 + "while setting up deep trees in '%s'" % test_case.name)
2681 raise
2684 # 2) Commit pre-initial state (-r2).
2686 main.run_svn(None, 'commit', '-m', 'pre-initial state', wc_dir)
2689 # 3) Copy "incoming" to "local".
2691 for test_case in greater_scheme:
2692 try:
2693 base_url = sbox.repo_url + "/" + test_case.name
2694 incoming_url = base_url + "/incoming"
2695 local_url = base_url + "/local"
2696 main.run_svn(None, 'cp', incoming_url, local_url, '-m',
2697 'copy incoming to local')
2698 except:
2699 print("ERROR IN: Tests scheme for merge: "
2700 + "while copying deep trees in '%s'" % test_case.name)
2701 raise
2703 # 4) Update to load all of the "/local" subdirs into the working copies.
2705 try:
2706 main.run_svn(None, 'up', sbox.wc_dir)
2707 except:
2708 print("ERROR IN: Tests scheme for merge: "
2709 + "while updating local subdirs")
2710 raise
2713 # 5) Perform incoming actions
2715 for test_case in greater_scheme:
2716 try:
2717 test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming"))
2718 except:
2719 print("ERROR IN: Tests scheme for merge: "
2720 + "while performing incoming action in '%s'" % test_case.name)
2721 raise
2724 # 6) or 7) Commit all incoming actions
2726 if not do_commit_local_changes:
2727 try:
2728 main.run_svn(None, 'ci', '-m', 'Committing incoming actions',
2729 sbox.wc_dir)
2730 except:
2731 print("ERROR IN: Tests scheme for merge: "
2732 + "while committing incoming actions")
2733 raise
2736 # 7) or 6) Perform all local actions.
2738 for test_case in greater_scheme:
2739 try:
2740 test_case.local_action(j(sbox.wc_dir, test_case.name, "local"))
2741 except:
2742 print("ERROR IN: Tests scheme for merge: "
2743 + "while performing local action in '%s'" % test_case.name)
2744 raise
2747 # 6) or 7) Commit all incoming actions
2749 if do_commit_local_changes:
2750 try:
2751 main.run_svn(None, 'ci', '-m', 'Committing incoming and local actions',
2752 sbox.wc_dir)
2753 except:
2754 print("ERROR IN: Tests scheme for merge: "
2755 + "while committing incoming and local actions")
2756 raise
2759 # 8) Merge all "incoming" subdirs to their respective "local" subdirs.
2760 # This creates conflicts between the local changes in the "local" wc
2761 # subdirs and the incoming states committed in the "incoming" subdirs.
2763 for test_case in greater_scheme:
2764 try:
2765 local = j(sbox.wc_dir, test_case.name, "local")
2766 incoming = sbox.repo_url + "/" + test_case.name + "/incoming"
2768 x_out = test_case.expected_output
2769 if x_out != None:
2770 x_out = x_out.copy()
2771 x_out.wc_dir = local
2773 x_disk = test_case.expected_disk
2775 x_status = test_case.expected_status
2776 if x_status != None:
2777 x_status.copy()
2778 x_status.wc_dir = local
2780 x_skip = test_case.expected_skip
2781 if x_skip != None:
2782 x_skip.copy()
2783 x_skip.wc_dir = local
2785 run_and_verify_merge(local, None, None, incoming, None,
2786 x_out, None, None, x_disk, None, x_skip,
2787 error_re_string = test_case.error_re_string,
2788 dry_run = False)
2789 run_and_verify_unquiet_status(local, x_status)
2790 except:
2791 print("ERROR IN: Tests scheme for merge: "
2792 + "while verifying in '%s'" % test_case.name)
2793 raise
2796 # 9) Verify that commit fails.
2798 for test_case in greater_scheme:
2799 try:
2800 local = j(wc_dir, test_case.name, 'local')
2802 x_status = test_case.expected_status
2803 if x_status != None:
2804 x_status.copy()
2805 x_status.wc_dir = local
2807 run_and_verify_commit(local, None, x_status,
2808 test_case.commit_block_string,
2809 local)
2810 except:
2811 print("ERROR IN: Tests scheme for merge: "
2812 + "while checking commit-blocking in '%s'" % test_case.name)
2813 raise