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