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