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 # Copyright (c) 2000-2008 CollabNet. All rights reserved.
10 # This software is licensed as described in the file COPYING, which
11 # you should have received as part of this distribution. The terms
12 # are also available at http://subversion.tigris.org/license-1.html.
13 # If newer versions of this license are posted there, you may use a
14 # newer version instead, at your option.
16 ######################################################################
18 import os
, shutil
, re
, sys
, errno
19 import difflib
, pprint
20 import xml
.parsers
.expat
21 from xml
.dom
.minidom
import parseString
23 import main
, verify
, tree
, wc
24 from svntest
import Failure
26 def no_sleep_for_timestamps():
27 os
.environ
['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'yes'
29 def do_sleep_for_timestamps():
30 os
.environ
['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'no'
32 def setup_pristine_repository():
33 """Create the pristine repository and 'svn import' the greek tree"""
35 # these directories don't exist out of the box, so we may have to create them
36 if not os
.path
.exists(main
.general_wc_dir
):
37 os
.makedirs(main
.general_wc_dir
)
39 if not os
.path
.exists(main
.general_repo_dir
):
40 os
.makedirs(main
.general_repo_dir
) # this also creates all the intermediate dirs
42 # If there's no pristine repos, create one.
43 if not os
.path
.exists(main
.pristine_dir
):
44 main
.create_repos(main
.pristine_dir
)
46 # if this is dav, gives us access rights to import the greek tree.
47 if main
.is_ra_type_dav():
48 authz_file
= os
.path
.join(main
.work_dir
, "authz")
49 main
.file_write(authz_file
, "[/]\n* = rw\n")
51 # dump the greek tree to disk.
52 main
.greek_state
.write_to_disk(main
.greek_dump_dir
)
54 # import the greek tree, using l:foo/p:bar
55 ### todo: svn should not be prompting for auth info when using
56 ### repositories with no auth/auth requirements
57 exit_code
, output
, errput
= main
.run_svn(None, 'import', '-m',
58 'Log message for revision 1.',
62 # check for any errors from the import
64 display_lines("Errors during initial 'svn import':",
65 'STDERR', None, errput
)
68 # verify the printed output of 'svn import'.
69 lastline
= output
.pop().strip()
70 cm
= re
.compile ("(Committed|Imported) revision [0-9]+.")
71 match
= cm
.search (lastline
)
73 print("ERROR: import did not succeed, while creating greek repos.")
74 print("The final line from 'svn import' was:")
77 output_tree
= tree
.build_tree_from_commit(output
)
79 ### due to path normalization in the .old_tree() method, we cannot
80 ### prepend the necessary '.' directory. thus, let's construct an old
81 ### tree manually from the greek_state.
83 for greek_path
in main
.greek_state
.desc
.keys():
84 output_list
.append([ os
.path
.join(main
.greek_dump_dir
, greek_path
),
85 None, {}, {'verb' : 'Adding'}])
86 expected_output_tree
= tree
.build_generic_tree(output_list
)
89 tree
.compare_trees("output", output_tree
, expected_output_tree
)
90 except tree
.SVNTreeUnequal
:
91 verify
.display_trees("ERROR: output of import command is unexpected.",
92 "OUTPUT TREE", expected_output_tree
, output_tree
)
95 # Finally, disallow any changes to the "pristine" repos.
96 error_msg
= "Don't modify the pristine repository"
97 create_failing_hook(main
.pristine_dir
, 'start-commit', error_msg
)
98 create_failing_hook(main
.pristine_dir
, 'pre-lock', error_msg
)
99 create_failing_hook(main
.pristine_dir
, 'pre-revprop-change', error_msg
)
102 ######################################################################
103 # Used by every test, so that they can run independently of one
104 # another. Every time this routine is called, it recursively copies
105 # the `pristine repos' to a new location.
106 # Note: make sure setup_pristine_repository was called once before
107 # using this function.
109 def guarantee_greek_repository(path
):
110 """Guarantee that a local svn repository exists at PATH, containing
111 nothing but the greek-tree at revision 1."""
113 if path
== main
.pristine_dir
:
114 print("ERROR: attempt to overwrite the pristine repos! Aborting.")
117 # copy the pristine repository to PATH.
118 main
.safe_rmtree(path
)
119 if main
.copy_repos(main
.pristine_dir
, path
, 1):
120 print("ERROR: copying repository failed.")
123 # make the repos world-writeable, for mod_dav_svn's sake.
124 main
.chmod_tree(path
, 0666, 0666)
127 def run_and_verify_svnlook(message
, expected_stdout
,
128 expected_stderr
, *varargs
):
129 """Like run_and_verify_svnlook2, but the expected exit code is
130 assumed to be 0 if no output is expected on stderr, and 1 otherwise.
132 Exit code is not checked on platforms without Popen3 - see note in
133 run_and_verify_svn2."""
135 if expected_stderr
is not None and expected_stderr
!= []:
137 return run_and_verify_svnlook2(message
, expected_stdout
, expected_stderr
,
138 expected_exit
, *varargs
)
140 def run_and_verify_svnlook2(message
, expected_stdout
, expected_stderr
,
141 expected_exit
, *varargs
):
142 """Run svnlook command and check its output and exit code.
144 Exit code is not checked on platforms without Popen3 - see note in
145 run_and_verify_svn2."""
146 exit_code
, out
, err
= main
.run_svnlook(*varargs
)
147 verify
.verify_outputs("Unexpected output", out
, err
,
148 expected_stdout
, expected_stderr
)
149 verify
.verify_exit_code(message
, exit_code
, expected_exit
)
150 return exit_code
, out
, err
153 def run_and_verify_svnadmin(message
, expected_stdout
,
154 expected_stderr
, *varargs
):
155 """Like run_and_verify_svnadmin2, but the expected exit code is
156 assumed to be 0 if no output is expected on stderr, and 1 otherwise.
158 Exit code is not checked on platforms without Popen3 - see note in
159 run_and_verify_svn2."""
161 if expected_stderr
is not None and expected_stderr
!= []:
163 return run_and_verify_svnadmin2(message
, expected_stdout
, expected_stderr
,
164 expected_exit
, *varargs
)
166 def run_and_verify_svnadmin2(message
, expected_stdout
, expected_stderr
,
167 expected_exit
, *varargs
):
168 """Run svnadmin command and check its output and exit code.
170 Exit code is not checked on platforms without Popen3 - see note in
171 run_and_verify_svn2."""
172 exit_code
, out
, err
= main
.run_svnadmin(*varargs
)
173 verify
.verify_outputs("Unexpected output", out
, err
,
174 expected_stdout
, expected_stderr
)
175 verify
.verify_exit_code(message
, exit_code
, expected_exit
)
176 return exit_code
, out
, err
179 def run_and_verify_svnversion(message
, wc_dir
, repo_url
,
180 expected_stdout
, expected_stderr
):
181 """like run_and_verify_svnversion2, but the expected exit code is
182 assumed to be 0 if no output is expected on stderr, and 1 otherwise.
184 Exit code is not checked on platforms without Popen3 - see note in
185 run_and_verify_svn2."""
187 if expected_stderr
is not None and expected_stderr
!= []:
189 return run_and_verify_svnversion2(message
, wc_dir
, repo_url
,
190 expected_stdout
, expected_stderr
,
193 def run_and_verify_svnversion2(message
, wc_dir
, repo_url
,
194 expected_stdout
, expected_stderr
,
196 """Run svnversion command and check its output and exit code.
198 Exit code is not checked on platforms without Popen3 - see note in
199 run_and_verify_svn2."""
200 exit_code
, out
, err
= main
.run_svnversion(wc_dir
, repo_url
)
201 verify
.verify_outputs("Unexpected output", out
, err
,
202 expected_stdout
, expected_stderr
)
203 verify
.verify_exit_code(message
, exit_code
, expected_exit
)
204 return exit_code
, out
, err
206 def run_and_verify_svn(message
, expected_stdout
, expected_stderr
, *varargs
):
207 """like run_and_verify_svn2, but the expected exit code is assumed to
208 be 0 if no output is expected on stderr, and 1 otherwise.
210 Exit code is not checked on platforms without Popen3 - see note in
211 run_and_verify_svn2."""
214 if expected_stderr
is not None and expected_stderr
!= []:
216 return run_and_verify_svn2(message
, expected_stdout
, expected_stderr
,
217 expected_exit
, *varargs
)
219 def run_and_verify_svn2(message
, expected_stdout
, expected_stderr
,
220 expected_exit
, *varargs
):
221 """Invokes main.run_svn() with *VARARGS, returns exit code as int, stdout
222 and stderr as lists of lines. For both EXPECTED_STDOUT and EXPECTED_STDERR,
223 create an appropriate instance of verify.ExpectedOutput (if necessary):
225 - If it is an array of strings, create a vanilla ExpectedOutput.
227 - If it is a single string, create a RegexOutput.
229 - If it is already an instance of ExpectedOutput
230 (e.g. UnorderedOutput), leave it alone.
232 ...and invoke compare_and_display_lines() on MESSAGE, a label based
233 on the name of the stream being compared (e.g. STDOUT), the
234 ExpectedOutput instance, and the actual output.
236 If EXPECTED_STDOUT is None, do not check stdout.
237 EXPECTED_STDERR may not be None.
239 If output checks pass, on supported platforms (namely those with the Popen3
240 class), the expected and actual codes are compared. On platforms lacking
241 Popen3, the actual exit code is unavailable and a value of None is returned
242 as the exit code from this and all other run_...() functions.
244 If a comparison fails, a Failure will be raised."""
246 if expected_stderr
is None:
247 raise verify
.SVNIncorrectDatatype("expected_stderr must not be None")
250 if expected_stderr
is not None and expected_stderr
!= []:
253 exit_code
, out
, err
= main
.run_svn(want_err
, *varargs
)
254 verify
.verify_outputs(message
, out
, err
, expected_stdout
, expected_stderr
)
255 verify
.verify_exit_code(message
, exit_code
, expected_exit
)
256 return exit_code
, out
, err
258 def run_and_verify_svn_match_any(message
, expected_stdout
, expected_stderr
,
260 """Like run_and_verify_svn_match_any2, but the expected exit code is
261 assumed to be 0 if no output is expected on stderr, and 1 otherwise.
263 Exit code is not checked on platforms without Popen3 - see note in
264 run_and_verify_svn2."""
266 if expected_stderr
is not None and expected_stderr
!= []:
268 return run_and_verify_svn_match_any2(message
, expected_stdout
,
269 expected_stderr
, expected_exit
,
273 def run_and_verify_svn_match_any2(message
, expected_stdout
, expected_stderr
,
274 expected_exit
, *varargs
):
275 """Like run_and_verify_svn2, except that only one stdout line must match
278 Exit code is not checked on platforms without Popen3 - see note in
279 run_and_verify_svn2."""
281 if expected_stderr
is None:
282 raise verify
.SVNIncorrectDatatype("expected_stderr must not be None")
285 if expected_stderr
is not None and expected_stderr
!= []:
288 exit_code
, out
, err
= main
.run_svn(want_err
, *varargs
)
289 verify
.verify_outputs(message
, out
, err
, expected_stdout
, expected_stderr
,
291 verify
.verify_exit_code(message
, exit_code
, expected_exit
)
292 return exit_code
, out
, err
295 def run_and_verify_load(repo_dir
, dump_file_content
):
296 "Runs 'svnadmin load' and reports any errors."
298 exit_code
, output
, errput
= main
.run_command_stdin(
299 main
.svnadmin_binary
, expected_stderr
, 1, dump_file_content
,
300 'load', '--force-uuid', '--quiet', repo_dir
)
302 verify
.verify_outputs("Unexpected stderr output", None, errput
,
303 None, expected_stderr
)
306 def run_and_verify_dump(repo_dir
):
307 "Runs 'svnadmin dump' and reports any errors, returning the dump content."
308 exit_code
, output
, errput
= main
.run_svnadmin('dump', repo_dir
)
309 verify
.verify_outputs("Missing expected output(s)", output
, errput
,
310 verify
.AnyOutput
, verify
.AnyOutput
)
314 def load_repo(sbox
, dumpfile_path
= None, dump_str
= None):
315 "Loads the dumpfile into sbox"
317 dump_str
= main
.file_read(dumpfile_path
, "rb")
319 # Create a virgin repos and working copy
320 main
.safe_rmtree(sbox
.repo_dir
, 1)
321 main
.safe_rmtree(sbox
.wc_dir
, 1)
322 main
.create_repos(sbox
.repo_dir
)
324 # Load the mergetracking dumpfile into the repos, and check it out the repo
325 run_and_verify_load(sbox
.repo_dir
, dump_str
)
326 run_and_verify_svn(None, None, [], "co", sbox
.repo_url
, sbox
.wc_dir
)
331 ######################################################################
334 # These are all routines that invoke 'svn' in particular ways, and
335 # then verify the results by comparing expected trees with actual
338 # For all the functions below, the OUTPUT_TREE and DISK_TREE args need
339 # to be created by feeding carefully constructed lists to
340 # tree.build_generic_tree(). A STATUS_TREE can be built by
341 # hand, or by editing the tree returned by get_virginal_state().
344 def run_and_verify_checkout(URL
, wc_dir_name
, output_tree
, disk_tree
,
345 singleton_handler_a
= None,
347 singleton_handler_b
= None,
350 """Checkout the URL into a new directory WC_DIR_NAME. *ARGS are any
351 extra optional args to the checkout subcommand.
353 The subcommand output will be verified against OUTPUT_TREE,
354 and the working copy itself will be verified against DISK_TREE.
355 For the latter comparison, SINGLETON_HANDLER_A and
356 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
357 function's doc string for more details. Return if successful, raise
360 WC_DIR_NAME is deleted if present unless the '--force' option is passed
363 if isinstance(output_tree
, wc
.State
):
364 output_tree
= output_tree
.old_tree()
365 if isinstance(disk_tree
, wc
.State
):
366 disk_tree
= disk_tree
.old_tree()
368 # Remove dir if it's already there, unless this is a forced checkout.
369 # In that case assume we want to test a forced checkout's toleration
370 # of obstructing paths.
377 main
.safe_rmtree(wc_dir_name
)
379 # Checkout and make a tree of the output, using l:foo/p:bar
380 ### todo: svn should not be prompting for auth info when using
381 ### repositories with no auth/auth requirements
382 exit_code
, output
, errput
= main
.run_svn(None, 'co',
383 URL
, wc_dir_name
, *args
)
384 actual
= tree
.build_tree_from_checkout (output
)
386 # Verify actual output against expected output.
388 tree
.compare_trees ("output", actual
, output_tree
)
389 except tree
.SVNTreeUnequal
:
390 print("ACTUAL OUTPUT TREE:")
391 tree
.dump_tree_script(actual
, wc_dir_name
+ os
.sep
)
394 # Create a tree by scanning the working copy
395 actual
= tree
.build_tree_from_wc (wc_dir_name
)
397 # Verify expected disk against actual disk.
399 tree
.compare_trees ("disk", actual
, disk_tree
,
400 singleton_handler_a
, a_baton
,
401 singleton_handler_b
, b_baton
)
402 except tree
.SVNTreeUnequal
:
403 print("ACTUAL DISK TREE:")
404 tree
.dump_tree_script(actual
, wc_dir_name
+ os
.sep
)
408 def run_and_verify_export(URL
, export_dir_name
, output_tree
, disk_tree
,
409 singleton_handler_a
= None,
411 singleton_handler_b
= None,
414 """Export the URL into a new directory WC_DIR_NAME.
416 The subcommand output will be verified against OUTPUT_TREE,
417 and the exported copy itself will be verified against DISK_TREE.
418 For the latter comparison, SINGLETON_HANDLER_A and
419 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
420 function's doc string for more details. Return if successful, raise
423 if isinstance(output_tree
, wc
.State
):
424 output_tree
= output_tree
.old_tree()
425 if isinstance(disk_tree
, wc
.State
):
426 disk_tree
= disk_tree
.old_tree()
428 # Export and make a tree of the output, using l:foo/p:bar
429 ### todo: svn should not be prompting for auth info when using
430 ### repositories with no auth/auth requirements
431 exit_code
, output
, errput
= main
.run_svn(None, 'export',
432 URL
, export_dir_name
, *args
)
433 actual
= tree
.build_tree_from_checkout (output
)
435 # Verify actual output against expected output.
437 tree
.compare_trees ("output", actual
, output_tree
)
438 except tree
.SVNTreeUnequal
:
439 print("ACTUAL OUTPUT TREE:")
440 tree
.dump_tree_script(actual
, export_dir_name
+ os
.sep
)
443 # Create a tree by scanning the working copy. Don't ignore
444 # the .svn directories so that we generate an error if they
446 actual
= tree
.build_tree_from_wc (export_dir_name
, ignore_svn
=False)
448 # Verify expected disk against actual disk.
450 tree
.compare_trees ("disk", actual
, disk_tree
,
451 singleton_handler_a
, a_baton
,
452 singleton_handler_b
, b_baton
)
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
, wc_dir_name
,
609 output_tree
, disk_tree
, status_tree
,
610 singleton_handler_a
, a_baton
,
611 singleton_handler_b
, b_baton
,
613 """Verify update of WC_DIR_NAME.
615 The subcommand output (found in ACTUAL_OUTPUT) will be verified
616 against OUTPUT_TREE (if provided), the working copy itself will be
617 verified against DISK_TREE (if provided), and the working copy's
618 'svn status' output will be verified against STATUS_TREE (if
619 provided). (This is a good way to check that revision numbers were
622 Return if successful, raise on failure.
624 For the comparison with DISK_TREE, pass SINGLETON_HANDLER_A and
625 SINGLETON_HANDLER_B to tree.compare_trees -- see that function's doc
626 string for more details. If CHECK_PROPS is set, then disk
627 comparison will examine props."""
629 # Verify actual output against expected output.
632 tree
.compare_trees ("output", actual_output
, output_tree
)
633 except tree
.SVNTreeUnequal
:
634 print("ACTUAL OUTPUT TREE:")
635 tree
.dump_tree_script(actual_output
, wc_dir_name
+ os
.sep
)
638 # Create a tree by scanning the working copy, and verify it
640 actual_disk
= tree
.build_tree_from_wc (wc_dir_name
, check_props
)
642 tree
.compare_trees ("disk", actual_disk
, disk_tree
,
643 singleton_handler_a
, a_baton
,
644 singleton_handler_b
, b_baton
)
645 except tree
.SVNTreeUnequal
:
646 print("ACTUAL DISK TREE:")
647 tree
.dump_tree_script(actual_disk
)
650 # Verify via 'status' command too, if possible.
652 run_and_verify_status(wc_dir_name
, status_tree
)
655 def verify_disk(wc_dir_name
,
657 singleton_handler_a
= None,
659 singleton_handler_b
= None,
661 check_props
= False):
663 """Verify WC_DIR_NAME against DISK_TREE. SINGLETON_HANDLER_A,
664 A_BATON, SINGLETON_HANDLER_B, and B_BATON will be passed to
665 tree.compare_trees, which see for details. If CHECK_PROPS is set,
666 the comparison will examin props. Returns if successful, raises on
668 if isinstance(disk_tree
, wc
.State
):
669 disk_tree
= disk_tree
.old_tree()
670 verify_update (None, wc_dir_name
, None, disk_tree
, None,
671 singleton_handler_a
, a_baton
,
672 singleton_handler_b
, b_baton
,
677 def run_and_verify_update(wc_dir_name
,
678 output_tree
, disk_tree
, status_tree
,
679 error_re_string
= None,
680 singleton_handler_a
= None,
682 singleton_handler_b
= None,
687 """Update WC_DIR_NAME. *ARGS are any extra optional args to the
688 update subcommand. NOTE: If *ARGS is specified at all, explicit
689 target paths must be passed in *ARGS as well (or a default `.' will
690 be chosen by the 'svn' binary). This allows the caller to update
691 many items in a single working copy dir, but still verify the entire
694 If ERROR_RE_STRING, the update must exit with error, and the error
695 message must match regular expression ERROR_RE_STRING.
697 Else if ERROR_RE_STRING is None, then:
699 If OUTPUT_TREE is not None, the subcommand output will be verified
700 against OUTPUT_TREE. If DISK_TREE is not None, the working copy
701 itself will be verified against DISK_TREE. If STATUS_TREE is not
702 None, the 'svn status' output will be verified against STATUS_TREE.
703 (This is a good way to check that revision numbers were bumped.)
705 For the DISK_TREE verification, SINGLETON_HANDLER_A and
706 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
707 function's doc string for more details.
709 If CHECK_PROPS is set, then disk comparison will examine props.
711 Return if successful, raise on failure."""
713 if isinstance(output_tree
, wc
.State
):
714 output_tree
= output_tree
.old_tree()
715 if isinstance(disk_tree
, wc
.State
):
716 disk_tree
= disk_tree
.old_tree()
717 if isinstance(status_tree
, wc
.State
):
718 status_tree
= status_tree
.old_tree()
720 # Update and make a tree of the output.
722 exit_code
, output
, errput
= main
.run_svn(error_re_string
, 'up', *args
)
724 exit_code
, output
, errput
= main
.run_svn(error_re_string
,
728 if (error_re_string
):
729 rm
= re
.compile(error_re_string
)
731 match
= rm
.search(line
)
734 raise main
.SVNUnmatchedError
736 actual
= tree
.build_tree_from_checkout (output
)
737 verify_update (actual
, wc_dir_name
,
738 output_tree
, disk_tree
, status_tree
,
739 singleton_handler_a
, a_baton
,
740 singleton_handler_b
, b_baton
,
744 def run_and_parse_info(*args
):
745 """Run 'svn info' and parse its output into a list of dicts,
746 one dict per target."""
751 # per-target variables
755 exit_code
, output
, errput
= main
.run_svn(None, 'info', *args
)
758 line
= line
[:-1] # trim '\n'
760 # separator line between items
761 all_infos
.append(iter_info
)
764 elif line
[0].isspace():
765 # continuation line (for tree conflicts)
766 iter_info
[prev_key
] += line
[1:]
769 key
, value
= line
.split(':', 1)
771 iter_info
[key
] = value
[1:]
773 # it's a "Tree conflict:\n" line; value is in continuation lines
779 def run_and_verify_info(expected_infos
, *args
):
780 """Run 'svn info' with the arguments in *ARGS and verify the results
781 against expected_infos. The latter should be a list of dicts (in the
782 same order as the targets).
784 In the dicts, each key is the before-the-colon part of the 'svn info' output,
785 and each value is either None (meaning that the key should *not* appear in
786 the 'svn info' output) or a regex matching the output value. Output lines
787 not matching a key in the dict are ignored.
789 Return if successful, raise on failure."""
791 actual_infos
= run_and_parse_info(*args
)
794 for actual
, expected
in zip(actual_infos
, expected_infos
):
796 for key
, value
in expected
.items():
797 if value
is None and key
in actual
:
798 raise main
.SVNLineUnequal("Found unexpected key '%s' with value '%s'"
799 % (key
, actual
[key
]))
800 if value
is not None and key
not in actual
:
801 raise main
.SVNLineUnequal("Expected key '%s' (with value '%s') "
802 "not found" % (key
, value
))
803 if value
is not None and not re
.search(value
, actual
[key
]):
804 raise verify
.SVNUnexpectedStdout("Values of key '%s' don't match:\n"
807 % (key
, value
, actual
[key
]))
810 sys
.stderr
.write("Bad 'svn info' output:\n"
813 % (actual_infos
, expected_infos
))
816 def run_and_verify_merge(dir, rev1
, rev2
, url
,
817 output_tree
, disk_tree
, status_tree
, skip_tree
,
818 error_re_string
= None,
819 singleton_handler_a
= None,
821 singleton_handler_b
= None,
826 """Run 'svn merge -rREV1:REV2 URL DIR', leaving off the '-r'
827 argument if both REV1 and REV2 are None."""
829 run_and_verify_merge2(dir, rev1
, rev2
, url
, None, output_tree
, disk_tree
,
830 status_tree
, skip_tree
, error_re_string
,
831 singleton_handler_a
, a_baton
, singleton_handler_b
,
832 b_baton
, check_props
, dry_run
, *args
)
834 run_and_verify_merge2(dir, rev1
, rev2
, url
, None, output_tree
, disk_tree
,
835 status_tree
, skip_tree
, error_re_string
,
836 singleton_handler_a
, a_baton
, singleton_handler_b
,
837 b_baton
, check_props
, dry_run
)
840 def run_and_verify_merge2(dir, rev1
, rev2
, url1
, url2
,
841 output_tree
, disk_tree
, status_tree
, skip_tree
,
842 error_re_string
= None,
843 singleton_handler_a
= None,
845 singleton_handler_b
= None,
850 """Run 'svn merge URL1@REV1 URL2@REV2 DIR' if URL2 is not None
851 (for a three-way merge between URLs and WC).
853 If URL2 is None, run 'svn merge -rREV1:REV2 URL1 DIR'. If both REV1
854 and REV2 are None, leave off the '-r' argument.
856 If ERROR_RE_STRING, the merge must exit with error, and the error
857 message must match regular expression ERROR_RE_STRING.
859 Else if ERROR_RE_STRING is None, then:
861 The subcommand output will be verified against OUTPUT_TREE, and the
862 working copy itself will be verified against DISK_TREE. If optional
863 STATUS_TREE is given, then 'svn status' output will be compared.
864 The 'skipped' merge output will be compared to SKIP_TREE.
866 For the DISK_TREE verification, SINGLETON_HANDLER_A and
867 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
868 function's doc string for more details.
870 If CHECK_PROPS is set, then disk comparison will examine props.
872 If DRY_RUN is set then a --dry-run merge will be carried out first and
873 the output compared with that of the full merge.
875 Return if successful, raise on failure."""
877 if isinstance(output_tree
, wc
.State
):
878 output_tree
= output_tree
.old_tree()
879 if isinstance(disk_tree
, wc
.State
):
880 disk_tree
= disk_tree
.old_tree()
881 if isinstance(status_tree
, wc
.State
):
882 status_tree
= status_tree
.old_tree()
883 if isinstance(skip_tree
, wc
.State
):
884 skip_tree
= skip_tree
.old_tree()
886 merge_command
= [ "merge" ]
888 merge_command
.extend((url1
+ "@" + str(rev1
), url2
+ "@" + str(rev2
)))
890 if not (rev1
is None and rev2
is None):
891 merge_command
.append("-r" + str(rev1
) + ":" + str(rev2
))
892 merge_command
.append(url1
)
893 merge_command
.append(dir)
894 merge_command
= tuple(merge_command
)
897 pre_disk
= tree
.build_tree_from_wc(dir)
898 dry_run_command
= merge_command
+ ('--dry-run',)
899 dry_run_command
= dry_run_command
+ args
900 exit_code
, out_dry
, err_dry
= main
.run_svn(error_re_string
,
902 post_disk
= tree
.build_tree_from_wc(dir)
904 tree
.compare_trees("disk", post_disk
, pre_disk
)
905 except tree
.SVNTreeError
:
906 print("=============================================================")
907 print("Dry-run merge altered working copy")
908 print("=============================================================")
912 # Update and make a tree of the output.
913 merge_command
= merge_command
+ args
914 exit_code
, out
, err
= main
.run_svn(error_re_string
, *merge_command
)
917 if not error_re_string
.startswith(".*"):
918 error_re_string
= ".*(" + error_re_string
+ ")"
919 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
920 verify
.verify_outputs(None, None, err
, None, expected_err
)
923 raise verify
.SVNUnexpectedStderr(err
)
925 if dry_run
and out
!= out_dry
:
926 print("=============================================================")
927 print("Merge outputs differ")
928 print("The dry-run merge output:")
931 print("The full merge output:")
934 print("=============================================================")
935 raise main
.SVNUnmatchedError
937 def missing_skip(a
, b
):
938 print("=============================================================")
939 print("Merge failed to skip: " + a
.path
)
940 print("=============================================================")
942 def extra_skip(a
, b
):
943 print("=============================================================")
944 print("Merge unexpectedly skipped: " + a
.path
)
945 print("=============================================================")
948 myskiptree
= tree
.build_tree_from_skipped(out
)
950 tree
.compare_trees("skip", myskiptree
, skip_tree
,
951 extra_skip
, None, missing_skip
, None)
952 except tree
.SVNTreeUnequal
:
953 print("ACTUAL SKIP TREE:")
954 tree
.dump_tree_script(myskiptree
, dir + os
.sep
)
957 actual
= tree
.build_tree_from_checkout(out
, 0)
958 verify_update (actual
, dir,
959 output_tree
, disk_tree
, status_tree
,
960 singleton_handler_a
, a_baton
,
961 singleton_handler_b
, b_baton
,
965 def run_and_verify_mergeinfo(error_re_string
= None,
966 expected_output
= [],
968 """Run 'svn mergeinfo ARGS', and compare the result against
969 EXPECTED_OUTPUT, a list of revisions expected in the output.
970 Raise an exception if an unexpected output is encountered."""
972 mergeinfo_command
= ["mergeinfo"]
973 mergeinfo_command
.extend(args
)
974 exit_code
, out
, err
= main
.run_svn(error_re_string
, *mergeinfo_command
)
977 if not error_re_string
.startswith(".*"):
978 error_re_string
= ".*(" + error_re_string
+ ")"
979 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
980 verify
.verify_outputs(None, None, err
, None, expected_err
)
983 out
= [_f
for _f
in [int(x
.rstrip()[1:]) for x
in out
] if _f
]
985 expected_output
.sort()
988 if out
!= expected_output
:
989 exp_hash
= dict.fromkeys(expected_output
)
994 extra_out
.append(rev
)
995 extra_exp
= list(exp_hash
.keys())
996 raise Exception("Unexpected 'svn mergeinfo' output:\n"
997 " expected but not found: %s\n"
998 " found but not expected: %s"
999 % (', '.join([str(x
) for x
in extra_exp
]),
1000 ', '.join([str(x
) for x
in extra_out
])))
1003 def run_and_verify_switch(wc_dir_name
,
1006 output_tree
, disk_tree
, status_tree
,
1007 error_re_string
= None,
1008 singleton_handler_a
= None,
1010 singleton_handler_b
= None,
1012 check_props
= False,
1015 """Switch WC_TARGET (in working copy dir WC_DIR_NAME) to SWITCH_URL.
1017 If ERROR_RE_STRING, the switch must exit with error, and the error
1018 message must match regular expression ERROR_RE_STRING.
1020 Else if ERROR_RE_STRING is None, then:
1022 The subcommand output will be verified against OUTPUT_TREE, and the
1023 working copy itself will be verified against DISK_TREE. If optional
1024 STATUS_TREE is given, then 'svn status' output will be
1025 compared. (This is a good way to check that revision numbers were
1028 For the DISK_TREE verification, SINGLETON_HANDLER_A and
1029 SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that
1030 function's doc string for more details.
1032 If CHECK_PROPS is set, then disk comparison will examine props.
1034 Return if successful, raise on failure."""
1036 if isinstance(output_tree
, wc
.State
):
1037 output_tree
= output_tree
.old_tree()
1038 if isinstance(disk_tree
, wc
.State
):
1039 disk_tree
= disk_tree
.old_tree()
1040 if isinstance(status_tree
, wc
.State
):
1041 status_tree
= status_tree
.old_tree()
1043 # Update and make a tree of the output.
1044 exit_code
, output
, errput
= main
.run_svn(error_re_string
, 'switch',
1045 switch_url
, wc_target
, *args
)
1048 if not error_re_string
.startswith(".*"):
1049 error_re_string
= ".*(" + error_re_string
+ ")"
1050 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
1051 verify
.verify_outputs(None, None, errput
, None, expected_err
)
1054 raise verify
.SVNUnexpectedStderr(err
)
1056 actual
= tree
.build_tree_from_checkout (output
)
1058 verify_update (actual
, wc_dir_name
,
1059 output_tree
, disk_tree
, status_tree
,
1060 singleton_handler_a
, a_baton
,
1061 singleton_handler_b
, b_baton
,
1065 def run_and_verify_commit(wc_dir_name
, output_tree
, status_tree
,
1066 error_re_string
= None,
1068 """Commit and verify results within working copy WC_DIR_NAME,
1069 sending ARGS to the commit subcommand.
1071 The subcommand output will be verified against OUTPUT_TREE. If
1072 optional STATUS_TREE is given, then 'svn status' output will
1073 be compared. (This is a good way to check that revision numbers
1076 If ERROR_RE_STRING is None, the commit must not exit with error. If
1077 ERROR_RE_STRING is a string, the commit must exit with error, and
1078 the error message must match regular expression ERROR_RE_STRING.
1080 Return if successful, raise on failure."""
1082 if isinstance(output_tree
, wc
.State
):
1083 output_tree
= output_tree
.old_tree()
1084 if isinstance(status_tree
, wc
.State
):
1085 status_tree
= status_tree
.old_tree()
1088 exit_code
, output
, errput
= main
.run_svn(error_re_string
, 'ci',
1093 if not error_re_string
.startswith(".*"):
1094 error_re_string
= ".*(" + error_re_string
+ ")"
1095 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
1096 verify
.verify_outputs(None, None, errput
, None, expected_err
)
1099 # Else not expecting error:
1101 # Remove the final output line, and verify that the commit succeeded.
1104 lastline
= output
.pop().strip()
1106 cm
= re
.compile("(Committed|Imported) revision [0-9]+.")
1107 match
= cm
.search(lastline
)
1109 print("ERROR: commit did not succeed.")
1110 print("The final line from 'svn ci' was:")
1112 raise main
.SVNCommitFailure
1114 # The new 'final' line in the output is either a regular line that
1115 # mentions {Adding, Deleting, Sending, ...}, or it could be a line
1116 # that says "Transmitting file data ...". If the latter case, we
1117 # want to remove the line from the output; it should be ignored when
1120 lastline
= output
.pop()
1122 tm
= re
.compile("Transmitting file data.+")
1123 match
= tm
.search(lastline
)
1125 # whoops, it was important output, put it back.
1126 output
.append(lastline
)
1128 # Convert the output into a tree.
1129 actual
= tree
.build_tree_from_commit (output
)
1131 # Verify actual output against expected output.
1133 tree
.compare_trees ("output", actual
, output_tree
)
1134 except tree
.SVNTreeError
:
1135 verify
.display_trees("Output of commit is unexpected",
1136 "OUTPUT TREE", output_tree
, actual
)
1137 print("ACTUAL OUTPUT TREE:")
1138 tree
.dump_tree_script(actual
, wc_dir_name
+ os
.sep
)
1141 # Verify via 'status' command too, if possible.
1143 run_and_verify_status(wc_dir_name
, status_tree
)
1146 # This function always passes '-q' to the status command, which
1147 # suppresses the printing of any unversioned or nonexistent items.
1148 def run_and_verify_status(wc_dir_name
, output_tree
,
1149 singleton_handler_a
= None,
1151 singleton_handler_b
= None,
1153 """Run 'status' on WC_DIR_NAME and compare it with the
1154 expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1155 be passed to tree.compare_trees - see that function's doc string for
1157 Returns on success, raises on failure."""
1159 if isinstance(output_tree
, wc
.State
):
1160 output_tree
= output_tree
.old_tree()
1162 exit_code
, output
, errput
= main
.run_svn(None, 'status', '-v', '-u', '-q',
1165 actual
= tree
.build_tree_from_status (output
)
1167 # Verify actual output against expected output.
1169 tree
.compare_trees ("status", actual
, output_tree
,
1170 singleton_handler_a
, a_baton
,
1171 singleton_handler_b
, b_baton
)
1172 except tree
.SVNTreeError
:
1173 verify
.display_trees(None, 'STATUS OUTPUT TREE', output_tree
, actual
)
1174 print("ACTUAL STATUS TREE:")
1175 tree
.dump_tree_script(actual
, wc_dir_name
+ os
.sep
)
1179 # A variant of previous func, but doesn't pass '-q'. This allows us
1180 # to verify unversioned or nonexistent items in the list.
1181 def run_and_verify_unquiet_status(wc_dir_name
, status_tree
,
1182 singleton_handler_a
= None,
1184 singleton_handler_b
= None,
1186 """Run 'status' on WC_DIR_NAME and compare it with the
1187 expected STATUS_TREE SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
1188 be passed to tree.compare_trees - see that function's doc string for
1190 Returns on success, raises on failure."""
1192 if isinstance(status_tree
, wc
.State
):
1193 status_tree
= status_tree
.old_tree()
1195 exit_code
, output
, errput
= main
.run_svn(None, 'status', '-v',
1198 actual
= tree
.build_tree_from_status (output
)
1200 # Verify actual output against expected output.
1202 tree
.compare_trees ("UNQUIET STATUS", actual
, status_tree
,
1203 singleton_handler_a
, a_baton
,
1204 singleton_handler_b
, b_baton
)
1205 except tree
.SVNTreeError
:
1206 print("ACTUAL UNQUIET STATUS TREE:")
1207 tree
.dump_tree_script(actual
, wc_dir_name
+ os
.sep
)
1210 def run_and_verify_diff_summarize_xml(error_re_string
= [],
1211 expected_prefix
= None,
1212 expected_paths
= [],
1213 expected_items
= [],
1214 expected_props
= [],
1215 expected_kinds
= [],
1217 """Run 'diff --summarize --xml' with the arguments *ARGS, which should
1218 contain all arguments beyond for your 'diff --summarize --xml' omitting
1219 said arguments. EXPECTED_PREFIX will store a "common" path prefix
1220 expected to be at the beginning of each summarized path. If
1221 EXPECTED_PREFIX is None, then EXPECTED_PATHS will need to be exactly
1222 as 'svn diff --summarize --xml' will output. If ERROR_RE_STRING, the
1223 command must exit with error, and the error message must match regular
1224 expression ERROR_RE_STRING.
1226 Else if ERROR_RE_STRING is None, the subcommand output will be parsed
1227 into an XML document and will then be verified by comparing the parsed
1228 output to the contents in the EXPECTED_PATHS, EXPECTED_ITEMS,
1229 EXPECTED_PROPS and EXPECTED_KINDS. Returns on success, raises
1232 exit_code
, output
, errput
= run_and_verify_svn(None, None, error_re_string
,
1233 'diff', '--summarize',
1237 # Return if errors are present since they were expected
1241 doc
= parseString(''.join(output
))
1242 paths
= doc
.getElementsByTagName("path")
1243 items
= expected_items
1244 kinds
= expected_kinds
1247 modified_path
= path
.childNodes
[0].data
1249 if (expected_prefix
is not None
1250 and modified_path
.find(expected_prefix
) == 0):
1251 modified_path
= modified_path
.replace(expected_prefix
, '')[1:].strip()
1253 # Workaround single-object diff
1254 if len(modified_path
) == 0:
1255 modified_path
= path
.childNodes
[0].data
.split(os
.sep
)[-1]
1257 # From here on, we use '/' as path separator.
1259 modified_path
= modified_path
.replace(os
.sep
, "/")
1261 if modified_path
not in expected_paths
:
1262 print("ERROR: %s not expected in the changed paths." % modified_path
)
1265 index
= expected_paths
.index(modified_path
)
1266 expected_item
= items
[index
]
1267 expected_kind
= kinds
[index
]
1268 expected_prop
= expected_props
[index
]
1269 actual_item
= path
.getAttribute('item')
1270 actual_kind
= path
.getAttribute('kind')
1271 actual_prop
= path
.getAttribute('props')
1273 if expected_item
!= actual_item
:
1274 print("ERROR: expected: %s actual: %s" % (expected_item
, actual_item
))
1277 if expected_kind
!= actual_kind
:
1278 print("ERROR: expected: %s actual: %s" % (expected_kind
, actual_kind
))
1281 if expected_prop
!= actual_prop
:
1282 print("ERROR: expected: %s actual: %s" % (expected_prop
, actual_prop
))
1285 def run_and_verify_diff_summarize(output_tree
, error_re_string
= None,
1286 singleton_handler_a
= None,
1288 singleton_handler_b
= None,
1291 """Run 'diff --summarize' with the arguments *ARGS.
1292 If ERROR_RE_STRING, the command must exit with error, and the error
1293 message must match regular expression ERROR_RE_STRING.
1295 Else if ERROR_RE_STRING is None, the subcommand output will be
1296 verified against OUTPUT_TREE. SINGLETON_HANDLER_A and
1297 SINGLETON_HANDLER_B will be passed to tree.compare_trees - see that
1298 function's doc string for more details. Returns on success, raises
1301 if isinstance(output_tree
, wc
.State
):
1302 output_tree
= output_tree
.old_tree()
1304 exit_code
, output
, errput
= main
.run_svn(None, 'diff', '--summarize',
1308 if not error_re_string
.startswith(".*"):
1309 error_re_string
= ".*(" + error_re_string
+ ")"
1310 expected_err
= verify
.RegexOutput(error_re_string
, match_all
=False)
1311 verify
.verify_outputs(None, None, errput
, None, expected_err
)
1314 actual
= tree
.build_tree_from_diff_summarize (output
)
1316 # Verify actual output against expected output.
1318 tree
.compare_trees ("output", actual
, output_tree
,
1319 singleton_handler_a
, a_baton
,
1320 singleton_handler_b
, b_baton
)
1321 except tree
.SVNTreeError
:
1322 verify
.display_trees(None, 'DIFF OUTPUT TREE', output_tree
, actual
)
1323 print("ACTUAL DIFF OUTPUT TREE:")
1324 tree
.dump_tree_script(actual
)
1327 def run_and_validate_lock(path
, username
):
1328 """`svn lock' the given path and validate the contents of the lock.
1329 Use the given username. This is important because locks are
1332 comment
= "Locking path:%s." % path
1335 run_and_verify_svn(None, ".*locked by user", [], 'lock',
1336 '--username', username
,
1337 '-m', comment
, path
)
1339 # Run info and check that we get the lock fields.
1340 exit_code
, output
, err
= run_and_verify_svn(None, None, [],
1344 ### TODO: Leverage RegexOuput([...], match_all=True) here.
1345 # prepare the regexs to compare against
1346 token_re
= re
.compile (".*?Lock Token: opaquelocktoken:.*?", re
.DOTALL
)
1347 author_re
= re
.compile (".*?Lock Owner: %s\n.*?" % username
, re
.DOTALL
)
1348 created_re
= re
.compile (".*?Lock Created:.*?", re
.DOTALL
)
1349 comment_re
= re
.compile (".*?%s\n.*?" % re
.escape(comment
), re
.DOTALL
)
1350 # join all output lines into one
1351 output
= "".join(output
)
1352 # Fail even if one regex does not match
1353 if ( not (token_re
.match(output
) and \
1354 author_re
.match(output
) and \
1355 created_re
.match(output
) and \
1356 comment_re
.match(output
))):
1359 ######################################################################
1360 # Other general utilities
1363 # This allows a test to *quickly* bootstrap itself.
1364 def make_repo_and_wc(sbox
, create_wc
= True, read_only
= False):
1365 """Create a fresh repository and checkout a wc from it.
1367 If read_only is False, a dedicated repository will be created, named
1368 TEST_NAME. The repository will live in the global dir 'general_repo_dir'.
1369 If read_only is True the pristine repository will be used.
1371 If create_wc is True, a dedicated working copy will be checked out from
1372 the repository, named TEST_NAME. The wc directory will live in the global
1373 dir 'general_wc_dir'.
1375 Both variables 'general_repo_dir' and 'general_wc_dir' are defined at the
1376 top of this test suite.) Returns on success, raises on failure."""
1378 # Create (or copy afresh) a new repos with a greek tree in it.
1380 guarantee_greek_repository(sbox
.repo_dir
)
1383 # Generate the expected output tree.
1384 expected_output
= main
.greek_state
.copy()
1385 expected_output
.wc_dir
= sbox
.wc_dir
1386 expected_output
.tweak(status
='A ', contents
=None)
1388 # Generate an expected wc tree.
1389 expected_wc
= main
.greek_state
1391 # Do a checkout, and verify the resulting output and disk contents.
1392 run_and_verify_checkout(sbox
.repo_url
,
1397 # just make sure the parent folder of our working copy is created
1399 os
.mkdir(main
.general_wc_dir
)
1400 except OSError, err
:
1401 if err
.errno
!= errno
.EEXIST
:
1404 # Duplicate a working copy or other dir.
1405 def duplicate_dir(wc_name
, wc_copy_name
):
1406 """Copy the working copy WC_NAME to WC_COPY_NAME. Overwrite any
1407 existing tree at that location."""
1409 main
.safe_rmtree(wc_copy_name
)
1410 shutil
.copytree(wc_name
, wc_copy_name
)
1414 def get_virginal_state(wc_dir
, rev
):
1415 "Return a virginal greek tree state for a WC and repos at revision REV."
1417 rev
= str(rev
) ### maybe switch rev to an integer?
1419 # copy the greek tree, shift it to the new wc_dir, insert a root elem,
1420 # then tweak all values
1421 state
= main
.greek_state
.copy()
1422 state
.wc_dir
= wc_dir
1423 state
.desc
[''] = wc
.StateItem()
1424 state
.tweak(contents
=None, status
=' ', wc_rev
=rev
)
1428 def remove_admin_tmp_dir(wc_dir
):
1429 "Remove the tmp directory within the administrative directory."
1431 tmp_path
= os
.path
.join(wc_dir
, main
.get_admin_name(), 'tmp')
1432 ### Any reason not to use main.safe_rmtree()?
1433 os
.rmdir(os
.path
.join(tmp_path
, 'prop-base'))
1434 os
.rmdir(os
.path
.join(tmp_path
, 'props'))
1435 os
.rmdir(os
.path
.join(tmp_path
, 'text-base'))
1438 # Cheap administrative directory locking
1439 def lock_admin_dir(wc_dir
):
1440 "Lock a SVN administrative directory"
1442 path
= os
.path
.join(wc_dir
, main
.get_admin_name(), 'lock')
1443 main
.file_append(path
, "stop looking!")
1445 def get_wc_uuid(wc_dir
):
1446 "Return the UUID of the working copy at WC_DIR."
1447 return run_and_parse_info(wc_dir
)[0]['Repository UUID']
1449 def create_failing_hook(repo_dir
, hook_name
, text
):
1450 """Create a HOOK_NAME hook in REPO_DIR that prints TEXT to stderr and exits
1453 hook_path
= os
.path
.join(repo_dir
, 'hooks', hook_name
)
1454 main
.create_python_hook_script(hook_path
, 'import sys;\n'
1455 'sys.stderr.write("""%%s hook failed: %%s""" %% (%s, %s));\n'
1456 'sys.exit(1);\n' % (repr(hook_name
), repr(text
)))
1458 def enable_revprop_changes(repo_dir
):
1459 """Enable revprop changes in a repository REPOS_DIR by creating a
1460 pre-revprop-change hook script and (if appropriate) making it executable."""
1462 hook_path
= main
.get_pre_revprop_change_hook_path (repo_dir
)
1463 main
.create_python_hook_script (hook_path
, 'import sys; sys.exit(0)')
1465 def disable_revprop_changes(repo_dir
):
1466 """Disable revprop changes in a repository REPO_DIR by creating a
1467 pre-revprop-change hook script like enable_revprop_changes, except that
1468 the hook prints "pre-revprop-change" followed by sys.argv"""
1469 hook_path
= main
.get_pre_revprop_change_hook_path (repo_dir
)
1470 main
.create_python_hook_script (hook_path
,
1472 'sys.stderr.write("pre-revprop-change %s %s %s %s %s" % (sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]))\n'
1475 def create_failing_post_commit_hook(repo_dir
):
1476 """Disable commits in a repository REPOS_DIR by creating a post-commit hook
1477 script which always reports errors."""
1479 hook_path
= main
.get_post_commit_hook_path (repo_dir
)
1480 main
.create_python_hook_script (hook_path
, 'import sys; '
1481 'sys.stderr.write("Post-commit hook failed"); '
1484 # set_prop can be used for binary properties are values like '*' which are not
1485 # handled correctly when specified on the command line.
1486 def set_prop(expected_err
, name
, value
, path
, valp
):
1487 """Set a property with value from a file"""
1488 valf
= open(valp
, 'wb')
1493 main
.run_svn(expected_err
, 'propset', '-F', valp
, name
, path
)
1495 def check_prop(name
, path
, exp_out
):
1496 """Verify that property NAME on PATH has a value of EXP_OUT"""
1497 # Not using run_svn because binary_mode must be set
1498 exit_code
, out
, err
= main
.run_command(main
.svn_binary
, None, 1, 'pg',
1499 '--strict', name
, path
,
1501 main
.default_config_dir
,
1502 '--username', main
.wc_author
,
1503 '--password', main
.wc_passwd
)
1505 print("svn pg --strict %s output does not match expected." % name
)
1506 print("Expected standard output: %s\n" % exp_out
)
1507 print("Actual standard output: %s\n" % out
)
1510 def fill_file_with_lines(wc_path
, line_nbr
, line_descrip
=None,
1512 """Change the file at WC_PATH (adding some lines), and return its
1513 new contents. LINE_NBR indicates the line number at which the new
1514 contents should assume that it's being appended. LINE_DESCRIP is
1515 something like 'This is line' (the default) or 'Conflicting line'."""
1517 if line_descrip
is None:
1518 line_descrip
= "This is line"
1520 # Generate the new contents for the file.
1522 for n
in range(line_nbr
, line_nbr
+ 3):
1523 contents
= contents
+ line_descrip
+ " " + repr(n
) + " in '" + \
1524 os
.path
.basename(wc_path
) + "'.\n"
1526 # Write the new contents to the file.
1528 main
.file_append(wc_path
, contents
)
1530 main
.file_write(wc_path
, contents
)
1534 def inject_conflict_into_wc(sbox
, state_path
, file_path
,
1535 expected_disk
, expected_status
, merged_rev
):
1536 """Create a conflict at FILE_PATH by replacing its contents,
1537 committing the change, backdating it to its previous revision,
1538 changing its contents again, then updating it to merge in the
1541 wc_dir
= sbox
.wc_dir
1543 # Make a change to the file.
1544 contents
= fill_file_with_lines(file_path
, 1, "This is line", append
=False)
1546 # Commit the changed file, first taking note of the current revision.
1547 prev_rev
= expected_status
.desc
[state_path
].wc_rev
1548 expected_output
= wc
.State(wc_dir
, {
1549 state_path
: wc
.StateItem(verb
='Sending'),
1552 expected_status
.tweak(state_path
, wc_rev
=merged_rev
)
1553 run_and_verify_commit(wc_dir
, expected_output
, expected_status
,
1556 # Backdate the file.
1557 exit_code
, output
, errput
= main
.run_svn(None, "up", "-r", str(prev_rev
),
1560 expected_status
.tweak(state_path
, wc_rev
=prev_rev
)
1562 # Make a conflicting change to the file, and backdate the file.
1563 conflicting_contents
= fill_file_with_lines(file_path
, 1, "Conflicting line",
1566 # Merge the previous change into the file to produce a conflict.
1568 expected_disk
.tweak(state_path
, contents
="")
1569 expected_output
= wc
.State(wc_dir
, {
1570 state_path
: wc
.StateItem(status
='C '),
1572 inject_conflict_into_expected_state(state_path
,
1573 expected_disk
, expected_status
,
1574 conflicting_contents
, contents
,
1576 exit_code
, output
, errput
= main
.run_svn(None, "up", "-r", str(merged_rev
),
1577 sbox
.repo_url
+ "/" + state_path
,
1580 expected_status
.tweak(state_path
, wc_rev
=merged_rev
)
1582 def inject_conflict_into_expected_state(state_path
,
1583 expected_disk
, expected_status
,
1584 wc_text
, merged_text
, merged_rev
):
1585 """Update the EXPECTED_DISK and EXPECTED_STATUS trees for the
1586 conflict at STATE_PATH (ignored if None). WC_TEXT, MERGED_TEXT, and
1587 MERGED_REV are used to determine the contents of the conflict (the
1588 text parameters should be newline-terminated)."""
1590 conflict_marker
= make_conflict_marker_text(wc_text
, merged_text
,
1592 existing_text
= expected_disk
.desc
[state_path
].contents
or ""
1593 expected_disk
.tweak(state_path
, contents
=existing_text
+ conflict_marker
)
1596 expected_status
.tweak(state_path
, status
='C ')
1598 def make_conflict_marker_text(wc_text
, merged_text
, merged_rev
):
1599 """Return the conflict marker text described by WC_TEXT (the current
1600 text in the working copy, MERGED_TEXT (the conflicting text merged
1601 in), and MERGED_REV (the revision from whence the conflicting text
1603 return "<<<<<<< .working\n" + wc_text
+ "=======\n" + \
1604 merged_text
+ ">>>>>>> .merge-right.r" + str(merged_rev
) + "\n"
1607 def build_greek_tree_conflicts(sbox
):
1608 """Create a working copy that has tree-conflict markings.
1609 After this function has been called, sbox.wc_dir is a working
1610 copy that has specific tree-conflict markings.
1612 In particular, this does two conflicting sets of edits and performs an
1613 update so that tree conflicts appear.
1615 Note that this function calls sbox.build() because it needs a clean sbox.
1616 So, there is no need to call sbox.build() before this.
1618 The conflicts are the result of an 'update' on the following changes:
1622 A/D/G/pi text-mod del
1623 A/D/G/rho del text-mod
1626 This function is useful for testing that tree-conflicts are handled
1627 properly once they have appeared, e.g. that commits are blocked, that the
1628 info output is correct, etc.
1630 See also the tree-conflicts tests using deep_trees in various other
1631 .py files, and tree_conflict_tests.py.
1635 wc_dir
= sbox
.wc_dir
1637 G
= j(wc_dir
, 'A', 'D', 'G')
1642 # Make incoming changes and "store them away" with a commit.
1643 main
.file_append(pi
, "Incoming edit.\n")
1644 main
.run_svn(None, 'del', rho
)
1645 main
.run_svn(None, 'del', tau
)
1647 expected_output
= wc
.State(wc_dir
, {
1648 'A/D/G/pi' : Item(verb
='Sending'),
1649 'A/D/G/rho' : Item(verb
='Deleting'),
1650 'A/D/G/tau' : Item(verb
='Deleting'),
1652 expected_status
= get_virginal_state(wc_dir
, 1)
1653 expected_status
.tweak('A/D/G/pi', wc_rev
='2')
1654 expected_status
.remove('A/D/G/rho', 'A/D/G/tau')
1655 run_and_verify_commit(wc_dir
, expected_output
, expected_status
, None,
1656 '-m', 'Incoming changes.', wc_dir
)
1658 # Update back to the pristine state ("time-warp").
1659 expected_output
= wc
.State(wc_dir
, {
1660 'A/D/G/pi' : Item(status
='U '),
1661 'A/D/G/rho' : Item(status
='A '),
1662 'A/D/G/tau' : Item(status
='A '),
1664 expected_disk
= main
.greek_state
1665 expected_status
= get_virginal_state(wc_dir
, 1)
1666 run_and_verify_update(wc_dir
, expected_output
, expected_disk
,
1667 expected_status
, None, None, None, None, None, False,
1670 # Make local changes
1671 main
.run_svn(None, 'del', pi
)
1672 main
.file_append(rho
, "Local edit.\n")
1673 main
.run_svn(None, 'del', tau
)
1675 # Update, receiving the incoming changes on top of the local changes,
1676 # causing tree conflicts. Don't check for any particular result: that is
1677 # the job of other tests.
1678 run_and_verify_svn(None, verify
.AnyOutput
, [], 'update', wc_dir
)
1681 def make_deep_trees(base
):
1682 """Helper function for deep trees conflicts. Create a set of trees,
1683 each in its own "container" dir. Any conflicts can be tested separately
1687 # Create the container dirs.
1692 DDF
= j(base
, 'DDF')
1693 DDD
= j(base
, 'DDD')
1695 os
.makedirs(j(D
, 'D1'))
1696 os
.makedirs(j(DF
, 'D1'))
1697 os
.makedirs(j(DD
, 'D1', 'D2'))
1698 os
.makedirs(j(DDF
, 'D1', 'D2'))
1699 os
.makedirs(j(DDD
, 'D1', 'D2', 'D3'))
1701 # Create their files.
1702 alpha
= j(F
, 'alpha')
1703 beta
= j(DF
, 'D1', 'beta')
1704 gamma
= j(DDF
, 'D1', 'D2', 'gamma')
1705 main
.file_append(alpha
, "This is the file 'alpha'.\n")
1706 main
.file_append(beta
, "This is the file 'beta'.\n")
1707 main
.file_append(gamma
, "This is the file 'gamma'.\n")
1710 def add_deep_trees(sbox
, base_dir_name
):
1711 """Prepare a "deep_trees" within a given directory.
1713 The directory <sbox.wc_dir>/<base_dir_name> is created and a deep_tree
1714 is created within. The items are only added, a commit has to be
1715 called separately, if needed.
1717 <base_dir_name> will thus be a container for the set of containers
1718 mentioned in make_deep_trees().
1721 base
= j(sbox
.wc_dir
, base_dir_name
)
1722 make_deep_trees(base
)
1723 main
.run_svn(None, 'add', base
)
1728 # initial deep trees state
1729 deep_trees_virginal_state
= wc
.State('', {
1731 'F/alpha' : Item("This is the file 'alpha'.\n"),
1736 'DF/D1/beta' : Item("This is the file 'beta'.\n"),
1739 'DD/D1/D2' : Item(),
1742 'DDF/D1/D2' : Item(),
1743 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\n"),
1746 'DDD/D1/D2' : Item(),
1747 'DDD/D1/D2/D3' : Item(),
1751 # Many actions on deep trees and their resulting states...
1753 def deep_trees_leaf_edit(base
):
1754 """Helper function for deep trees test cases. Append text to files,
1755 create new files in empty directories, and change leaf node properties."""
1757 F
= j(base
, 'F', 'alpha')
1758 DF
= j(base
, 'DF', 'D1', 'beta')
1759 DDF
= j(base
, 'DDF', 'D1', 'D2', 'gamma')
1760 main
.file_append(F
, "More text for file alpha.\n")
1761 main
.file_append(DF
, "More text for file beta.\n")
1762 main
.file_append(DDF
, "More text for file gamma.\n")
1763 run_and_verify_svn(None, verify
.AnyOutput
, [],
1764 'propset', 'prop1', '1', F
, DF
, DDF
)
1766 D
= j(base
, 'D', 'D1')
1767 DD
= j(base
, 'DD', 'D1', 'D2')
1768 DDD
= j(base
, 'DDD', 'D1', 'D2', 'D3')
1769 run_and_verify_svn(None, verify
.AnyOutput
, [],
1770 'propset', 'prop1', '1', D
, DD
, DDD
)
1771 D
= j(base
, 'D', 'D1', 'delta')
1772 DD
= j(base
, 'DD', 'D1', 'D2', 'epsilon')
1773 DDD
= j(base
, 'DDD', 'D1', 'D2', 'D3', 'zeta')
1774 main
.file_append(D
, "This is the file 'delta'.\n")
1775 main
.file_append(DD
, "This is the file 'epsilon'.\n")
1776 main
.file_append(DDD
, "This is the file 'zeta'.\n")
1777 run_and_verify_svn(None, verify
.AnyOutput
, [],
1780 # deep trees state after a call to deep_trees_leaf_edit
1781 deep_trees_after_leaf_edit
= wc
.State('', {
1783 'F/alpha' : Item("This is the file 'alpha'.\nMore text for file alpha.\n"),
1786 'D/D1/delta' : Item("This is the file 'delta'.\n"),
1789 'DF/D1/beta' : Item("This is the file 'beta'.\nMore text for file beta.\n"),
1792 'DD/D1/D2' : Item(),
1793 'DD/D1/D2/epsilon' : Item("This is the file 'epsilon'.\n"),
1796 'DDF/D1/D2' : Item(),
1797 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\nMore text for file gamma.\n"),
1800 'DDD/D1/D2' : Item(),
1801 'DDD/D1/D2/D3' : Item(),
1802 'DDD/D1/D2/D3/zeta' : Item("This is the file 'zeta'.\n"),
1806 def deep_trees_leaf_del(base
):
1807 """Helper function for deep trees test cases. Delete files and empty
1810 F
= j(base
, 'F', 'alpha')
1811 D
= j(base
, 'D', 'D1')
1812 DF
= j(base
, 'DF', 'D1', 'beta')
1813 DD
= j(base
, 'DD', 'D1', 'D2')
1814 DDF
= j(base
, 'DDF', 'D1', 'D2', 'gamma')
1815 DDD
= j(base
, 'DDD', 'D1', 'D2', 'D3')
1816 main
.run_svn(None, 'rm', F
, D
, DF
, DD
, DDF
, DDD
)
1818 # deep trees state after a call to deep_trees_leaf_del
1819 deep_trees_after_leaf_del
= wc
.State('', {
1828 'DDF/D1/D2' : Item(),
1831 'DDD/D1/D2' : Item(),
1835 def deep_trees_tree_del(base
):
1836 """Helper function for deep trees test cases. Delete top-level dirs."""
1838 F
= j(base
, 'F', 'alpha')
1839 D
= j(base
, 'D', 'D1')
1840 DF
= j(base
, 'DF', 'D1')
1841 DD
= j(base
, 'DD', 'D1')
1842 DDF
= j(base
, 'DDF', 'D1')
1843 DDD
= j(base
, 'DDD', 'D1')
1844 main
.run_svn(None, 'rm', F
, D
, DF
, DD
, DDF
, DDD
)
1846 # deep trees state after a call to deep_trees_tree_del
1847 deep_trees_after_tree_del
= wc
.State('', {
1856 # deep trees state without any files
1857 deep_trees_empty_dirs
= wc
.State('', {
1865 'DD/D1/D2' : Item(),
1868 'DDF/D1/D2' : Item(),
1871 'DDD/D1/D2' : Item(),
1872 'DDD/D1/D2/D3' : Item(),
1875 # Expected merge/update/switch output.
1877 deep_trees_conflict_output
= wc
.State('', {
1878 'F/alpha' : Item(status
=' ', treeconflict
='C'),
1879 'D/D1' : Item(status
=' ', treeconflict
='C'),
1880 'DF/D1' : Item(status
=' ', treeconflict
='C'),
1881 'DD/D1' : Item(status
=' ', treeconflict
='C'),
1882 'DDF/D1' : Item(status
=' ', treeconflict
='C'),
1883 'DDD/D1' : Item(status
=' ', treeconflict
='C'),
1886 deep_trees_conflict_output_skipped
= wc
.State('', {
1887 'D/D1' : Item(verb
='Skipped'),
1888 'F/alpha' : Item(verb
='Skipped'),
1889 'DD/D1' : Item(verb
='Skipped'),
1890 'DF/D1' : Item(verb
='Skipped'),
1891 'DDD/D1' : Item(verb
='Skipped'),
1892 'DDF/D1' : Item(verb
='Skipped'),
1895 # Expected status output after merge/update/switch.
1897 deep_trees_status_local_tree_del
= wc
.State('', {
1898 '' : Item(status
=' ', wc_rev
=3),
1899 'D' : Item(status
=' ', wc_rev
=3),
1900 'D/D1' : Item(status
='D ', wc_rev
=2, treeconflict
='C'),
1901 'DD' : Item(status
=' ', wc_rev
=3),
1902 'DD/D1' : Item(status
='D ', wc_rev
=2, treeconflict
='C'),
1903 'DD/D1/D2' : Item(status
='D ', wc_rev
=2),
1904 'DDD' : Item(status
=' ', wc_rev
=3),
1905 'DDD/D1' : Item(status
='D ', wc_rev
=2, treeconflict
='C'),
1906 'DDD/D1/D2' : Item(status
='D ', wc_rev
=2),
1907 'DDD/D1/D2/D3' : Item(status
='D ', wc_rev
=2),
1908 'DDF' : Item(status
=' ', wc_rev
=3),
1909 'DDF/D1' : Item(status
='D ', wc_rev
=2, treeconflict
='C'),
1910 'DDF/D1/D2' : Item(status
='D ', wc_rev
=2),
1911 'DDF/D1/D2/gamma' : Item(status
='D ', wc_rev
=2),
1912 'DF' : Item(status
=' ', wc_rev
=3),
1913 'DF/D1' : Item(status
='D ', wc_rev
=2, treeconflict
='C'),
1914 'DF/D1/beta' : Item(status
='D ', wc_rev
=2),
1915 'F' : Item(status
=' ', wc_rev
=3),
1916 'F/alpha' : Item(status
='D ', wc_rev
=2, treeconflict
='C'),
1919 deep_trees_status_local_leaf_edit
= wc
.State('', {
1920 '' : Item(status
=' ', wc_rev
=3),
1921 'D' : Item(status
=' ', wc_rev
=3),
1922 'D/D1' : Item(status
=' M', wc_rev
=2, treeconflict
='C'),
1923 'D/D1/delta' : Item(status
='A ', wc_rev
=0),
1924 'DD' : Item(status
=' ', wc_rev
=3),
1925 'DD/D1' : Item(status
=' ', wc_rev
=2, treeconflict
='C'),
1926 'DD/D1/D2' : Item(status
=' M', wc_rev
=2),
1927 'DD/D1/D2/epsilon' : Item(status
='A ', wc_rev
=0),
1928 'DDD' : Item(status
=' ', wc_rev
=3),
1929 'DDD/D1' : Item(status
=' ', wc_rev
=2, treeconflict
='C'),
1930 'DDD/D1/D2' : Item(status
=' ', wc_rev
=2),
1931 'DDD/D1/D2/D3' : Item(status
=' M', wc_rev
=2),
1932 'DDD/D1/D2/D3/zeta' : Item(status
='A ', wc_rev
=0),
1933 'DDF' : Item(status
=' ', wc_rev
=3),
1934 'DDF/D1' : Item(status
=' ', wc_rev
=2, treeconflict
='C'),
1935 'DDF/D1/D2' : Item(status
=' ', wc_rev
=2),
1936 'DDF/D1/D2/gamma' : Item(status
='MM', wc_rev
=2),
1937 'DF' : Item(status
=' ', wc_rev
=3),
1938 'DF/D1' : Item(status
=' ', wc_rev
=2, treeconflict
='C'),
1939 'DF/D1/beta' : Item(status
='MM', wc_rev
=2),
1940 'F' : Item(status
=' ', wc_rev
=3),
1941 'F/alpha' : Item(status
='MM', wc_rev
=2, treeconflict
='C'),
1945 class DeepTreesTestCase
:
1946 """Describes one tree-conflicts test case.
1947 See deep_trees_run_tests_scheme_for_update(), ..._switch(), ..._merge().
1949 The name field is the subdirectory name in which the test should be run.
1951 The local_action and incoming_action are the functions to run
1952 to construct the local changes and incoming changes, respectively.
1953 See deep_trees_leaf_edit, deep_trees_tree_del, etc.
1955 The expected_* and error_re_string arguments are described in functions
1956 run_and_verify_[update|switch|merge]
1958 Note: expected_skip is only used in merge, i.e. using
1959 deep_trees_run_tests_scheme_for_merge.
1962 def __init__(self
, name
, local_action
, incoming_action
,
1963 expected_output
= None, expected_disk
= None,
1964 expected_status
= None, expected_skip
= None,
1965 error_re_string
= None):
1967 self
.local_action
= local_action
1968 self
.incoming_action
= incoming_action
1969 self
.expected_output
= expected_output
1970 self
.expected_disk
= expected_disk
1971 self
.expected_status
= expected_status
1972 self
.expected_skip
= expected_skip
1973 self
.error_re_string
= error_re_string
1977 def deep_trees_run_tests_scheme_for_update(sbox
, greater_scheme
):
1979 Runs a given list of tests for conflicts occuring at an update operation.
1981 This function wants to save time and perform a number of different
1982 test cases using just a single repository and performing just one commit
1983 for all test cases instead of one for each test case.
1985 1) Each test case is initialized in a separate subdir. Each subdir
1986 again contains one set of "deep_trees", being separate container
1987 dirs for different depths of trees (F, D, DF, DD, DDF, DDD).
1989 2) A commit is performed across all test cases and depths.
1990 (our initial state, -r2)
1992 3) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
1993 its *incoming* action is performed (e.g. "deep_trees_leaf_edit"), in
1994 each of the different depth trees (F, D, DF, ... DDD).
1996 4) A commit is performed across all test cases and depths:
1997 our "incoming" state is "stored away in the repository for now",
2000 5) All test case dirs and contained deep_trees are time-warped
2001 (updated) back to -r2, the initial state containing deep_trees.
2003 6) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"),
2004 its *local* action is performed (e.g. "deep_trees_leaf_del"), in
2005 each of the different depth trees (F, D, DF, ... DDD).
2007 7) An update to -r3 is performed across all test cases and depths.
2008 This causes tree-conflicts between the "local" state in the working
2009 copy and the "incoming" state from the repository, -r3.
2011 The sbox parameter is just the sbox passed to a test function. No need
2012 to call sbox.build(), since it is called (once) within this function.
2014 The "table" greater_scheme models all of the different test cases
2015 that should be run using a single repository.
2017 greater_scheme is a list of DeepTreesTestCase items, which define complete
2018 test setups, so that they can be performed as described above.
2024 wc_dir
= sbox
.wc_dir
2027 # 1) create directories
2029 for test_case
in greater_scheme
:
2031 add_deep_trees(sbox
, test_case
.name
)
2033 print("ERROR IN: Tests scheme for update: "
2034 + "while setting up deep trees in '%s'" % test_case
.name
)
2038 # 2) commit initial state
2040 main
.run_svn(None, 'commit', '-m', 'initial state', wc_dir
)
2043 # 3) apply incoming changes
2045 for test_case
in greater_scheme
:
2047 test_case
.incoming_action(j(sbox
.wc_dir
, test_case
.name
))
2049 print("ERROR IN: Tests scheme for update: "
2050 + "while performing incoming action in '%s'" % test_case
.name
)
2054 # 4) commit incoming changes
2056 main
.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir
)
2059 # 5) time-warp back to -r2
2061 main
.run_svn(None, 'update', '-r2', wc_dir
)
2064 # 6) apply local changes
2066 for test_case
in greater_scheme
:
2068 test_case
.local_action(j(wc_dir
, test_case
.name
))
2070 print("ERROR IN: Tests scheme for update: "
2071 + "while performing local action in '%s'" % test_case
.name
)
2075 # 7) update to -r3, conflicting with incoming changes.
2076 # A lot of different things are expected.
2077 # Do separate update operations for each test case.
2079 for test_case
in greater_scheme
:
2081 base
= j(wc_dir
, test_case
.name
)
2083 x_out
= test_case
.expected_output
2085 x_out
= x_out
.copy()
2088 x_disk
= test_case
.expected_disk
2090 x_status
= test_case
.expected_status
2091 if x_status
!= None:
2093 x_status
.wc_dir
= base
2095 run_and_verify_update(base
, x_out
, x_disk
, None,
2096 error_re_string
= test_case
.error_re_string
)
2098 run_and_verify_unquiet_status(base
, x_status
)
2100 print("ERROR IN: Tests scheme for update: "
2101 + "while verifying in '%s'" % test_case
.name
)
2106 def deep_trees_skipping_on_update(sbox
, test_case
, skip_paths
,
2109 Create tree conflicts, then update again, expecting the existing tree
2110 conflicts to be skipped.
2114 wc_dir
= sbox
.wc_dir
2116 # Initialize silently.
2117 setup_case
= DeepTreesTestCase(test_case
.name
,
2118 test_case
.local_action
,
2119 test_case
.incoming_action
,
2123 deep_trees_run_tests_scheme_for_update(sbox
, [setup_case
])
2125 # Update whole working copy again.
2126 base
= j(wc_dir
, test_case
.name
)
2128 x_out
= test_case
.expected_output
2130 x_out
= x_out
.copy()
2133 x_disk
= test_case
.expected_disk
2135 x_status
= test_case
.expected_status
2136 if x_status
!= None:
2138 x_status
.wc_dir
= base
2140 run_and_verify_update(base
, x_out
, x_disk
, None,
2141 error_re_string
= test_case
.error_re_string
)
2143 run_and_verify_unquiet_status(base
, x_status
)
2145 # Update subtrees, expecting a single 'Skipped' output for each one.
2146 for path
in skip_paths
:
2147 run_and_verify_update(j(base
, path
),
2148 wc
.State(base
, {path
: Item(verb
='Skipped')}),
2151 # Update subtrees, expecting a single 'Skipped' output for each one.
2152 # This time, cd to the subdir before .
2153 was_cwd
= os
.getcwd()
2154 for path
, skipped
in chdir_skip_paths
:
2155 #print("CHDIR TO: %s" % j(base, path))
2156 os
.chdir(j(base
, path
))
2157 run_and_verify_update('',
2158 wc
.State('', {skipped
: Item(verb
='Skipped')}),
2162 run_and_verify_unquiet_status(base
, x_status
)
2165 def deep_trees_run_tests_scheme_for_switch(sbox
, greater_scheme
):
2167 Runs a given list of tests for conflicts occuring at a switch operation.
2169 This function wants to save time and perform a number of different
2170 test cases using just a single repository and performing just one commit
2171 for all test cases instead of one for each test case.
2173 1) Each test case is initialized in a separate subdir. Each subdir
2174 again contains two subdirs: one "local" and one "incoming" for
2175 the switch operation. These contain a set of deep_trees each.
2177 2) A commit is performed across all test cases and depths.
2178 (our initial state, -r2)
2180 3) In each test case subdir's incoming subdir, the
2181 incoming actions are performed.
2183 4) A commit is performed across all test cases and depths. (-r3)
2185 5) In each test case subdir's local subdir, the local actions are
2186 performed. They remain uncommitted in the working copy.
2188 6) In each test case subdir's local dir, a switch is performed to its
2189 corresponding incoming dir.
2190 This causes conflicts between the "local" state in the working
2191 copy and the "incoming" state from the incoming subdir (still -r3).
2193 The sbox parameter is just the sbox passed to a test function. No need
2194 to call sbox.build(), since it is called (once) within this function.
2196 The "table" greater_scheme models all of the different test cases
2197 that should be run using a single repository.
2199 greater_scheme is a list of DeepTreesTestCase items, which define complete
2200 test setups, so that they can be performed as described above.
2206 wc_dir
= sbox
.wc_dir
2209 # 1) Create directories.
2211 for test_case
in greater_scheme
:
2213 base
= j(sbox
.wc_dir
, test_case
.name
)
2215 make_deep_trees(j(base
, "local"))
2216 make_deep_trees(j(base
, "incoming"))
2217 main
.run_svn(None, 'add', base
)
2219 print("ERROR IN: Tests scheme for switch: "
2220 + "while setting up deep trees in '%s'" % test_case
.name
)
2224 # 2) Commit initial state (-r2).
2226 main
.run_svn(None, 'commit', '-m', 'initial state', wc_dir
)
2229 # 3) Apply incoming changes
2231 for test_case
in greater_scheme
:
2233 test_case
.incoming_action(j(sbox
.wc_dir
, test_case
.name
, "incoming"))
2235 print("ERROR IN: Tests scheme for switch: "
2236 + "while performing incoming action in '%s'" % test_case
.name
)
2240 # 4) Commit all changes (-r3).
2242 main
.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir
)
2245 # 5) Apply local changes in their according subdirs.
2247 for test_case
in greater_scheme
:
2249 test_case
.local_action(j(sbox
.wc_dir
, test_case
.name
, "local"))
2251 print("ERROR IN: Tests scheme for switch: "
2252 + "while performing local action in '%s'" % test_case
.name
)
2256 # 6) switch the local dir to the incoming url, conflicting with incoming
2257 # changes. A lot of different things are expected.
2258 # Do separate switch operations for each test case.
2260 for test_case
in greater_scheme
:
2262 local
= j(wc_dir
, test_case
.name
, "local")
2263 incoming
= sbox
.repo_url
+ "/" + test_case
.name
+ "/incoming"
2265 x_out
= test_case
.expected_output
2267 x_out
= x_out
.copy()
2268 x_out
.wc_dir
= local
2270 x_disk
= test_case
.expected_disk
2272 x_status
= test_case
.expected_status
2273 if x_status
!= None:
2275 x_status
.wc_dir
= local
2277 run_and_verify_switch(local
, local
, incoming
, x_out
, x_disk
, None,
2278 error_re_string
= test_case
.error_re_string
)
2279 run_and_verify_unquiet_status(local
, x_status
)
2281 print("ERROR IN: Tests scheme for switch: "
2282 + "while verifying in '%s'" % test_case
.name
)
2286 def deep_trees_run_tests_scheme_for_merge(sbox
, greater_scheme
,
2287 do_commit_local_changes
):
2289 Runs a given list of tests for conflicts occuring at a merge operation.
2291 This function wants to save time and perform a number of different
2292 test cases using just a single repository and performing just one commit
2293 for all test cases instead of one for each test case.
2295 1) Each test case is initialized in a separate subdir. Each subdir
2296 initially contains another subdir, called "incoming", which
2297 contains a set of deep_trees.
2299 2) A commit is performed across all test cases and depths.
2300 (a pre-initial state)
2302 3) In each test case subdir, the "incoming" subdir is copied to "local",
2303 via the `svn copy' command. Each test case's subdir now has two sub-
2304 dirs: "local" and "incoming", initial states for the merge operation.
2306 4) An update is performed across all test cases and depths, so that the
2307 copies made in 3) are pulled into the wc.
2309 5) In each test case's "incoming" subdir, the incoming action is
2312 6) A commit is performed across all test cases and depths, to commit
2313 the incoming changes.
2314 If do_commit_local_changes is True, this becomes step 7 (swap steps).
2316 7) In each test case's "local" subdir, the local_action is performed.
2317 If do_commit_local_changes is True, this becomes step 6 (swap steps).
2318 Then, in effect, the local changes are committed as well.
2320 8) In each test case subdir, the "incoming" subdir is merged into the
2322 This causes conflicts between the "local" state in the working
2323 copy and the "incoming" state from the incoming subdir.
2325 The sbox parameter is just the sbox passed to a test function. No need
2326 to call sbox.build(), since it is called (once) within this function.
2328 The "table" greater_scheme models all of the different test cases
2329 that should be run using a single repository.
2331 greater_scheme is a list of DeepTreesTestCase items, which define complete
2332 test setups, so that they can be performed as described above.
2338 wc_dir
= sbox
.wc_dir
2340 # 1) Create directories.
2341 for test_case
in greater_scheme
:
2343 base
= j(sbox
.wc_dir
, test_case
.name
)
2345 make_deep_trees(j(base
, "incoming"))
2346 main
.run_svn(None, 'add', base
)
2348 print("ERROR IN: Tests scheme for merge: "
2349 + "while setting up deep trees in '%s'" % test_case
.name
)
2353 # 2) Commit pre-initial state (-r2).
2355 main
.run_svn(None, 'commit', '-m', 'pre-initial state', wc_dir
)
2358 # 3) Copy "incoming" to "local".
2360 for test_case
in greater_scheme
:
2362 base_url
= sbox
.repo_url
+ "/" + test_case
.name
2363 incoming_url
= base_url
+ "/incoming"
2364 local_url
= base_url
+ "/local"
2365 main
.run_svn(None, 'cp', incoming_url
, local_url
, '-m',
2366 'copy incoming to local')
2368 print("ERROR IN: Tests scheme for merge: "
2369 + "while copying deep trees in '%s'" % test_case
.name
)
2372 # 4) Update to load all of the "/local" subdirs into the working copies.
2375 main
.run_svn(None, 'up', sbox
.wc_dir
)
2377 print("ERROR IN: Tests scheme for merge: "
2378 + "while updating local subdirs")
2382 # 5) Perform incoming actions
2384 for test_case
in greater_scheme
:
2386 test_case
.incoming_action(j(sbox
.wc_dir
, test_case
.name
, "incoming"))
2388 print("ERROR IN: Tests scheme for merge: "
2389 + "while performing incoming action in '%s'" % test_case
.name
)
2393 # 6) or 7) Commit all incoming actions
2395 if not do_commit_local_changes
:
2397 main
.run_svn(None, 'ci', '-m', 'Committing incoming actions',
2400 print("ERROR IN: Tests scheme for merge: "
2401 + "while committing incoming actions")
2405 # 7) or 6) Perform all local actions.
2407 for test_case
in greater_scheme
:
2409 test_case
.local_action(j(sbox
.wc_dir
, test_case
.name
, "local"))
2411 print("ERROR IN: Tests scheme for merge: "
2412 + "while performing local action in '%s'" % test_case
.name
)
2416 # 6) or 7) Commit all incoming actions
2418 if do_commit_local_changes
:
2420 main
.run_svn(None, 'ci', '-m', 'Committing incoming and local actions',
2423 print("ERROR IN: Tests scheme for merge: "
2424 + "while committing incoming and local actions")
2428 # 8) Merge all "incoming" subdirs to their respective "local" subdirs.
2429 # This creates conflicts between the local changes in the "local" wc
2430 # subdirs and the incoming states committed in the "incoming" subdirs.
2432 for test_case
in greater_scheme
:
2434 local
= j(sbox
.wc_dir
, test_case
.name
, "local")
2435 incoming
= sbox
.repo_url
+ "/" + test_case
.name
+ "/incoming"
2437 x_out
= test_case
.expected_output
2439 x_out
= x_out
.copy()
2440 x_out
.wc_dir
= local
2442 x_disk
= test_case
.expected_disk
2444 x_status
= test_case
.expected_status
2445 if x_status
!= None:
2447 x_status
.wc_dir
= local
2449 x_skip
= test_case
.expected_skip
2452 x_skip
.wc_dir
= local
2454 run_and_verify_merge(local
, None, None, incoming
,
2455 x_out
, x_disk
, None, x_skip
,
2456 error_re_string
= test_case
.error_re_string
,
2458 run_and_verify_unquiet_status(local
, x_status
)
2460 print("ERROR IN: Tests scheme for merge: "
2461 + "while verifying in '%s'" % test_case
.name
)