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