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