3 # run_tests.py: test suite for cvs2svn
5 # Usage: run_tests.py [-v | --verbose] [list | <num>]
9 # enable verbose output
11 # Arguments (at most one argument is allowed):
13 # If the word "list" is passed as an argument, the list of
14 # available tests is printed (but no tests are run).
17 # If a number is passed as an argument, then only the test
18 # with that number is run.
20 # If no argument is specified, then all tests are run.
22 # Subversion is a tool for revision control.
23 # See http://subversion.tigris.org for more information.
25 # ====================================================================
26 # Copyright (c) 2000-2007 CollabNet. All rights reserved.
28 # This software is licensed as described in the file COPYING, which
29 # you should have received as part of this distribution. The terms
30 # are also available at http://subversion.tigris.org/license-1.html.
31 # If newer versions of this license are posted there, you may use a
32 # newer version instead, at your option.
34 ######################################################################
46 from difflib
import Differ
48 # Make sure that a supported version of Python is being used:
49 if not (0x02040000 <= sys
.hexversion
< 0x03000000):
51 'error: Python 2, version 2.4 or higher required.\n'
55 # This script needs to run in the correct directory. Make sure we're there.
56 if not (os
.path
.exists('cvs2svn') and os
.path
.exists('test-data')):
57 sys
.stderr
.write("error: I need to be run in the directory containing "
58 "'cvs2svn' and 'test-data'.\n")
61 # Load the Subversion test framework.
63 from svntest
import Failure
64 from svntest
.main
import run_command
65 from svntest
.main
import run_tests
66 from svntest
.main
import safe_rmtree
67 from svntest
.testcase
import TestCase
68 from svntest
.testcase
import Skip
69 from svntest
.testcase
import XFail
70 from svntest
.tree
import build_tree_from_wc
71 from svntest
.tree
import get_child
73 cvs2svn
= os
.path
.abspath('cvs2svn')
74 cvs2git
= os
.path
.abspath('cvs2git')
76 # We use the installed svn and svnlook binaries, instead of using
77 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
78 # behavior -- or even existence -- of local builds shouldn't affect
79 # the cvs2svn test suite.
83 test_data_dir
= 'test-data'
84 tmp_dir
= 'cvs2svn-tmp'
87 #----------------------------------------------------------------------
89 #----------------------------------------------------------------------
92 # The value to expect for svn:keywords if it is set:
93 KEYWORDS
= 'Author Date Id Revision'
96 class RunProgramException(Failure
):
100 class MissingErrorException(Failure
):
101 def __init__(self
, error_re
):
103 self
, "Test failed because no error matched '%s'" % (error_re
,)
107 def run_program(program
, error_re
, *varargs
):
108 """Run PROGRAM with VARARGS, return stdout as a list of lines.
110 If there is any stderr and ERROR_RE is None, raise
111 RunProgramException, and print the stderr lines if
112 svntest.main.verbose_mode is true.
114 If ERROR_RE is not None, it is a string regular expression that must
115 match some line of stderr. If it fails to match, raise
116 MissingErrorExpection."""
118 # FIXME: exit_code is currently ignored.
119 exit_code
, out
, err
= run_command(program
, 1, 0, *varargs
)
122 # Specified error expected on stderr.
124 raise MissingErrorException(error_re
)
127 if re
.match(error_re
, line
):
129 raise MissingErrorException(error_re
)
133 if svntest
.main
.verbose_mode
:
134 print '\n%s said:\n' % program
138 raise RunProgramException()
143 def run_cvs2svn(error_re
, *varargs
):
144 """Run cvs2svn with VARARGS, return stdout as a list of lines.
146 If there is any stderr and ERROR_RE is None, raise
147 RunProgramException, and print the stderr lines if
148 svntest.main.verbose_mode is true.
150 If ERROR_RE is not None, it is a string regular expression that must
151 match some line of stderr. If it fails to match, raise
152 MissingErrorException."""
154 # Use the same python that is running this script
155 return run_program(sys
.executable
, error_re
, cvs2svn
, *varargs
)
156 # On Windows, for an unknown reason, the cmd.exe process invoked by
157 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
158 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
159 # this. Therefore, the redirection of the output to the .s-revs file fails.
160 # We no longer use the problematic invocation on any system, but this
161 # comment remains to warn about this problem.
164 def run_cvs2git(error_re
, *varargs
):
165 """Run cvs2git with VARARGS, returning stdout as a list of lines.
167 If there is any stderr and ERROR_RE is None, raise
168 RunProgramException, and print the stderr lines if
169 svntest.main.verbose_mode is true.
171 If ERROR_RE is not None, it is a string regular expression that must
172 match some line of stderr. If it fails to match, raise
173 MissingErrorException."""
175 # Use the same python that is running this script
176 return run_program(sys
.executable
, error_re
, cvs2git
, *varargs
)
177 # On Windows, for an unknown reason, the cmd.exe process invoked by
178 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
179 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
180 # this. Therefore, the redirection of the output to the .s-revs file fails.
181 # We no longer use the problematic invocation on any system, but this
182 # comment remains to warn about this problem.
185 def run_svn(*varargs
):
186 """Run svn with VARARGS; return stdout as a list of lines.
187 If there is any stderr, raise RunProgramException, and print the
188 stderr lines if svntest.main.verbose_mode is true."""
189 return run_program(svn
, None, *varargs
)
192 def repos_to_url(path_to_svn_repos
):
193 """This does what you think it does."""
194 rpath
= os
.path
.abspath(path_to_svn_repos
)
197 return 'file://%s' % rpath
.replace(os
.sep
, '/')
200 def svn_strptime(timestr
):
201 return time
.strptime(timestr
, '%Y-%m-%d %H:%M:%S')
205 def __init__(self
, revision
, author
, date
, symbols
):
206 self
.revision
= revision
209 # Internally, we represent the date as seconds since epoch (UTC).
210 # Since standard subversion log output shows dates in localtime
212 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
214 # and time.mktime() converts from localtime, it all works out very
216 self
.date
= time
.mktime(svn_strptime(date
[0:19]))
218 # The following symbols are used for string interpolation when
220 self
.symbols
= symbols
222 # The changed paths will be accumulated later, as log data is read.
223 # Keys here are paths such as '/trunk/foo/bar', values are letter
224 # codes such as 'M', 'A', and 'D'.
225 self
.changed_paths
= { }
227 # The msg will be accumulated later, as log data is read.
230 def absorb_changed_paths(self
, out
):
231 'Read changed paths from OUT into self, until no more.'
233 line
= out
.readline()
234 if len(line
) == 1: return
236 op_portion
= line
[3:4]
237 path_portion
= line
[5:]
238 # If we're running on Windows we get backslashes instead of
240 path_portion
= path_portion
.replace('\\', '/')
241 # # We could parse out history information, but currently we
242 # # just leave it in the path portion because that's how some
245 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
247 # path_portion = m.group(1)
248 self
.changed_paths
[path_portion
] = op_portion
250 def __cmp__(self
, other
):
251 return cmp(self
.revision
, other
.revision
) or \
252 cmp(self
.author
, other
.author
) or cmp(self
.date
, other
.date
) or \
253 cmp(self
.changed_paths
, other
.changed_paths
) or \
254 cmp(self
.msg
, other
.msg
)
256 def get_path_op(self
, path
):
257 """Return the operator for the change involving PATH.
259 PATH is allowed to include string interpolation directives (e.g.,
260 '%(trunk)s'), which are interpolated against self.symbols. Return
261 None if there is no record for PATH."""
262 return self
.changed_paths
.get(path
% self
.symbols
)
264 def check_msg(self
, msg
):
265 """Verify that this Log's message starts with the specified MSG."""
266 if self
.msg
.find(msg
) != 0:
268 "Revision %d log message was:\n%s\n\n"
269 "It should have begun with:\n%s\n\n"
270 % (self
.revision
, self
.msg
, msg
,)
273 def check_change(self
, path
, op
):
274 """Verify that this Log includes a change for PATH with operator OP.
276 PATH is allowed to include string interpolation directives (e.g.,
277 '%(trunk)s'), which are interpolated against self.symbols."""
279 path
= path
% self
.symbols
280 found_op
= self
.changed_paths
.get(path
, None)
283 "Revision %d does not include change for path %s "
284 "(it should have been %s).\n"
285 % (self
.revision
, path
, op
,)
289 "Revision %d path %s had op %s (it should have been %s)\n"
290 % (self
.revision
, path
, found_op
, op
,)
293 def check_changes(self
, changed_paths
):
294 """Verify that this Log has precisely the CHANGED_PATHS specified.
296 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
297 strings are allowed to include string interpolation directives
298 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
301 for (path
, op
) in changed_paths
:
302 cp
[path
% self
.symbols
] = op
304 if self
.changed_paths
!= cp
:
306 "Revision %d changed paths list was:\n%s\n\n"
307 "It should have been:\n%s\n\n"
308 % (self
.revision
, self
.changed_paths
, cp
,)
311 def check(self
, msg
, changed_paths
):
312 """Verify that this Log has the MSG and CHANGED_PATHS specified.
314 Convenience function to check two things at once. MSG is passed
315 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
318 self
.check_changes(changed_paths
)
321 def parse_log(svn_repos
, symbols
):
322 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
324 Initialize the Logs' symbols with SYMBOLS."""
327 'Make a list of lines behave like an open file handle.'
328 def __init__(self
, lines
):
331 if len(self
.lines
) > 0:
332 return self
.lines
.pop(0)
336 def absorb_message_body(out
, num_lines
, log
):
337 """Read NUM_LINES of log message body from OUT into Log item LOG."""
339 for i
in range(num_lines
):
340 log
.msg
+= out
.readline()
342 log_start_re
= re
.compile('^r(?P<rev>[0-9]+) \| '
343 '(?P<author>[^\|]+) \| '
345 '\| (?P<lines>[0-9]+) (line|lines)$')
347 log_separator
= '-' * 72
351 out
= LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos
)))
355 line
= out
.readline()
359 if line
.find(log_separator
) == 0:
360 line
= out
.readline()
363 m
= log_start_re
.match(line
)
366 int(m
.group('rev')), m
.group('author'), m
.group('date'), symbols
)
367 line
= out
.readline()
368 if not line
.find('Changed paths:') == 0:
369 print 'unexpected log output (missing changed paths)'
370 print "Line: '%s'" % line
372 this_log
.absorb_changed_paths(out
)
373 absorb_message_body(out
, int(m
.group('lines')), this_log
)
374 logs
[this_log
.revision
] = this_log
376 break # We've reached the end of the log output.
378 print 'unexpected log output (missing revision line)'
379 print "Line: '%s'" % line
382 print 'unexpected log output (missing log separator)'
383 print "Line: '%s'" % line
390 """Unconditionally remove PATH and its subtree, if any. PATH may be
391 non-existent, a file or symlink, or a directory."""
392 if os
.path
.isdir(path
):
394 elif os
.path
.exists(path
):
398 log_msg_text_wrapper
= textwrap
.TextWrapper(width
=76)
400 def sym_log_msg(symbolic_name
, is_tag
=None):
401 """Return the expected log message for a cvs2svn-synthesized revision
402 creating branch or tag SYMBOLIC_NAME."""
404 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
410 return log_msg_text_wrapper
.fill(
411 "This commit was manufactured by cvs2svn to create %s '%s'."
412 % (type, symbolic_name
)
416 def make_conversion_id(
417 name
, args
, passbypass
, options_file
=None, symbol_hints_file
=None
419 """Create an identifying tag for a conversion.
421 The return value can also be used as part of a filesystem path.
423 NAME is the name of the CVS repository.
425 ARGS are the extra arguments to be passed to cvs2svn.
427 PASSBYPASS is a boolean indicating whether the conversion is to be
428 run one pass at a time.
430 If OPTIONS_FILE is specified, it is an options file that will be
431 used for the conversion.
433 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
434 will be used for the conversion.
436 The 1-to-1 mapping between cvs2svn command parameters and
437 conversion_ids allows us to avoid running the same conversion more
438 than once, when multiple tests use exactly the same conversion."""
442 _win32_fname_mapping
= { '/': '_sl_', '\\': '_bs_', ':': '_co_',
443 '*': '_st_', '?': '_qm_', '"': '_qq_',
444 '<': '_lt_', '>': '_gt_', '|': '_pi_', }
446 # Replace some characters that Win32 isn't happy about having in a
447 # filename (which was causing the eol_mime test to fail).
449 for a
, b
in _win32_fname_mapping
.items():
450 sanitized_arg
= sanitized_arg
.replace(a
, b
)
451 conv_id
+= sanitized_arg
454 conv_id
+= '-passbypass'
456 if options_file
is not None:
457 conv_id
+= '--options=%s' % options_file
459 if symbol_hints_file
is not None:
460 conv_id
+= '--symbol-hints=%s' % symbol_hints_file
466 """A record of a cvs2svn conversion.
470 conv_id -- the conversion id for this Conversion.
472 name -- a one-word name indicating the involved repositories.
474 dumpfile -- the name of the SVN dumpfile created by the conversion
475 (if the DUMPFILE constructor argument was used); otherwise,
478 repos -- the path to the svn repository. Unset if DUMPFILE was
481 logs -- a dictionary of Log instances, as returned by parse_log().
482 Unset if DUMPFILE was specified.
484 symbols -- a dictionary of symbols used for string interpolation
487 stdout -- a list of lines written by cvs2svn to stdout
489 _wc -- the basename of the svn working copy (within tmp_dir).
490 Unset if DUMPFILE was specified.
492 _wc_path -- the path to the svn working copy, if it has already
493 been created; otherwise, None. (The working copy is created
494 lazily when get_wc() is called.) Unset if DUMPFILE was
497 _wc_tree -- the tree built from the svn working copy, if it has
498 already been created; otherwise, None. The tree is created
499 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
502 _svnrepos -- the basename of the svn repository (within tmp_dir).
503 Unset if DUMPFILE was specified."""
505 # The number of the last cvs2svn pass (determined lazily by
510 def get_last_pass(cls
):
511 """Return the number of cvs2svn's last pass."""
513 if cls
.last_pass
is None:
514 out
= run_cvs2svn(None, '--help-passes')
515 cls
.last_pass
= int(out
[-1].split()[0])
519 self
, conv_id
, name
, error_re
, passbypass
, symbols
, args
,
520 options_file
=None, symbol_hints_file
=None, dumpfile
=None,
522 self
.conv_id
= conv_id
524 self
.symbols
= symbols
525 if not os
.path
.isdir(tmp_dir
):
528 cvsrepos
= os
.path
.join(test_data_dir
, '%s-cvsrepos' % self
.name
)
531 self
.dumpfile
= os
.path
.join(tmp_dir
, dumpfile
)
532 # Clean up from any previous invocations of this script.
536 self
.repos
= os
.path
.join(tmp_dir
, '%s-svnrepos' % self
.conv_id
)
537 self
._wc
= os
.path
.join(tmp_dir
, '%s-wc' % self
.conv_id
)
541 # Clean up from any previous invocations of this script.
547 self
.options_file
= os
.path
.join(cvsrepos
, options_file
)
549 '--options=%s' % self
.options_file
,
551 assert not symbol_hints_file
553 self
.options_file
= None
554 if tmp_dir
!= 'cvs2svn-tmp':
555 # Only include this argument if it differs from cvs2svn's default:
557 '--tmpdir=%s' % tmp_dir
,
560 if symbol_hints_file
:
561 self
.symbol_hints_file
= os
.path
.join(cvsrepos
, symbol_hints_file
)
563 '--symbol-hints=%s' % self
.symbol_hints_file
,
567 args
.extend(['--dumpfile=%s' % (self
.dumpfile
,)])
569 args
.extend(['-s', self
.repos
])
570 args
.extend([cvsrepos
])
574 for p
in range(1, self
.get_last_pass() + 1):
575 self
.stdout
+= run_cvs2svn(error_re
, '-p', str(p
), *args
)
577 self
.stdout
= run_cvs2svn(error_re
, *args
)
580 if not os
.path
.isfile(self
.dumpfile
):
582 "Dumpfile not created: '%s'"
583 % os
.path
.join(os
.getcwd(), self
.dumpfile
)
586 if os
.path
.isdir(self
.repos
):
587 self
.logs
= parse_log(self
.repos
, self
.symbols
)
588 elif error_re
is None:
590 "Repository not created: '%s'"
591 % os
.path
.join(os
.getcwd(), self
.repos
)
594 def output_found(self
, pattern
):
595 """Return True if PATTERN matches any line in self.stdout.
597 PATTERN is a regular expression pattern as a string.
600 pattern_re
= re
.compile(pattern
)
602 for line
in self
.stdout
:
603 if pattern_re
.match(line
):
604 # We found the pattern that we were looking for.
609 def find_tag_log(self
, tagname
):
610 """Search LOGS for a log message containing 'TAGNAME' and return the
611 log in which it was found."""
612 for i
in xrange(len(self
.logs
), 0, -1):
613 if self
.logs
[i
].msg
.find("'"+tagname
+"'") != -1:
615 raise ValueError("Tag %s not found in logs" % tagname
)
617 def get_wc(self
, *args
):
618 """Return the path to the svn working copy, or a path within the WC.
620 If a working copy has not been created yet, create it now.
622 If ARGS are specified, then they should be strings that form
623 fragments of a path within the WC. They are joined using
624 os.path.join() and appended to the WC path."""
626 if self
._wc
_path
is None:
627 run_svn('co', repos_to_url(self
.repos
), self
._wc
)
628 self
._wc
_path
= self
._wc
629 return os
.path
.join(self
._wc
_path
, *args
)
631 def get_wc_tree(self
):
632 if self
._wc
_tree
is None:
633 self
._wc
_tree
= build_tree_from_wc(self
.get_wc(), 1)
636 def path_exists(self
, *args
):
637 """Return True if the specified path exists within the repository.
639 (The strings in ARGS are first joined into a path using
642 return os
.path
.exists(self
.get_wc(*args
))
644 def check_props(self
, keys
, checks
):
645 """Helper function for checking lots of properties. For a list of
646 files in the conversion, check that the values of the properties
647 listed in KEYS agree with those listed in CHECKS. CHECKS is a
648 list of tuples: [ (filename, [value, value, ...]), ...], where the
649 values are listed in the same order as the key names are listed in
652 for (file, values
) in checks
:
653 assert len(values
) == len(keys
)
654 props
= props_for_path(self
.get_wc_tree(), file)
655 for i
in range(len(keys
)):
656 if props
.get(keys
[i
]) != values
[i
]:
658 "File %s has property %s set to \"%s\" "
659 "(it should have been \"%s\").\n"
660 % (file, keys
[i
], props
.get(keys
[i
]), values
[i
],)
665 """A record of a cvs2svn conversion.
669 name -- a one-word name indicating the CVS repository to be converted.
671 stdout -- a list of lines written by cvs2svn to stdout."""
673 def __init__(self
, name
, error_re
, args
, options_file
=None):
675 if not os
.path
.isdir(tmp_dir
):
678 cvsrepos
= os
.path
.join(test_data_dir
, '%s-cvsrepos' % self
.name
)
682 self
.options_file
= os
.path
.join(cvsrepos
, options_file
)
684 '--options=%s' % self
.options_file
,
687 self
.options_file
= None
689 self
.stdout
= run_cvs2git(error_re
, *args
)
692 # Cache of conversions that have already been done. Keys are conv_id;
693 # values are Conversion instances.
694 already_converted
= { }
696 def ensure_conversion(
697 name
, error_re
=None, passbypass
=None,
698 trunk
=None, branches
=None, tags
=None,
699 args
=None, options_file
=None, symbol_hints_file
=None, dumpfile
=None,
701 """Convert CVS repository NAME to Subversion, but only if it has not
702 been converted before by this invocation of this script. If it has
703 been converted before, return the Conversion object from the
706 If no error, return a Conversion instance.
708 If ERROR_RE is a string, it is a regular expression expected to
709 match some line of stderr printed by the conversion. If there is an
710 error and ERROR_RE is not set, then raise Failure.
712 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
713 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
715 NAME is just one word. For example, 'main' would mean to convert
716 './test-data/main-cvsrepos', and after the conversion, the resulting
717 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
718 a checked out head working copy in './cvs2svn-tmp/main-wc'.
720 Any other options to pass to cvs2svn should be in ARGS, each element
721 being one option, e.g., '--trunk-only'. If the option takes an
722 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
723 are passed to cvs2svn in the order that they appear in ARGS.
725 If OPTIONS_FILE is specified, then it should be the name of a file
726 within the main directory of the cvs repository associated with this
727 test. It is passed to cvs2svn using the --options option (which
728 suppresses some other options that are incompatible with --options).
730 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
731 file within the main directory of the cvs repository associated with
732 this test. It is passed to cvs2svn using the --symbol-hints option.
734 If DUMPFILE is specified, then it is the name of a dumpfile within
735 the temporary directory to which the conversion output should be
746 args
.append('--trunk=%s' % (trunk
,))
749 branches
= 'branches'
751 args
.append('--branches=%s' % (branches
,))
756 args
.append('--tags=%s' % (tags
,))
758 conv_id
= make_conversion_id(
759 name
, args
, passbypass
, options_file
, symbol_hints_file
762 if conv_id
not in already_converted
:
764 # Run the conversion and store the result for the rest of this
766 already_converted
[conv_id
] = Conversion(
767 conv_id
, name
, error_re
, passbypass
,
768 {'trunk' : trunk
, 'branches' : branches
, 'tags' : tags
},
769 args
, options_file
, symbol_hints_file
, dumpfile
,
772 # Remember the failure so that a future attempt to run this conversion
773 # does not bother to retry, but fails immediately.
774 already_converted
[conv_id
] = None
777 conv
= already_converted
[conv_id
]
783 class Cvs2SvnTestCase(TestCase
):
785 self
, name
, description
=None, variant
=None,
786 error_re
=None, passbypass
=None,
787 trunk
=None, branches
=None, tags
=None,
789 options_file
=None, symbol_hints_file
=None, dumpfile
=None,
791 TestCase
.__init
__(self
)
794 if description
is not None:
795 self
._description
= description
797 # By default, use the first line of the class docstring as the
799 self
._description
= self
.__doc
__.splitlines()[0]
801 # Check that the original description is OK before we tinker with
803 self
.check_description()
805 if variant
is not None:
806 # Modify description to show the variant. Trim description
807 # first if necessary to stay within the 50-character limit.
808 suffix
= '...variant %s' % (variant
,)
809 self
._description
= self
._description
[:50 - len(suffix
)] + suffix
810 # Check that the description is still OK:
811 self
.check_description()
813 self
.error_re
= error_re
814 self
.passbypass
= passbypass
816 self
.branches
= branches
819 self
.options_file
= options_file
820 self
.symbol_hints_file
= symbol_hints_file
821 self
.dumpfile
= dumpfile
823 def get_description(self
):
824 return self
._description
826 def ensure_conversion(self
):
827 return ensure_conversion(
829 error_re
=self
.error_re
, passbypass
=self
.passbypass
,
830 trunk
=self
.trunk
, branches
=self
.branches
, tags
=self
.tags
,
832 options_file
=self
.options_file
,
833 symbol_hints_file
=self
.symbol_hints_file
,
834 dumpfile
=self
.dumpfile
,
838 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase
):
839 """Test properties resulting from a conversion."""
841 def __init__(self
, name
, props_to_test
, expected_props
, **kw
):
842 """Initialize an instance of Cvs2SvnPropertiesTestCase.
844 NAME is the name of the test, passed to Cvs2SvnTestCase.
845 PROPS_TO_TEST is a list of the names of svn properties that should
846 be tested. EXPECTED_PROPS is a list of tuples [(filename,
847 [value,...])], where the second item in each tuple is a list of
848 values expected for the properties listed in PROPS_TO_TEST for the
849 specified filename. If a property must *not* be set, then its
850 value should be listed as None."""
852 Cvs2SvnTestCase
.__init
__(self
, name
, **kw
)
853 self
.props_to_test
= props_to_test
854 self
.expected_props
= expected_props
857 conv
= self
.ensure_conversion()
858 conv
.check_props(self
.props_to_test
, self
.expected_props
)
861 #----------------------------------------------------------------------
863 #----------------------------------------------------------------------
867 "cvs2svn with no arguments shows usage"
868 out
= run_cvs2svn(None)
869 if (len(out
) > 2 and out
[0].find('ERROR:') == 0
870 and out
[1].find('DBM module')):
871 print 'cvs2svn cannot execute due to lack of proper DBM module.'
872 print 'Exiting without running any further tests.'
874 if out
[0].find('Usage:') < 0:
875 raise Failure('Basic cvs2svn invocation failed.')
878 def show_help_passes():
879 "cvs2svn --help-passes shows pass information"
880 out
= run_cvs2svn(None, '--help-passes')
881 if out
[0].find('PASSES') < 0:
882 raise Failure('cvs2svn --help-passes failed.')
886 "detection of the executable flag"
887 if sys
.platform
== 'win32':
889 conv
= ensure_conversion('main')
890 st
= os
.stat(conv
.get_wc('trunk', 'single-files', 'attr-exec'))
891 if not st
[0] & stat
.S_IXUSR
:
896 "conversion of filename with a space"
897 conv
= ensure_conversion('main')
898 if not conv
.path_exists('trunk', 'single-files', 'space fname'):
903 "two commits in quick succession"
904 conv
= ensure_conversion('main')
906 os
.path
.join(conv
.repos
, 'trunk', 'single-files', 'twoquick'), {})
911 class PruneWithCare(Cvs2SvnTestCase
):
912 "prune, but never too much"
914 def __init__(self
, **kw
):
915 Cvs2SvnTestCase
.__init
__(self
, 'main', **kw
)
918 # Robert Pluim encountered this lovely one while converting the
919 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
920 # repository (see issue #1302). Step 4 is the doozy:
922 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
923 # revision 2: adds trunk/blah/NEWS
924 # revision 3: deletes trunk/blah/cookie
925 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
926 # revision 5: does nothing
928 # After fixing cvs2svn, the sequence (correctly) looks like this:
930 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
931 # revision 2: adds trunk/blah/NEWS
932 # revision 3: deletes trunk/blah/cookie
933 # revision 4: does nothing [because trunk/blah/cookie already deleted]
934 # revision 5: deletes blah
936 # The difference is in 4 and 5. In revision 4, it's not correct to
937 # prune blah/, because NEWS is still in there, so revision 4 does
938 # nothing now. But when we delete NEWS in 5, that should bubble up
939 # and prune blah/ instead.
941 # ### Note that empty revisions like 4 are probably going to become
942 # ### at least optional, if not banished entirely from cvs2svn's
943 # ### output. Hmmm, or they may stick around, with an extra
944 # ### revision property explaining what happened. Need to think
945 # ### about that. In some sense, it's a bug in Subversion itself,
946 # ### that such revisions don't show up in 'svn log' output.
948 # In the test below, 'trunk/full-prune/first' represents
949 # cookie, and 'trunk/full-prune/second' represents NEWS.
951 conv
= self
.ensure_conversion()
953 # Confirm that revision 4 removes '/trunk/full-prune/first',
954 # and that revision 6 removes '/trunk/full-prune'.
956 # Also confirm similar things about '/full-prune-reappear/...',
957 # which is similar, except that later on it reappears, restored
958 # from pruneland, because a file gets added to it.
960 # And finally, a similar thing for '/partial-prune/...', except that
961 # in its case, a permanent file on the top level prevents the
962 # pruning from going farther than the subdirectory containing first
965 for path
in ('full-prune/first',
966 'full-prune-reappear/sub/first',
967 'partial-prune/sub/first'):
968 conv
.logs
[5].check_change('/%(trunk)s/' + path
, 'D')
970 for path
in ('full-prune',
971 'full-prune-reappear',
972 'partial-prune/sub'):
973 conv
.logs
[7].check_change('/%(trunk)s/' + path
, 'D')
975 for path
in ('full-prune-reappear',
976 'full-prune-reappear/appears-later'):
977 conv
.logs
[33].check_change('/%(trunk)s/' + path
, 'A')
980 def interleaved_commits():
981 "two interleaved trunk commits, different log msgs"
982 # See test-data/main-cvsrepos/proj/README.
983 conv
= ensure_conversion('main')
985 # The initial import.
987 conv
.logs
[rev
].check('Initial import.', (
988 ('/%(trunk)s/interleaved', 'A'),
989 ('/%(trunk)s/interleaved/1', 'A'),
990 ('/%(trunk)s/interleaved/2', 'A'),
991 ('/%(trunk)s/interleaved/3', 'A'),
992 ('/%(trunk)s/interleaved/4', 'A'),
993 ('/%(trunk)s/interleaved/5', 'A'),
994 ('/%(trunk)s/interleaved/a', 'A'),
995 ('/%(trunk)s/interleaved/b', 'A'),
996 ('/%(trunk)s/interleaved/c', 'A'),
997 ('/%(trunk)s/interleaved/d', 'A'),
998 ('/%(trunk)s/interleaved/e', 'A'),
1001 def check_letters(rev
):
1002 """Check if REV is the rev where only letters were committed."""
1004 conv
.logs
[rev
].check('Committing letters only.', (
1005 ('/%(trunk)s/interleaved/a', 'M'),
1006 ('/%(trunk)s/interleaved/b', 'M'),
1007 ('/%(trunk)s/interleaved/c', 'M'),
1008 ('/%(trunk)s/interleaved/d', 'M'),
1009 ('/%(trunk)s/interleaved/e', 'M'),
1012 def check_numbers(rev
):
1013 """Check if REV is the rev where only numbers were committed."""
1015 conv
.logs
[rev
].check('Committing numbers only.', (
1016 ('/%(trunk)s/interleaved/1', 'M'),
1017 ('/%(trunk)s/interleaved/2', 'M'),
1018 ('/%(trunk)s/interleaved/3', 'M'),
1019 ('/%(trunk)s/interleaved/4', 'M'),
1020 ('/%(trunk)s/interleaved/5', 'M'),
1023 # One of the commits was letters only, the other was numbers only.
1024 # But they happened "simultaneously", so we don't assume anything
1025 # about which commit appeared first, so we just try both ways.
1029 check_numbers(rev
+ 1)
1032 check_letters(rev
+ 1)
1035 def simple_commits():
1036 "simple trunk commits"
1037 # See test-data/main-cvsrepos/proj/README.
1038 conv
= ensure_conversion('main')
1040 # The initial import.
1041 conv
.logs
[13].check('Initial import.', (
1042 ('/%(trunk)s/proj', 'A'),
1043 ('/%(trunk)s/proj/default', 'A'),
1044 ('/%(trunk)s/proj/sub1', 'A'),
1045 ('/%(trunk)s/proj/sub1/default', 'A'),
1046 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1047 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1048 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1049 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1050 ('/%(trunk)s/proj/sub2', 'A'),
1051 ('/%(trunk)s/proj/sub2/default', 'A'),
1052 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1053 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1054 ('/%(trunk)s/proj/sub3', 'A'),
1055 ('/%(trunk)s/proj/sub3/default', 'A'),
1059 conv
.logs
[18].check('First commit to proj, affecting two files.', (
1060 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1061 ('/%(trunk)s/proj/sub3/default', 'M'),
1064 # The second commit.
1065 conv
.logs
[19].check('Second commit to proj, affecting all 7 files.', (
1066 ('/%(trunk)s/proj/default', 'M'),
1067 ('/%(trunk)s/proj/sub1/default', 'M'),
1068 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1069 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1070 ('/%(trunk)s/proj/sub2/default', 'M'),
1071 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1072 ('/%(trunk)s/proj/sub3/default', 'M')
1076 class SimpleTags(Cvs2SvnTestCase
):
1077 "simple tags and branches, no commits"
1079 def __init__(self
, **kw
):
1080 # See test-data/main-cvsrepos/proj/README.
1081 Cvs2SvnTestCase
.__init
__(self
, 'main', **kw
)
1084 conv
= self
.ensure_conversion()
1086 # Verify the copy source for the tags we are about to check
1087 # No need to verify the copyfrom revision, as simple_commits did that
1088 conv
.logs
[13].check('Initial import.', (
1089 ('/%(trunk)s/proj', 'A'),
1090 ('/%(trunk)s/proj/default', 'A'),
1091 ('/%(trunk)s/proj/sub1', 'A'),
1092 ('/%(trunk)s/proj/sub1/default', 'A'),
1093 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1094 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1095 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1096 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1097 ('/%(trunk)s/proj/sub2', 'A'),
1098 ('/%(trunk)s/proj/sub2/default', 'A'),
1099 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1100 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1101 ('/%(trunk)s/proj/sub3', 'A'),
1102 ('/%(trunk)s/proj/sub3/default', 'A'),
1105 fromstr
= ' (from /%(branches)s/B_FROM_INITIALS:14)'
1107 # Tag on rev 1.1.1.1 of all files in proj
1108 conv
.logs
[14].check(sym_log_msg('B_FROM_INITIALS'), (
1109 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1110 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1111 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1114 # The same, as a tag
1115 log
= conv
.find_tag_log('T_ALL_INITIAL_FILES')
1116 log
.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1117 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr
, 'A'),
1120 # Tag on rev 1.1.1.1 of all files in proj, except one
1121 log
= conv
.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1122 log
.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1123 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr
, 'A'),
1124 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1127 # The same, as a branch
1128 conv
.logs
[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1129 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr
, 'A'),
1130 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1134 def simple_branch_commits():
1135 "simple branch commits"
1136 # See test-data/main-cvsrepos/proj/README.
1137 conv
= ensure_conversion('main')
1139 conv
.logs
[23].check('Modify three files, on branch B_MIXED.', (
1140 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1141 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1142 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1146 def mixed_time_tag():
1148 # See test-data/main-cvsrepos/proj/README.
1149 conv
= ensure_conversion('main')
1151 log
= conv
.find_tag_log('T_MIXED')
1153 ('/%(tags)s/T_MIXED (from /%(branches)s/B_MIXED:20)', 'A'),
1157 def mixed_time_branch_with_added_file():
1158 "mixed-time branch, and a file added to the branch"
1159 # See test-data/main-cvsrepos/proj/README.
1160 conv
= ensure_conversion('main')
1162 # A branch from the same place as T_MIXED in the previous test,
1163 # plus a file added directly to the branch
1164 conv
.logs
[20].check(sym_log_msg('B_MIXED'), (
1165 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1166 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1167 ('/%(branches)s/B_MIXED/single-files', 'D'),
1168 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1169 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1170 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1173 conv
.logs
[22].check('Add a file on branch B_MIXED.', (
1174 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1179 "a commit affecting both trunk and a branch"
1180 # See test-data/main-cvsrepos/proj/README.
1181 conv
= ensure_conversion('main')
1183 conv
.logs
[24].check(
1184 'A single commit affecting one file on branch B_MIXED '
1185 'and one on trunk.', (
1186 ('/%(trunk)s/proj/sub2/default', 'M'),
1187 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1191 def split_time_branch():
1192 "branch some trunk files, and later branch the rest"
1193 # See test-data/main-cvsrepos/proj/README.
1194 conv
= ensure_conversion('main')
1196 # First change on the branch, creating it
1197 conv
.logs
[25].check(sym_log_msg('B_SPLIT'), (
1198 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1199 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1200 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1201 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1204 conv
.logs
[29].check('First change on branch B_SPLIT.', (
1205 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1206 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1207 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1208 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1209 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1212 # A trunk commit for the file which was not branched
1213 conv
.logs
[30].check('A trunk change to sub1/subsubB/default. '
1214 'This was committed about an', (
1215 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1218 # Add the file not already branched to the branch, with modification:w
1219 conv
.logs
[31].check(sym_log_msg('B_SPLIT'), (
1220 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1221 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1224 conv
.logs
[32].check('This change affects sub3/default and '
1225 'sub1/subsubB/default, on branch', (
1226 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1227 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1231 def multiple_tags():
1232 "multiple tags referring to same revision"
1233 conv
= ensure_conversion('main')
1234 if not conv
.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1236 if not conv
.path_exists(
1237 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1241 def multiply_defined_symbols():
1242 "multiple definitions of symbol names"
1244 # We can only check one line of the error output at a time, so test
1245 # twice. (The conversion only have to be done once because the
1246 # results are cached.)
1247 conv
= ensure_conversion(
1248 'multiply-defined-symbols',
1250 r
"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1254 conv
= ensure_conversion(
1255 'multiply-defined-symbols',
1257 r
"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1263 def multiply_defined_symbols_renamed():
1264 "rename multiply defined symbols"
1266 conv
= ensure_conversion(
1267 'multiply-defined-symbols',
1268 options_file
='cvs2svn-rename.options',
1272 def multiply_defined_symbols_ignored():
1273 "ignore multiply defined symbols"
1275 conv
= ensure_conversion(
1276 'multiply-defined-symbols',
1277 options_file
='cvs2svn-ignore.options',
1281 def repeatedly_defined_symbols():
1282 "multiple identical definitions of symbol names"
1284 # If a symbol is defined multiple times but has the same value each
1285 # time, that should not be an error.
1287 conv
= ensure_conversion('repeatedly-defined-symbols')
1291 "conversion of invalid symbolic names"
1292 conv
= ensure_conversion('bogus-tag')
1295 def overlapping_branch():
1296 "ignore a file with a branch with two names"
1297 conv
= ensure_conversion('overlapping-branch')
1299 if not conv
.output_found('.*cannot also have name \'vendorB\''):
1302 conv
.logs
[2].check('imported', (
1303 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1304 ('/%(trunk)s/overlapping-branch', 'A'),
1307 if len(conv
.logs
) != 2:
1311 class PhoenixBranch(Cvs2SvnTestCase
):
1312 "convert a branch file rooted in a 'dead' revision"
1314 def __init__(self
, **kw
):
1315 Cvs2SvnTestCase
.__init
__(self
, 'phoenix', **kw
)
1318 conv
= self
.ensure_conversion()
1319 conv
.logs
[8].check('This file was supplied by Jack Moffitt', (
1320 ('/%(branches)s/volsung_20010721', 'A'),
1321 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1323 conv
.logs
[9].check('This file was supplied by Jack Moffitt', (
1324 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1328 ###TODO: We check for 4 changed paths here to accomodate creating tags
1329 ###and branches in rev 1, but that will change, so this will
1330 ###eventually change back.
1331 def ctrl_char_in_log():
1332 "handle a control char in a log message"
1333 # This was issue #1106.
1335 conv
= ensure_conversion('ctrl-char-in-log')
1336 conv
.logs
[rev
].check_changes((
1337 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1339 if conv
.logs
[rev
].msg
.find('\x04') < 0:
1341 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1345 "handle tags rooted in a redeleted revision"
1346 conv
= ensure_conversion('overdead')
1349 class NoTrunkPrune(Cvs2SvnTestCase
):
1350 "ensure that trunk doesn't get pruned"
1352 def __init__(self
, **kw
):
1353 Cvs2SvnTestCase
.__init
__(self
, 'overdead', **kw
)
1356 conv
= self
.ensure_conversion()
1357 for rev
in conv
.logs
.keys():
1358 rev_logs
= conv
.logs
[rev
]
1359 if rev_logs
.get_path_op('/%(trunk)s') == 'D':
1363 def double_delete():
1364 "file deleted twice, in the root of the repository"
1365 # This really tests several things: how we handle a file that's
1366 # removed (state 'dead') in two successive revisions; how we
1367 # handle a file in the root of the repository (there were some
1368 # bugs in cvs2svn's svn path construction for top-level files); and
1369 # the --no-prune option.
1370 conv
= ensure_conversion(
1371 'double-delete', args
=['--trunk-only', '--no-prune'])
1373 path
= '/%(trunk)s/twice-removed'
1375 conv
.logs
[rev
].check('Updated CVS', (
1378 conv
.logs
[rev
+ 1].check('Remove this file for the first time.', (
1381 conv
.logs
[rev
+ 2].check('Remove this file for the second time,', (
1386 "branch created from both trunk and another branch"
1387 # See test-data/split-branch-cvsrepos/README.
1389 # The conversion will fail if the bug is present, and
1390 # ensure_conversion will raise Failure.
1391 conv
= ensure_conversion('split-branch')
1394 def resync_misgroups():
1395 "resyncing should not misorder commit groups"
1396 # See test-data/resync-misgroups-cvsrepos/README.
1398 # The conversion will fail if the bug is present, and
1399 # ensure_conversion will raise Failure.
1400 conv
= ensure_conversion('resync-misgroups')
1403 class TaggedBranchAndTrunk(Cvs2SvnTestCase
):
1404 "allow tags with mixed trunk and branch sources"
1406 def __init__(self
, **kw
):
1407 Cvs2SvnTestCase
.__init
__(self
, 'tagged-branch-n-trunk', **kw
)
1410 conv
= self
.ensure_conversion()
1412 tags
= conv
.symbols
.get('tags', 'tags')
1414 a_path
= conv
.get_wc(tags
, 'some-tag', 'a.txt')
1415 b_path
= conv
.get_wc(tags
, 'some-tag', 'b.txt')
1416 if not (os
.path
.exists(a_path
) and os
.path
.exists(b_path
)):
1418 if (open(a_path
, 'r').read().find('1.24') == -1) \
1419 or (open(b_path
, 'r').read().find('1.5') == -1):
1424 "never use the rev-in-progress as a copy source"
1426 # See issue #1427 and r8544.
1427 conv
= ensure_conversion('enroot-race')
1429 conv
.logs
[rev
].check_changes((
1430 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1431 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1432 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1434 conv
.logs
[rev
+ 1].check_changes((
1435 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1436 ('/%(trunk)s/proj/a.txt', 'M'),
1437 ('/%(trunk)s/proj/b.txt', 'M'),
1441 def enroot_race_obo():
1442 "do use the last completed rev as a copy source"
1443 conv
= ensure_conversion('enroot-race-obo')
1444 conv
.logs
[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1445 if not len(conv
.logs
) == 3:
1449 class BranchDeleteFirst(Cvs2SvnTestCase
):
1450 "correctly handle deletion as initial branch action"
1452 def __init__(self
, **kw
):
1453 Cvs2SvnTestCase
.__init
__(self
, 'branch-delete-first', **kw
)
1456 # See test-data/branch-delete-first-cvsrepos/README.
1458 # The conversion will fail if the bug is present, and
1459 # ensure_conversion would raise Failure.
1460 conv
= self
.ensure_conversion()
1462 branches
= conv
.symbols
.get('branches', 'branches')
1464 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1465 if conv
.path_exists(branches
, 'branch-1', 'file'):
1467 if conv
.path_exists(branches
, 'branch-2', 'file'):
1469 if not conv
.path_exists(branches
, 'branch-3', 'file'):
1473 def nonascii_filenames():
1474 "non ascii files converted incorrectly"
1477 # on a en_US.iso-8859-1 machine this test fails with
1478 # svn: Can't recode ...
1480 # as described in the issue
1482 # on a en_US.UTF-8 machine this test fails with
1483 # svn: Malformed XML ...
1485 # which means at least it fails. Unfortunately it won't fail
1486 # with the same error...
1488 # mangle current locale settings so we know we're not running
1489 # a UTF-8 locale (which does not exhibit this problem)
1490 current_locale
= locale
.getlocale()
1491 new_locale
= 'en_US.ISO8859-1'
1492 locale_changed
= None
1494 # From http://docs.python.org/lib/module-sys.html
1496 # getfilesystemencoding():
1498 # Return the name of the encoding used to convert Unicode filenames
1499 # into system file names, or None if the system default encoding is
1500 # used. The result value depends on the operating system:
1502 # - On Windows 9x, the encoding is ``mbcs''.
1503 # - On Mac OS X, the encoding is ``utf-8''.
1504 # - On Unix, the encoding is the user's preference according to the
1505 # result of nl_langinfo(CODESET), or None if the
1506 # nl_langinfo(CODESET) failed.
1507 # - On Windows NT+, file names are Unicode natively, so no conversion is
1510 # So we're going to skip this test on Mac OS X for now.
1511 if sys
.platform
== "darwin":
1512 raise svntest
.Skip()
1515 # change locale to non-UTF-8 locale to generate latin1 names
1516 locale
.setlocale(locale
.LC_ALL
, # this might be too broad?
1519 except locale
.Error
:
1520 raise svntest
.Skip()
1523 srcrepos_path
= os
.path
.join(test_data_dir
,'main-cvsrepos')
1524 dstrepos_path
= os
.path
.join(test_data_dir
,'non-ascii-cvsrepos')
1525 if not os
.path
.exists(dstrepos_path
):
1526 # create repos from existing main repos
1527 shutil
.copytree(srcrepos_path
, dstrepos_path
)
1528 base_path
= os
.path
.join(dstrepos_path
, 'single-files')
1529 shutil
.copyfile(os
.path
.join(base_path
, 'twoquick,v'),
1530 os
.path
.join(base_path
, 'two\366uick,v'))
1531 new_path
= os
.path
.join(dstrepos_path
, 'single\366files')
1532 os
.rename(base_path
, new_path
)
1534 conv
= ensure_conversion('non-ascii', args
=['--encoding=latin1'])
1537 locale
.setlocale(locale
.LC_ALL
, current_locale
)
1538 safe_rmtree(dstrepos_path
)
1541 class UnicodeTest(Cvs2SvnTestCase
):
1542 "metadata contains unicode"
1544 warning_pattern
= r
'ERROR\: There were warnings converting .* messages'
1546 def __init__(self
, name
, warning_expected
, **kw
):
1547 if warning_expected
:
1548 error_re
= self
.warning_pattern
1552 Cvs2SvnTestCase
.__init
__(self
, name
, error_re
=error_re
, **kw
)
1553 self
.warning_expected
= warning_expected
1557 # ensure the availability of the "utf_8" encoding:
1558 u
'a'.encode('utf_8').decode('utf_8')
1560 raise svntest
.Skip()
1562 self
.ensure_conversion()
1565 class UnicodeAuthor(UnicodeTest
):
1566 "author name contains unicode"
1568 def __init__(self
, warning_expected
, **kw
):
1569 UnicodeTest
.__init
__(self
, 'unicode-author', warning_expected
, **kw
)
1572 class UnicodeLog(UnicodeTest
):
1573 "log message contains unicode"
1575 def __init__(self
, warning_expected
, **kw
):
1576 UnicodeTest
.__init
__(self
, 'unicode-log', warning_expected
, **kw
)
1579 def vendor_branch_sameness():
1580 "avoid spurious changes for initial revs"
1581 conv
= ensure_conversion(
1582 'vendor-branch-sameness', args
=['--keep-trivial-imports']
1585 # The following files are in this repository:
1587 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1588 # the same contents, the file's default branch is 1.1.1,
1589 # and both revisions are in state 'Exp'.
1591 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1592 # 1.1 (the addition of a line of text).
1594 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1596 # d.txt: This file was created by 'cvs add' instead of import, so
1597 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1598 # The timestamp on the add is exactly the same as for the
1599 # imports of the other files.
1601 # e.txt: Like a.txt, except that the log message for revision 1.1
1602 # is not the standard import log message.
1604 # (Aside from e.txt, the log messages for the same revisions are the
1605 # same in all files.)
1607 # We expect that only a.txt is recognized as an import whose 1.1
1608 # revision can be omitted. The other files should be added on trunk
1609 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1610 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1611 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1612 # and c.txt should be 'D'eleted.
1615 conv
.logs
[rev
].check('Initial revision', (
1616 ('/%(trunk)s/proj', 'A'),
1617 ('/%(trunk)s/proj/b.txt', 'A'),
1618 ('/%(trunk)s/proj/c.txt', 'A'),
1619 ('/%(trunk)s/proj/d.txt', 'A'),
1622 conv
.logs
[rev
+ 1].check(sym_log_msg('vbranchA'), (
1623 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1624 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1627 conv
.logs
[rev
+ 2].check('First vendor branch revision.', (
1628 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1629 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1630 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1633 conv
.logs
[rev
+ 3].check('This commit was generated by cvs2svn '
1634 'to compensate for changes in r4,', (
1635 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1636 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1637 ('/%(trunk)s/proj/c.txt', 'D'),
1641 conv
.logs
[rev
].check('This log message is not the standard', (
1642 ('/%(trunk)s/proj/e.txt', 'A'),
1645 conv
.logs
[rev
+ 2].check('First vendor branch revision', (
1646 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1649 conv
.logs
[rev
+ 3].check('This commit was generated by cvs2svn '
1650 'to compensate for changes in r9,', (
1651 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1655 def vendor_branch_trunk_only():
1656 "handle vendor branches with --trunk-only"
1657 conv
= ensure_conversion('vendor-branch-sameness', args
=['--trunk-only'])
1660 conv
.logs
[rev
].check('Initial revision', (
1661 ('/%(trunk)s/proj', 'A'),
1662 ('/%(trunk)s/proj/b.txt', 'A'),
1663 ('/%(trunk)s/proj/c.txt', 'A'),
1664 ('/%(trunk)s/proj/d.txt', 'A'),
1667 conv
.logs
[rev
+ 1].check('First vendor branch revision', (
1668 ('/%(trunk)s/proj/a.txt', 'A'),
1669 ('/%(trunk)s/proj/b.txt', 'M'),
1670 ('/%(trunk)s/proj/c.txt', 'D'),
1673 conv
.logs
[rev
+ 2].check('This log message is not the standard', (
1674 ('/%(trunk)s/proj/e.txt', 'A'),
1677 conv
.logs
[rev
+ 3].check('First vendor branch revision', (
1678 ('/%(trunk)s/proj/e.txt', 'M'),
1682 def default_branches():
1683 "handle default branches correctly"
1684 conv
= ensure_conversion('default-branches')
1686 # There are seven files in the repository:
1689 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1690 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1691 # committed (thus losing the default branch "1.1.1"), then
1692 # 1.1.1.4 was imported. All vendor import release tags are
1696 # Like a.txt, but without rev 1.2.
1699 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1702 # Same as the previous two, but 1.1.1 branch is unlabeled.
1705 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1707 # deleted-on-vendor-branch.txt,v:
1708 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1710 # added-then-imported.txt,v:
1711 # Added with 'cvs add' to create 1.1, then imported with
1712 # completely different contents to create 1.1.1.1, therefore
1713 # never had a default branch.
1716 conv
.logs
[2].check("Import (vbranchA, vtag-1).", (
1717 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1718 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1719 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1720 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1721 ('/%(branches)s/vbranchA', 'A'),
1722 ('/%(branches)s/vbranchA/proj', 'A'),
1723 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1724 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1725 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1726 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1729 conv
.logs
[3].check("This commit was generated by cvs2svn "
1730 "to compensate for changes in r2,", (
1731 ('/%(trunk)s/proj', 'A'),
1732 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1733 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1734 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1735 ('/%(trunk)s/proj/d.txt '
1736 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1737 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1738 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1739 ('/%(trunk)s/proj/e.txt '
1740 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1743 conv
.logs
[4].check(sym_log_msg('vtag-1',1), (
1744 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1745 ('/%(tags)s/vtag-1/proj/d.txt '
1746 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1749 conv
.logs
[5].check("Import (vbranchA, vtag-2).", (
1750 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1751 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1752 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1753 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1754 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1755 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1758 conv
.logs
[6].check("This commit was generated by cvs2svn "
1759 "to compensate for changes in r5,", (
1760 ('/%(trunk)s/proj/a.txt '
1761 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1762 ('/%(trunk)s/proj/b.txt '
1763 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1764 ('/%(trunk)s/proj/c.txt '
1765 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1766 ('/%(trunk)s/proj/d.txt '
1767 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1768 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1769 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1771 ('/%(trunk)s/proj/e.txt '
1772 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1775 conv
.logs
[7].check(sym_log_msg('vtag-2',1), (
1776 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1777 ('/%(tags)s/vtag-2/proj/d.txt '
1778 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1781 conv
.logs
[8].check("Import (vbranchA, vtag-3).", (
1782 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1783 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1784 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1785 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1786 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1787 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1790 conv
.logs
[9].check("This commit was generated by cvs2svn "
1791 "to compensate for changes in r8,", (
1792 ('/%(trunk)s/proj/a.txt '
1793 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1794 ('/%(trunk)s/proj/b.txt '
1795 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1796 ('/%(trunk)s/proj/c.txt '
1797 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1798 ('/%(trunk)s/proj/d.txt '
1799 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1800 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1801 ('/%(trunk)s/proj/e.txt '
1802 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1805 conv
.logs
[10].check(sym_log_msg('vtag-3',1), (
1806 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1807 ('/%(tags)s/vtag-3/proj/d.txt '
1808 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1809 ('/%(tags)s/vtag-3/proj/e.txt '
1810 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1813 conv
.logs
[11].check("First regular commit, to a.txt, on vtag-3.", (
1814 ('/%(trunk)s/proj/a.txt', 'M'),
1817 conv
.logs
[12].check("Add a file to the working copy.", (
1818 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1821 conv
.logs
[13].check(sym_log_msg('vbranchA'), (
1822 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1823 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1826 conv
.logs
[14].check("Import (vbranchA, vtag-4).", (
1827 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1828 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1829 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1830 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1831 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1832 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1833 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1836 conv
.logs
[15].check("This commit was generated by cvs2svn "
1837 "to compensate for changes in r14,", (
1838 ('/%(trunk)s/proj/b.txt '
1839 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1840 ('/%(trunk)s/proj/c.txt '
1841 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1842 ('/%(trunk)s/proj/d.txt '
1843 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1844 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1845 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1847 ('/%(trunk)s/proj/e.txt '
1848 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1851 conv
.logs
[16].check(sym_log_msg('vtag-4',1), (
1852 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1853 ('/%(tags)s/vtag-4/proj/d.txt '
1854 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
1858 def default_branches_trunk_only():
1859 "handle default branches with --trunk-only"
1861 conv
= ensure_conversion('default-branches', args
=['--trunk-only'])
1863 conv
.logs
[2].check("Import (vbranchA, vtag-1).", (
1864 ('/%(trunk)s/proj', 'A'),
1865 ('/%(trunk)s/proj/a.txt', 'A'),
1866 ('/%(trunk)s/proj/b.txt', 'A'),
1867 ('/%(trunk)s/proj/c.txt', 'A'),
1868 ('/%(trunk)s/proj/d.txt', 'A'),
1869 ('/%(trunk)s/proj/e.txt', 'A'),
1870 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1873 conv
.logs
[3].check("Import (vbranchA, vtag-2).", (
1874 ('/%(trunk)s/proj/a.txt', 'M'),
1875 ('/%(trunk)s/proj/b.txt', 'M'),
1876 ('/%(trunk)s/proj/c.txt', 'M'),
1877 ('/%(trunk)s/proj/d.txt', 'M'),
1878 ('/%(trunk)s/proj/e.txt', 'M'),
1879 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
1882 conv
.logs
[4].check("Import (vbranchA, vtag-3).", (
1883 ('/%(trunk)s/proj/a.txt', 'M'),
1884 ('/%(trunk)s/proj/b.txt', 'M'),
1885 ('/%(trunk)s/proj/c.txt', 'M'),
1886 ('/%(trunk)s/proj/d.txt', 'M'),
1887 ('/%(trunk)s/proj/e.txt', 'M'),
1888 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1891 conv
.logs
[5].check("First regular commit, to a.txt, on vtag-3.", (
1892 ('/%(trunk)s/proj/a.txt', 'M'),
1895 conv
.logs
[6].check("Add a file to the working copy.", (
1896 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1899 conv
.logs
[7].check("Import (vbranchA, vtag-4).", (
1900 ('/%(trunk)s/proj/b.txt', 'M'),
1901 ('/%(trunk)s/proj/c.txt', 'M'),
1902 ('/%(trunk)s/proj/d.txt', 'M'),
1903 ('/%(trunk)s/proj/e.txt', 'M'),
1904 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1908 def default_branch_and_1_2():
1909 "do not allow 1.2 revision with default branch"
1911 conv
= ensure_conversion(
1912 'default-branch-and-1-2',
1914 r
'.*File \'.*\' has default branch
=1\
.1\
.1 but also a revision
1\
.2'
1919 def compose_tag_three_sources():
1920 "compose a tag from three sources"
1921 conv = ensure_conversion('compose
-tag
-three
-sources
')
1923 conv.logs[2].check("Add on trunk", (
1924 ('/%(trunk)s/tagged
-on
-trunk
-1.1', 'A
'),
1925 ('/%(trunk)s/tagged
-on
-trunk
-1.2-a
', 'A
'),
1926 ('/%(trunk)s/tagged
-on
-trunk
-1.2-b
', 'A
'),
1927 ('/%(trunk)s/tagged
-on
-b1
', 'A
'),
1928 ('/%(trunk)s/tagged
-on
-b2
', 'A
'),
1931 conv.logs[3].check(sym_log_msg('b1
'), (
1932 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A
'),
1935 conv.logs[4].check(sym_log_msg('b2
'), (
1936 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A
'),
1939 conv.logs[5].check("Commit on branch b1", (
1940 ('/%(branches)s/b1
/tagged
-on
-trunk
-1.1', 'M
'),
1941 ('/%(branches)s/b1
/tagged
-on
-trunk
-1.2-a
', 'M
'),
1942 ('/%(branches)s/b1
/tagged
-on
-trunk
-1.2-b
', 'M
'),
1943 ('/%(branches)s/b1
/tagged
-on
-b1
', 'M
'),
1944 ('/%(branches)s/b1
/tagged
-on
-b2
', 'M
'),
1947 conv.logs[6].check("Commit on branch b2", (
1948 ('/%(branches)s/b2
/tagged
-on
-trunk
-1.1', 'M
'),
1949 ('/%(branches)s/b2
/tagged
-on
-trunk
-1.2-a
', 'M
'),
1950 ('/%(branches)s/b2
/tagged
-on
-trunk
-1.2-b
', 'M
'),
1951 ('/%(branches)s/b2
/tagged
-on
-b1
', 'M
'),
1952 ('/%(branches)s/b2
/tagged
-on
-b2
', 'M
'),
1955 conv.logs[7].check("Commit again on trunk", (
1956 ('/%(trunk)s/tagged
-on
-trunk
-1.2-a
', 'M
'),
1957 ('/%(trunk)s/tagged
-on
-trunk
-1.2-b
', 'M
'),
1958 ('/%(trunk)s/tagged
-on
-trunk
-1.1', 'M
'),
1959 ('/%(trunk)s/tagged
-on
-b1
', 'M
'),
1960 ('/%(trunk)s/tagged
-on
-b2
', 'M
'),
1963 conv.logs[8].check(sym_log_msg('T
',1), (
1964 ('/%(tags)s/T (from /%(trunk)s:7)', 'A
'),
1965 ('/%(tags)s/T
/tagged
-on
-trunk
-1.1 '
1966 '(from /%(trunk)s/tagged
-on
-trunk
-1.1:2)', 'R
'),
1967 ('/%(tags)s/T
/tagged
-on
-b1 (from /%(branches)s/b1
/tagged
-on
-b1
:5)', 'R
'),
1968 ('/%(tags)s/T
/tagged
-on
-b2 (from /%(branches)s/b2
/tagged
-on
-b2
:6)', 'R
'),
1972 def pass5_when_to_fill():
1973 "reserve a svn revnum for a fill only when required"
1974 # The conversion will fail if the bug is present, and
1975 # ensure_conversion would raise Failure.
1976 conv = ensure_conversion('pass5
-when
-to
-fill
')
1979 class EmptyTrunk(Cvs2SvnTestCase):
1980 "don't
break when the trunk
is empty
"
1982 def __init__(self, **kw):
1983 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
1986 # The conversion will fail if the bug is present, and
1987 # ensure_conversion would raise Failure.
1988 conv = self.ensure_conversion()
1991 def no_spurious_svn_commits():
1992 "ensure that we don
't create any spurious commits"
1993 conv = ensure_conversion('phoenix
')
1995 # Check spurious commit that could be created in
1996 # SVNCommitCreator._pre_commit()
1998 # (When you add a file on a branch, CVS creates a trunk revision
1999 # in state 'dead
'. If the log message of that commit is equal to
2000 # the one that CVS generates, we do not ever create a 'fill
'
2001 # SVNCommit for it.)
2003 # and spurious commit that could be created in
2004 # SVNCommitCreator._commit()
2006 # (When you add a file on a branch, CVS creates a trunk revision
2007 # in state 'dead
'. If the log message of that commit is equal to
2008 # the one that CVS generates, we do not create a primary SVNCommit
2010 conv.logs[17].check('File added on branch xiphophorus
', (
2011 ('/%(branches)s/xiphophorus
/added
-on
-branch
.txt
', 'A
'),
2014 # Check to make sure that a commit *is* generated:
2015 # (When you add a file on a branch, CVS creates a trunk revision
2016 # in state 'dead
'. If the log message of that commit is NOT equal
2017 # to the one that CVS generates, we create a primary SVNCommit to
2018 # serve as a home for the log message in question.
2019 conv.logs[18].check('file added
-on
-branch2
.txt was initially added on
'
2020 + 'branch xiphophorus
,\nand this log message was tweaked
', ())
2022 # Check spurious commit that could be created in
2023 # SVNCommitCreator._commit_symbols().
2024 conv.logs[19].check('This
file was also added on branch xiphophorus
,', (
2025 ('/%(branches)s/xiphophorus
/added
-on
-branch2
.txt
', 'A
'),
2029 class PeerPathPruning(Cvs2SvnTestCase):
2030 "make sure that filling prunes paths correctly"
2032 def __init__(self, **kw):
2033 Cvs2SvnTestCase.__init__(self, 'peer
-path
-pruning
', **kw)
2036 conv = self.ensure_conversion()
2037 conv.logs[6].check(sym_log_msg('BRANCH
'), (
2038 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A
'),
2039 ('/%(branches)s/BRANCH
/bar
', 'D
'),
2040 ('/%(branches)s/BRANCH
/foo (from /%(trunk)s/foo
:5)', 'R
'),
2044 def invalid_closings_on_trunk():
2045 "verify correct revs are copied to default branches"
2046 # The conversion will fail if the bug is present, and
2047 # ensure_conversion would raise Failure.
2048 conv = ensure_conversion('invalid
-closings
-on
-trunk
')
2051 def individual_passes():
2052 "run each pass individually"
2053 conv = ensure_conversion('main
')
2054 conv2 = ensure_conversion('main
', passbypass=1)
2056 if conv.logs != conv2.logs:
2061 "reveal a big bug in our resync algorithm"
2062 # This will fail if the bug is present
2063 conv = ensure_conversion('resync
-bug
')
2066 def branch_from_default_branch():
2067 "reveal a bug in our default branch detection code"
2068 conv = ensure_conversion('branch
-from-default
-branch
')
2070 # This revision will be a default branch synchronization only
2071 # if cvs2svn is correctly determining default branch revisions.
2073 # The bug was that cvs2svn was treating revisions on branches off of
2074 # default branches as default branch revisions, resulting in
2075 # incorrectly regarding the branch off of the default branch as a
2076 # non-trunk default branch. Crystal clear? I thought so. See
2077 # issue #42 for more incoherent blathering.
2078 conv.logs[5].check("This commit was generated by cvs2svn", (
2079 ('/%(trunk)s/proj
/file.txt
'
2080 '(from /%(branches)s/upstream
/proj
/file.txt
:4)', 'R
'),
2084 def file_in_attic_too():
2085 "die if a file exists in and out of the attic"
2087 'file-in-attic
-too
',
2089 r'.*A CVS repository cannot contain both
'
2090 r'(.*)' + re.escape(os.sep) + r'(.*) '
2092 r'\
1' + re.escape(os.sep) + r'Attic
' + re.escape(os.sep) + r'\
2'
2097 def retain_file_in_attic_too():
2098 "test --retain-conflicting-attic-files option"
2099 conv = ensure_conversion(
2100 'file-in-attic
-too
', args=['--retain
-conflicting
-attic
-files
'])
2101 if not conv.path_exists('trunk
', 'file.txt
'):
2103 if not conv.path_exists('trunk
', 'Attic
', 'file.txt
'):
2107 def symbolic_name_filling_guide():
2108 "reveal a big bug in our SymbolFillingGuide"
2109 # This will fail if the bug is present
2110 conv = ensure_conversion('symbolic
-name
-overfill
')
2113 # Helpers for tests involving file contents and properties.
2115 class NodeTreeWalkException:
2116 "Exception class for node tree traversals."
2119 def node_for_path(node, path):
2120 "In the tree rooted under SVNTree NODE, return the node at PATH."
2121 if node.name != '__SVN_ROOT_NODE
':
2122 raise NodeTreeWalkException()
2123 path = path.strip('/')
2124 components = path.split('/')
2125 for component in components:
2126 node = get_child(node, component)
2129 # Helper for tests involving properties.
2130 def props_for_path(node, path):
2131 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2132 return node_for_path(node, path).props
2135 class EOLMime(Cvs2SvnPropertiesTestCase):
2136 """eol settings and mime types together
2138 The files are as follows:
2140 trunk/foo.txt: no -kb, mime file says nothing.
2141 trunk/foo.xml: no -kb, mime file says text.
2142 trunk/foo.zip: no -kb, mime file says non-text.
2143 trunk/foo.bin: has -kb, mime file says nothing.
2144 trunk/foo.csv: has -kb, mime file says text.
2145 trunk/foo.dbf: has -kb, mime file says non-text.
2148 def __init__(self, args, **kw):
2149 # TODO: It's a bit klugey to construct this path here
. But so far
2150 # there's only one test with a mime.types file. If we have more,
2151 # we should abstract this into some helper, which would be located
2152 # near ensure_conversion(). Note that it is a convention of this
2153 # test suite for a mime.types file to be located in the top level
2154 # of the CVS repository to which it applies.
2155 self
.mime_path
= os
.path
.join(
2156 test_data_dir
, 'eol-mime-cvsrepos', 'mime.types')
2158 Cvs2SvnPropertiesTestCase
.__init
__(
2160 props_to_test
=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2161 args
=['--mime-types=%s' % self
.mime_path
] + args
,
2165 # We do four conversions. Each time, we pass --mime-types=FILE with
2166 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2167 # Thus there's one conversion with neither flag, one with just the
2168 # former, one with just the latter, and one with both.
2171 # Neither --no-default-eol nor --eol-from-mime-type:
2172 eol_mime1
= EOLMime(
2176 ('trunk/foo.txt', [None, None, None]),
2177 ('trunk/foo.xml', [None, 'text/xml', None]),
2178 ('trunk/foo.zip', [None, 'application/zip', None]),
2179 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2180 ('trunk/foo.csv', [None, 'text/csv', None]),
2181 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2185 # Just --no-default-eol, not --eol-from-mime-type:
2186 eol_mime2
= EOLMime(
2188 args
=['--default-eol=native'],
2190 ('trunk/foo.txt', ['native', None, KEYWORDS
]),
2191 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS
]),
2192 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS
]),
2193 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2194 ('trunk/foo.csv', [None, 'text/csv', None]),
2195 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2199 # Just --eol-from-mime-type, not --no-default-eol:
2200 eol_mime3
= EOLMime(
2202 args
=['--eol-from-mime-type'],
2204 ('trunk/foo.txt', [None, None, None]),
2205 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS
]),
2206 ('trunk/foo.zip', [None, 'application/zip', None]),
2207 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2208 ('trunk/foo.csv', [None, 'text/csv', None]),
2209 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2213 # Both --no-default-eol and --eol-from-mime-type:
2214 eol_mime4
= EOLMime(
2216 args
=['--eol-from-mime-type', '--default-eol=native'],
2218 ('trunk/foo.txt', ['native', None, KEYWORDS
]),
2219 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS
]),
2220 ('trunk/foo.zip', [None, 'application/zip', None]),
2221 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2222 ('trunk/foo.csv', [None, 'text/csv', None]),
2223 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2227 cvs_revnums_off
= Cvs2SvnPropertiesTestCase(
2229 description
='test non-setting of cvs2svn:cvs-rev property',
2231 props_to_test
=['cvs2svn:cvs-rev'],
2233 ('trunk/foo.txt', [None]),
2234 ('trunk/foo.xml', [None]),
2235 ('trunk/foo.zip', [None]),
2236 ('trunk/foo.bin', [None]),
2237 ('trunk/foo.csv', [None]),
2238 ('trunk/foo.dbf', [None]),
2242 cvs_revnums_on
= Cvs2SvnPropertiesTestCase(
2244 description
='test setting of cvs2svn:cvs-rev property',
2245 args
=['--cvs-revnums'],
2246 props_to_test
=['cvs2svn:cvs-rev'],
2248 ('trunk/foo.txt', ['1.2']),
2249 ('trunk/foo.xml', ['1.2']),
2250 ('trunk/foo.zip', ['1.2']),
2251 ('trunk/foo.bin', ['1.2']),
2252 ('trunk/foo.csv', ['1.2']),
2253 ('trunk/foo.dbf', ['1.2']),
2257 keywords
= Cvs2SvnPropertiesTestCase(
2259 description
='test setting of svn:keywords property among others',
2260 args
=['--default-eol=native'],
2261 props_to_test
=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2263 ('trunk/foo.default', [KEYWORDS
, 'native', None]),
2264 ('trunk/foo.kkvl', [KEYWORDS
, 'native', None]),
2265 ('trunk/foo.kkv', [KEYWORDS
, 'native', None]),
2266 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2267 ('trunk/foo.kk', [None, 'native', None]),
2268 ('trunk/foo.ko', [None, 'native', None]),
2269 ('trunk/foo.kv', [None, 'native', None]),
2274 "test setting of svn:ignore property"
2275 conv
= ensure_conversion('cvsignore')
2276 wc_tree
= conv
.get_wc_tree()
2277 topdir_props
= props_for_path(wc_tree
, 'trunk/proj')
2278 subdir_props
= props_for_path(wc_tree
, '/trunk/proj/subdir')
2280 if topdir_props
['svn:ignore'] != \
2281 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n\n':
2284 if subdir_props
['svn:ignore'] != \
2285 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n\n':
2290 "test that CVS can still do what RCS can't"
2291 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2292 conv
= ensure_conversion('requires-cvs', args
=["--use-cvs"])
2294 atsign_contents
= file(conv
.get_wc("trunk", "atsign-add")).read()
2295 cl_contents
= file(conv
.get_wc("trunk", "client_lock.idl")).read()
2297 if atsign_contents
[-1:] == "@":
2299 if cl_contents
.find("gregh\n//\n//Integration for locks") < 0:
2302 if not (conv
.logs
[21].author
== "William Lyon Phelps III" and
2303 conv
.logs
[20].author
== "j random"):
2307 def questionable_branch_names():
2308 "test that we can handle weird branch names"
2309 conv
= ensure_conversion('questionable-symbols')
2310 # If the conversion succeeds, then we're okay. We could check the
2311 # actual branch paths, too, but the main thing is to know that the
2312 # conversion doesn't fail.
2315 def questionable_tag_names():
2316 "test that we can handle weird tag names"
2317 conv
= ensure_conversion('questionable-symbols')
2318 conv
.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2319 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2321 conv
.find_tag_log('TagWith/Backslash_E').check(
2322 sym_log_msg('TagWith/Backslash_E',1),
2324 ('/%(tags)s/TagWith', 'A'),
2325 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2328 conv
.find_tag_log('TagWith/Slash_Z').check(
2329 sym_log_msg('TagWith/Slash_Z',1),
2331 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2336 def revision_reorder_bug():
2337 "reveal a bug that reorders file revisions"
2338 conv
= ensure_conversion('revision-reorder-bug')
2339 # If the conversion succeeds, then we're okay. We could check the
2340 # actual revisions, too, but the main thing is to know that the
2341 # conversion doesn't fail.
2345 "test that exclude really excludes everything"
2346 conv
= ensure_conversion('main', args
=['--exclude=.*'])
2347 for log
in conv
.logs
.values():
2348 for item
in log
.changed_paths
.keys():
2349 if item
.startswith('/branches/') or item
.startswith('/tags/'):
2353 def vendor_branch_delete_add():
2354 "add trunk file that was deleted on vendor branch"
2355 # This will error if the bug is present
2356 conv
= ensure_conversion('vendor-branch-delete-add')
2359 def resync_pass2_pull_forward():
2360 "ensure pass2 doesn't pull rev too far forward"
2361 conv
= ensure_conversion('resync-pass2-pull-forward')
2362 # If the conversion succeeds, then we're okay. We could check the
2363 # actual revisions, too, but the main thing is to know that the
2364 # conversion doesn't fail.
2368 "only LFs for svn:eol-style=native files"
2369 conv
= ensure_conversion('native-eol', args
=['--default-eol=native'])
2370 lines
= run_program(svntest
.main
.svnadmin_binary
, None, 'dump', '-q',
2372 # Verify that all files in the dump have LF EOLs. We're actually
2373 # testing the whole dump file, but the dump file itself only uses
2374 # LF EOLs, so we're safe.
2376 if line
[-1] != '\n' or line
[:-1].find('\r') != -1:
2381 "reveal a bug that created a branch twice"
2382 conv
= ensure_conversion('double-fill')
2383 # If the conversion succeeds, then we're okay. We could check the
2384 # actual revisions, too, but the main thing is to know that the
2385 # conversion doesn't fail.
2389 "reveal a second bug that created a branch twice"
2390 conv
= ensure_conversion('double-fill2')
2391 conv
.logs
[6].check_msg(sym_log_msg('BRANCH1'))
2392 conv
.logs
[7].check_msg(sym_log_msg('BRANCH2'))
2394 # This check should fail:
2395 conv
.logs
[8].check_msg(sym_log_msg('BRANCH2'))
2399 raise Failure('Symbol filled twice in a row')
2402 def resync_pass2_push_backward():
2403 "ensure pass2 doesn't push rev too far backward"
2404 conv
= ensure_conversion('resync-pass2-push-backward')
2405 # If the conversion succeeds, then we're okay. We could check the
2406 # actual revisions, too, but the main thing is to know that the
2407 # conversion doesn't fail.
2411 "reveal a bug that added a branch file twice"
2412 conv
= ensure_conversion('double-add')
2413 # If the conversion succeeds, then we're okay. We could check the
2414 # actual revisions, too, but the main thing is to know that the
2415 # conversion doesn't fail.
2418 def bogus_branch_copy():
2419 "reveal a bug that copies a branch file wrongly"
2420 conv
= ensure_conversion('bogus-branch-copy')
2421 # If the conversion succeeds, then we're okay. We could check the
2422 # actual revisions, too, but the main thing is to know that the
2423 # conversion doesn't fail.
2426 def nested_ttb_directories():
2427 "require error if ttb directories are not disjoint"
2429 {'trunk' : 'a', 'branches' : 'a',},
2430 {'trunk' : 'a', 'tags' : 'a',},
2431 {'branches' : 'a', 'tags' : 'a',},
2432 # This option conflicts with the default trunk path:
2433 {'branches' : 'trunk',},
2434 # Try some nested directories:
2435 {'trunk' : 'a', 'branches' : 'a/b',},
2436 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2437 {'branches' : 'a', 'tags' : 'a/b',},
2440 for opts
in opts_list
:
2442 'main', error_re
=r
'The following paths are not disjoint\:', **opts
2446 class AutoProps(Cvs2SvnPropertiesTestCase
):
2449 The files are as follows:
2451 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2452 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2453 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2454 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2455 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2456 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2457 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2458 trunk/foo.UPCASE1: no -kb, no mime type.
2459 trunk/foo.UPCASE2: no -kb, no mime type.
2462 def __init__(self
, args
, **kw
):
2463 ### TODO: It's a bit klugey to construct this path here. See also
2464 ### the comment in eol_mime().
2465 auto_props_path
= os
.path
.join(
2466 test_data_dir
, 'eol-mime-cvsrepos', 'auto-props')
2468 Cvs2SvnPropertiesTestCase
.__init
__(
2478 '--auto-props=%s' % auto_props_path
,
2479 '--eol-from-mime-type'
2484 auto_props_ignore_case
= AutoProps(
2485 description
="test auto-props",
2486 args
=['--default-eol=native'],
2488 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS
, None]),
2489 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS
, None]),
2490 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2491 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2493 ['bin', None, 'application/octet-stream', None, '']),
2494 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2496 ['dbf', None, 'application/what-is-dbf', None, None]),
2497 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS
, None]),
2498 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS
, None]),
2502 def ctrl_char_in_filename():
2503 "do not allow control characters in filenames"
2506 srcrepos_path
= os
.path
.join(test_data_dir
,'main-cvsrepos')
2507 dstrepos_path
= os
.path
.join(test_data_dir
,'ctrl-char-filename-cvsrepos')
2508 if os
.path
.exists(dstrepos_path
):
2509 safe_rmtree(dstrepos_path
)
2511 # create repos from existing main repos
2512 shutil
.copytree(srcrepos_path
, dstrepos_path
)
2513 base_path
= os
.path
.join(dstrepos_path
, 'single-files')
2515 shutil
.copyfile(os
.path
.join(base_path
, 'twoquick,v'),
2516 os
.path
.join(base_path
, 'two\rquick,v'))
2518 # Operating systems that don't allow control characters in
2519 # filenames will hopefully have thrown an exception; in that
2520 # case, just skip this test.
2521 raise svntest
.Skip()
2523 conv
= ensure_conversion(
2524 'ctrl-char-filename',
2525 error_re
=(r
'.*Character .* in filename .* '
2526 r
'is not supported by Subversion\.'),
2529 safe_rmtree(dstrepos_path
)
2532 def commit_dependencies():
2533 "interleaved and multi-branch commits to same files"
2534 conv
= ensure_conversion("commit-dependencies")
2535 conv
.logs
[2].check('adding', (
2536 ('/%(trunk)s/interleaved', 'A'),
2537 ('/%(trunk)s/interleaved/file1', 'A'),
2538 ('/%(trunk)s/interleaved/file2', 'A'),
2540 conv
.logs
[3].check('big commit', (
2541 ('/%(trunk)s/interleaved/file1', 'M'),
2542 ('/%(trunk)s/interleaved/file2', 'M'),
2544 conv
.logs
[4].check('dependant small commit', (
2545 ('/%(trunk)s/interleaved/file1', 'M'),
2547 conv
.logs
[5].check('adding', (
2548 ('/%(trunk)s/multi-branch', 'A'),
2549 ('/%(trunk)s/multi-branch/file1', 'A'),
2550 ('/%(trunk)s/multi-branch/file2', 'A'),
2552 conv
.logs
[6].check(sym_log_msg("branch"), (
2553 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2554 ('/%(branches)s/branch/interleaved', 'D'),
2556 conv
.logs
[7].check('multi-branch-commit', (
2557 ('/%(trunk)s/multi-branch/file1', 'M'),
2558 ('/%(trunk)s/multi-branch/file2', 'M'),
2559 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2560 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2564 def double_branch_delete():
2565 "fill branches before modifying files on them"
2566 conv
= ensure_conversion('double-branch-delete')
2568 # Test for issue #102. The file IMarshalledValue.java is branched,
2569 # deleted, readded on the branch, and then deleted again. If the
2570 # fill for the file on the branch is postponed until after the
2571 # modification, the file will end up live on the branch instead of
2572 # dead! Make sure it happens at the right time.
2574 conv
.logs
[6].check('JBAS-2436 - Adding LGPL Header2', (
2575 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2578 conv
.logs
[7].check('JBAS-3025 - Removing dependency', (
2579 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2583 def symbol_mismatches():
2584 "error for conflicting tag/branch"
2588 args
=['--symbol-default=strict'],
2589 error_re
=r
'.*Problems determining how symbols should be converted',
2593 def overlook_symbol_mismatches():
2594 "overlook conflicting tag/branch when --trunk-only"
2596 # This is a test for issue #85.
2598 ensure_conversion('symbol-mess', args
=['--trunk-only'])
2601 def force_symbols():
2602 "force symbols to be tags/branches"
2604 conv
= ensure_conversion(
2606 args
=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2607 if conv
.path_exists('tags', 'BRANCH') \
2608 or not conv
.path_exists('branches', 'BRANCH'):
2610 if not conv
.path_exists('tags', 'TAG') \
2611 or conv
.path_exists('branches', 'TAG'):
2613 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2614 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2616 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2617 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2621 def commit_blocks_tags():
2622 "commit prevents forced tag"
2624 basic_args
= ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2627 args
=(basic_args
+ ['--force-tag=BRANCH_WITH_COMMIT']),
2629 r
'.*The following branches cannot be forced to be tags '
2630 r
'because they have commits'
2635 def blocked_excludes():
2636 "error for blocked excludes"
2638 basic_args
= ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2639 for blocker
in ['BRANCH', 'COMMIT', 'UNNAMED']:
2643 args
=(basic_args
+ ['--exclude=BLOCKED_BY_%s' % blocker
]))
2644 raise MissingErrorException()
2649 def unblock_blocked_excludes():
2650 "excluding blocker removes blockage"
2652 basic_args
= ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2653 for blocker
in ['BRANCH', 'COMMIT']:
2656 args
=(basic_args
+ ['--exclude=BLOCKED_BY_%s' % blocker
,
2657 '--exclude=BLOCKING_%s' % blocker
]))
2660 def regexp_force_symbols():
2661 "force symbols via regular expressions"
2663 conv
= ensure_conversion(
2665 args
=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2666 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2667 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2669 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2670 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2674 def heuristic_symbol_default():
2675 "test 'heuristic' symbol default"
2677 conv
= ensure_conversion(
2678 'symbol-mess', args
=['--symbol-default=heuristic'])
2679 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2680 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2682 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2683 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2687 def branch_symbol_default():
2688 "test 'branch' symbol default"
2690 conv
= ensure_conversion(
2691 'symbol-mess', args
=['--symbol-default=branch'])
2692 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2693 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2695 if conv
.path_exists('tags', 'MOSTLY_TAG') \
2696 or not conv
.path_exists('branches', 'MOSTLY_TAG'):
2700 def tag_symbol_default():
2701 "test 'tag' symbol default"
2703 conv
= ensure_conversion(
2704 'symbol-mess', args
=['--symbol-default=tag'])
2705 if not conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2706 or conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2708 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2709 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2713 def symbol_transform():
2714 "test --symbol-transform"
2716 conv
= ensure_conversion(
2719 '--symbol-default=heuristic',
2720 '--symbol-transform=BRANCH:branch',
2721 '--symbol-transform=TAG:tag',
2722 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2724 if not conv
.path_exists('branches', 'branch'):
2726 if not conv
.path_exists('tags', 'tag'):
2728 if not conv
.path_exists('branches', 'MOSTLY.BRANCH'):
2730 if not conv
.path_exists('tags', 'MOSTLY.TAG'):
2734 def write_symbol_info():
2735 "test --write-symbol-info"
2739 'trunk', 'trunk', '.'],
2740 ['0', 'BLOCKED_BY_UNNAMED',
2741 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2742 ['0', 'BLOCKING_COMMIT',
2743 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2744 ['0', 'BLOCKED_BY_COMMIT',
2745 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2746 ['0', 'BLOCKING_BRANCH',
2747 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2748 ['0', 'BLOCKED_BY_BRANCH',
2749 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2750 ['0', 'MOSTLY_BRANCH',
2754 ['0', 'BRANCH_WITH_COMMIT',
2755 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2757 'branch', 'branches/BRANCH', '.trunk.'],
2759 'tag', 'tags/TAG', '.trunk.'],
2760 ['0', 'unlabeled-1.1.12.1.2',
2761 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2763 expected_lines
.sort()
2765 symbol_info_file
= os
.path
.join(tmp_dir
, 'symbol-mess-symbol-info.txt')
2770 '--symbol-default=strict',
2771 '--write-symbol-info=%s' % (symbol_info_file
,),
2772 '--passes=:CollateSymbolsPass',
2775 raise MissingErrorException()
2779 comment_re
= re
.compile(r
'^\s*\#')
2780 for l
in open(symbol_info_file
, 'r'):
2781 if comment_re
.match(l
):
2783 lines
.append(l
.strip().split())
2785 if lines
!= expected_lines
:
2786 s
= ['Symbol info incorrect\n']
2788 for diffline
in differ
.compare(
2789 [' '.join(line
) + '\n' for line
in expected_lines
],
2790 [' '.join(line
) + '\n' for line
in lines
],
2793 raise Failure(''.join(s
))
2797 "test --symbol-hints for setting branch/tag"
2799 conv
= ensure_conversion(
2800 'symbol-mess', symbol_hints_file
='symbol-mess-symbol-hints.txt',
2802 if not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2804 if not conv
.path_exists('tags', 'MOSTLY_TAG'):
2806 conv
.logs
[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2807 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2809 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2810 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
2812 conv
.logs
[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2813 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
2818 "test --symbol-hints for setting parent"
2820 conv
= ensure_conversion(
2821 'symbol-mess', symbol_hints_file
='symbol-mess-parent-hints.txt',
2823 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2824 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
2828 def parent_hints_invalid():
2829 "test --symbol-hints with an invalid parent"
2831 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2832 # this symbol hints file sets the preferred parent to BRANCH
2834 conv
= ensure_conversion(
2835 'symbol-mess', symbol_hints_file
='symbol-mess-parent-hints-invalid.txt',
2837 r
"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
2842 def parent_hints_wildcards():
2843 "test --symbol-hints wildcards"
2845 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2846 # this symbol hints file sets the preferred parent to BRANCH
2848 conv
= ensure_conversion(
2850 symbol_hints_file
='symbol-mess-parent-hints-wildcards.txt',
2852 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2853 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
2858 "test --symbol-hints for setting svn paths"
2860 conv
= ensure_conversion(
2861 'symbol-mess', symbol_hints_file
='symbol-mess-path-hints.txt',
2863 conv
.logs
[1].check('Standard project directories initialized by cvs2svn.', (
2866 ('/a/strange', 'A'),
2867 ('/a/strange/trunk', 'A'),
2868 ('/a/strange/trunk/path', 'A'),
2872 conv
.logs
[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2874 ('/special/tag', 'A'),
2875 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
2877 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2878 ('/special/other', 'A'),
2879 ('/special/other/branch', 'A'),
2880 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
2882 conv
.logs
[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2883 ('/special/branch', 'A'),
2884 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
2889 "test problem from issue 99"
2891 conv
= ensure_conversion('issue-99')
2895 "test problem from issue 100"
2897 conv
= ensure_conversion('issue-100')
2898 file1
= conv
.get_wc('trunk', 'file1.txt')
2899 if file(file1
).read() != 'file1.txt<1.2>\n':
2904 "test problem from issue 106"
2906 conv
= ensure_conversion('issue-106')
2909 def options_option():
2910 "use of the --options option"
2912 conv
= ensure_conversion('main', options_file
='cvs2svn.options')
2916 "multiproject conversion"
2918 conv
= ensure_conversion(
2919 'main', options_file
='cvs2svn-multiproject.options'
2921 conv
.logs
[1].check('Standard project directories initialized by cvs2svn.', (
2922 ('/partial-prune', 'A'),
2923 ('/partial-prune/trunk', 'A'),
2924 ('/partial-prune/branches', 'A'),
2925 ('/partial-prune/tags', 'A'),
2926 ('/partial-prune/releases', 'A'),
2931 "multiproject conversion with cross-project commits"
2933 conv
= ensure_conversion(
2934 'main', options_file
='cvs2svn-crossproject.options'
2938 def tag_with_no_revision():
2939 "tag defined but revision is deleted"
2941 conv
= ensure_conversion('tag-with-no-revision')
2944 def delete_cvsignore():
2945 "svn:ignore should vanish when .cvsignore does"
2947 # This is issue #81.
2949 conv
= ensure_conversion('delete-cvsignore')
2951 wc_tree
= conv
.get_wc_tree()
2952 props
= props_for_path(wc_tree
, 'trunk/proj')
2954 if props
.has_key('svn:ignore'):
2958 def repeated_deltatext():
2959 "ignore repeated deltatext blocks with warning"
2961 conv
= ensure_conversion('repeated-deltatext')
2962 warning_re
= r
'.*Deltatext block for revision 1.1 appeared twice'
2963 if not conv
.output_found(warning_re
):
2968 "process some nasty dependency graphs"
2970 # It's not how well the bear can dance, but that the bear can dance
2972 conv
= ensure_conversion('nasty-graphs')
2975 def tagging_after_delete():
2976 "optimal tag after deleting files"
2978 conv
= ensure_conversion('tagging-after-delete')
2980 # tag should be 'clean', no deletes
2981 log
= conv
.find_tag_log('tag1')
2983 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
2985 log
.check_changes(expected
)
2988 def crossed_branches():
2989 "branches created in inconsistent orders"
2991 conv
= ensure_conversion('crossed-branches')
2994 def file_directory_conflict():
2995 "error when filename conflicts with directory name"
2997 conv
= ensure_conversion(
2998 'file-directory-conflict',
2999 error_re
=r
'.*Directory name conflicts with filename',
3003 def attic_directory_conflict():
3004 "error when attic filename conflicts with dirname"
3006 # This tests the problem reported in issue #105.
3008 conv
= ensure_conversion(
3009 'attic-directory-conflict',
3010 error_re
=r
'.*Directory name conflicts with filename',
3015 "verify that --use-internal-co works"
3017 rcs_conv
= ensure_conversion(
3018 'main', args
=['--use-rcs', '--default-eol=native'],
3020 conv
= ensure_conversion(
3021 'main', args
=['--default-eol=native'],
3023 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3025 rcs_lines
= run_program(
3026 svntest
.main
.svnadmin_binary
, None, 'dump', '-q', '-r', '1:HEAD',
3028 lines
= run_program(
3029 svntest
.main
.svnadmin_binary
, None, 'dump', '-q', '-r', '1:HEAD',
3031 # Compare all lines following the repository UUID:
3032 if lines
[3:] != rcs_lines
[3:]:
3036 def internal_co_exclude():
3037 "verify that --use-internal-co --exclude=... works"
3039 rcs_conv
= ensure_conversion(
3041 args
=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3043 conv
= ensure_conversion(
3045 args
=['--exclude=BRANCH', '--default-eol=native'],
3047 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3049 rcs_lines
= run_program(
3050 svntest
.main
.svnadmin_binary
, None, 'dump', '-q', '-r', '1:HEAD',
3052 lines
= run_program(
3053 svntest
.main
.svnadmin_binary
, None, 'dump', '-q', '-r', '1:HEAD',
3055 # Compare all lines following the repository UUID:
3056 if lines
[3:] != rcs_lines
[3:]:
3060 def internal_co_trunk_only():
3061 "verify that --use-internal-co --trunk-only works"
3063 conv
= ensure_conversion(
3065 args
=['--trunk-only', '--default-eol=native'],
3067 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3071 def leftover_revs():
3072 "check for leftover checked-out revisions"
3074 conv
= ensure_conversion(
3076 args
=['--exclude=BRANCH', '--default-eol=native'],
3078 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3082 def requires_internal_co():
3083 "test that internal co can do more than RCS"
3084 # See issues 4, 11 for the bugs whose regression we're testing for.
3085 # Unlike in requires_cvs above, issue 29 is not covered.
3086 conv
= ensure_conversion('requires-cvs')
3088 atsign_contents
= file(conv
.get_wc("trunk", "atsign-add")).read()
3090 if atsign_contents
[-1:] == "@":
3093 if not (conv
.logs
[21].author
== "William Lyon Phelps III" and
3094 conv
.logs
[20].author
== "j random"):
3098 def internal_co_keywords():
3099 "test that internal co handles keywords correctly"
3100 conv_ic
= ensure_conversion('internal-co-keywords',
3101 args
=["--keywords-off"])
3102 conv_cvs
= ensure_conversion('internal-co-keywords',
3103 args
=["--use-cvs", "--keywords-off"])
3105 ko_ic
= file(conv_ic
.get_wc('trunk', 'dir', 'ko.txt')).read()
3106 ko_cvs
= file(conv_cvs
.get_wc('trunk', 'dir', 'ko.txt')).read()
3107 kk_ic
= file(conv_ic
.get_wc('trunk', 'dir', 'kk.txt')).read()
3108 kk_cvs
= file(conv_cvs
.get_wc('trunk', 'dir', 'kk.txt')).read()
3109 kv_ic
= file(conv_ic
.get_wc('trunk', 'dir', 'kv.txt')).read()
3110 kv_cvs
= file(conv_cvs
.get_wc('trunk', 'dir', 'kv.txt')).read()
3117 # The date format changed between cvs and co ('/' instead of '-').
3118 # Accept either one:
3119 date_substitution_re
= re
.compile(r
' ([0-9]*)-([0-9]*)-([0-9]*) ')
3120 if kv_ic
!= kv_cvs \
3121 and date_substitution_re
.sub(r
' \1/\2/\3 ', kv_ic
) != kv_cvs
:
3125 def timestamp_chaos():
3126 "test timestamp adjustments"
3128 conv
= ensure_conversion('timestamp-chaos', args
=["-v"])
3131 '2007-01-01 21:00:00', # Initial commit
3132 '2007-01-01 21:00:00', # revision 1.1 of both files
3133 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3134 '2007-01-01 21:00:02', # revision 1.2 of file1.txt, adjusted backwards
3135 '2007-01-01 22:00:00', # revision 1.3 of both files
3137 for i
in range(len(times
)):
3138 if abs(conv
.logs
[i
+ 1].date
- time
.mktime(svn_strptime(times
[i
]))) > 0.1:
3143 "convert a repository that contains symlinks"
3145 # This is a test for issue #97.
3147 proj
= os
.path
.join(test_data_dir
, 'symlinks-cvsrepos', 'proj')
3150 os
.path
.join('..', 'file.txt,v'),
3151 os
.path
.join(proj
, 'dir1', 'file.txt,v'),
3155 os
.path
.join(proj
, 'dir2'),
3161 except AttributeError:
3162 # Apparently this OS doesn't support symlinks, so skip test.
3163 raise svntest
.Skip()
3166 for (src
,dst
) in links
:
3167 os
.symlink(src
, dst
)
3169 conv
= ensure_conversion('symlinks')
3170 conv
.logs
[2].check('', (
3171 ('/%(trunk)s/proj', 'A'),
3172 ('/%(trunk)s/proj/file.txt', 'A'),
3173 ('/%(trunk)s/proj/dir1', 'A'),
3174 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3175 ('/%(trunk)s/proj/dir2', 'A'),
3176 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3179 for (src
,dst
) in links
:
3183 def empty_trunk_path():
3184 "allow --trunk to be empty if --trunk-only"
3186 # This is a test for issue #53.
3188 conv
= ensure_conversion(
3189 'main', args
=['--trunk-only', '--trunk='],
3193 def preferred_parent_cycle():
3194 "handle a cycle in branch parent preferences"
3196 conv
= ensure_conversion('preferred-parent-cycle')
3199 def branch_from_empty_dir():
3200 "branch from an empty directory"
3202 conv
= ensure_conversion('branch-from-empty-dir')
3206 "add a file on a branch then on trunk"
3208 conv
= ensure_conversion('trunk-readd')
3211 def branch_from_deleted_1_1():
3212 "branch from a 1.1 revision that will be deleted"
3214 conv
= ensure_conversion('branch-from-deleted-1-1')
3215 conv
.logs
[5].check('Adding b.txt:1.1.2.1', (
3216 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3218 conv
.logs
[6].check('Adding b.txt:1.1.4.1', (
3219 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3221 conv
.logs
[7].check('Adding b.txt:1.2', (
3222 ('/%(trunk)s/proj/b.txt', 'A'),
3225 conv
.logs
[8].check('Adding c.txt:1.1.2.1', (
3226 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3228 conv
.logs
[9].check('Adding c.txt:1.1.4.1', (
3229 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3233 def add_on_branch():
3234 "add a file on a branch using newer CVS"
3236 conv
= ensure_conversion('add-on-branch')
3237 conv
.logs
[6].check('Adding b.txt:1.1', (
3238 ('/%(trunk)s/proj/b.txt', 'A'),
3240 conv
.logs
[7].check('Adding b.txt:1.1.2.2', (
3241 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3243 conv
.logs
[8].check('Adding c.txt:1.1', (
3244 ('/%(trunk)s/proj/c.txt', 'A'),
3246 conv
.logs
[9].check('Removing c.txt:1.2', (
3247 ('/%(trunk)s/proj/c.txt', 'D'),
3249 conv
.logs
[10].check('Adding c.txt:1.2.2.2', (
3250 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3252 conv
.logs
[11].check('Adding d.txt:1.1', (
3253 ('/%(trunk)s/proj/d.txt', 'A'),
3255 conv
.logs
[12].check('Adding d.txt:1.1.2.2', (
3256 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3261 "test output in git-fast-import format"
3263 # Note: To test importing into git, do
3265 # ./run-tests <test-number>
3268 # cat cvs2svn-tmp/git-{blob,dump}.dat | git-fast-import
3270 # Or, to load the dumpfiles separately:
3272 # cat cvs2svn-tmp/git-blob.dat \
3273 # | git-fast-import --export-marks=cvs2svn-tmp/git-marks.dat
3274 # cat cvs2svn-tmp/git-dump.dat \
3275 # | git-fast-import --import-marks=cvs2svn-tmp/git-marks.dat
3277 # Then use "gitk --all", "git log", etc. to test the contents of the
3280 # We don't have the infrastructure to check that the resulting git
3281 # repository is correct, so we just check that the conversion runs
3283 conv
= GitConversion('main', None, [
3284 '--blobfile=cvs2svn-tmp/blobfile.out',
3285 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3286 '--username=cvs2git',
3287 'test-data/main-cvsrepos',
3292 "test cvs2git using options file"
3294 conv
= GitConversion('main', None, [], options_file
='cvs2git.options')
3298 "output in git-fast-import format with inline data"
3300 # The output should be suitable for import by Mercurial.
3302 # Note: To test importing into git, do
3304 # ./run-tests <test-number>
3307 # cat cvs2svn-tmp/git-dump.dat | git-fast-import
3309 # Then use "gitk --all", "git log", etc. to test the contents of the
3312 # We don't have the infrastructure to check that the resulting
3313 # Mercurial repository is correct, so we just check that the
3314 # conversion runs to completion:
3315 conv
= GitConversion('main', None, [], options_file
='cvs2hg.options')
3318 def invalid_symbol():
3319 "a symbol with the incorrect format"
3321 conv
= ensure_conversion('invalid-symbol')
3322 if not conv
.output_found(
3323 r
".*branch 'SYMBOL' references invalid revision 1$"
3328 def invalid_symbol_ignore():
3329 "ignore a symbol with the incorrect format"
3331 conv
= ensure_conversion(
3332 'invalid-symbol', options_file
='cvs2svn-ignore.options'
3336 class EOLVariants(Cvs2SvnTestCase
):
3337 "handle various --eol-style options"
3339 eol_style_strings
= {
3346 def __init__(self
, eol_style
):
3347 self
.eol_style
= eol_style
3348 self
.dumpfile
= 'eol-variants-%s.dump' % (self
.eol_style
,)
3349 Cvs2SvnTestCase
.__init
__(
3350 self
, 'eol-variants', variant
=self
.eol_style
,
3351 dumpfile
=self
.dumpfile
,
3353 '--default-eol=%s' % (self
.eol_style
,),
3358 conv
= self
.ensure_conversion()
3359 dump_contents
= open(conv
.dumpfile
, 'rb').read()
3360 expected_text
= self
.eol_style_strings
[self
.eol_style
].join(
3361 ['line 1', 'line 2', '\n\n']
3363 if not dump_contents
.endswith(expected_text
):
3368 "handle a file with no revisions (issue #80)"
3370 conv
= ensure_conversion('no-revs-file')
3373 def mirror_keyerror_test():
3374 "a case that gave KeyError in SVNRepositoryMirror"
3376 conv
= ensure_conversion('mirror-keyerror')
3379 def exclude_ntdb_test():
3380 "exclude a non-trunk default branch"
3382 symbol_info_file
= os
.path
.join(tmp_dir
, 'exclude-ntdb-symbol-info.txt')
3383 conv
= ensure_conversion(
3386 '--write-symbol-info=%s' % (symbol_info_file
,),
3387 '--exclude=branch3',
3389 '--exclude=vendortag3',
3390 '--exclude=vendorbranch',
3395 def mirror_keyerror2_test():
3396 "a case that gave KeyError in RepositoryMirror"
3398 conv
= ensure_conversion('mirror-keyerror2')
3401 def mirror_keyerror3_test():
3402 "a case that gave KeyError in RepositoryMirror"
3404 conv
= ensure_conversion('mirror-keyerror3')
3407 def add_cvsignore_to_branch_test():
3408 "check adding .cvsignore to an existing branch"
3410 # This a test for issue #122.
3412 conv
= ensure_conversion('add-cvsignore-to-branch')
3413 wc_tree
= conv
.get_wc_tree()
3414 trunk_props
= props_for_path(wc_tree
, 'trunk/dir')
3415 if trunk_props
['svn:ignore'] != '*.o\n\n':
3418 branch_props
= props_for_path(wc_tree
, 'branches/BRANCH/dir')
3419 if branch_props
['svn:ignore'] != '*.o\n\n':
3423 def missing_deltatext():
3424 "a revision's deltatext is missing"
3426 # This is a type of RCS file corruption that has been observed.
3427 conv
= ensure_conversion(
3428 'missing-deltatext',
3430 r
"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3435 ########################################################################
3438 # list all tests here, starting with None:
3447 PruneWithCare(variant
=1, trunk
='a', branches
='b', tags
='c'),
3448 PruneWithCare(variant
=2, trunk
='a/1', branches
='b/1', tags
='c/1'),
3449 PruneWithCare(variant
=3, trunk
='a/1', branches
='a/2', tags
='a/3'),
3450 interleaved_commits
,
3454 SimpleTags(variant
=1, trunk
='a', branches
='b', tags
='c'),
3455 SimpleTags(variant
=2, trunk
='a/1', branches
='b/1', tags
='c/1'),
3456 SimpleTags(variant
=3, trunk
='a/1', branches
='a/2', tags
='a/3'),
3457 simple_branch_commits
,
3459 mixed_time_branch_with_added_file
,
3466 PhoenixBranch(variant
=1, trunk
='a/1', branches
='b/1', tags
='c/1'),
3470 NoTrunkPrune(variant
=1, trunk
='a', branches
='b', tags
='c'),
3471 NoTrunkPrune(variant
=2, trunk
='a/1', branches
='b/1', tags
='c/1'),
3472 NoTrunkPrune(variant
=3, trunk
='a/1', branches
='a/2', tags
='a/3'),
3477 TaggedBranchAndTrunk(),
3478 TaggedBranchAndTrunk(variant
=1, trunk
='a/1', branches
='a/2', tags
='a/3'),
3481 BranchDeleteFirst(),
3482 BranchDeleteFirst(variant
=1, trunk
='a/1', branches
='a/2', tags
='a/3'),
3486 warning_expected
=1),
3489 variant
='encoding', args
=['--encoding=utf_8']),
3492 variant
='fallback-encoding', args
=['--fallback-encoding=utf_8']),
3494 warning_expected
=1),
3497 variant
='encoding', args
=['--encoding=utf_8']),
3500 variant
='fallback-encoding', args
=['--fallback-encoding=utf_8']),
3501 vendor_branch_sameness
,
3502 vendor_branch_trunk_only
,
3504 default_branches_trunk_only
,
3506 default_branch_and_1_2
,
3507 compose_tag_three_sources
,
3510 PeerPathPruning(variant
=1, trunk
='a/1', branches
='a/2', tags
='a/3'),
3512 EmptyTrunk(variant
=1, trunk
='a', branches
='b', tags
='c'),
3513 EmptyTrunk(variant
=2, trunk
='a/1', branches
='a/2', tags
='a/3'),
3514 no_spurious_svn_commits
,
3515 invalid_closings_on_trunk
,
3519 branch_from_default_branch
,
3521 retain_file_in_attic_too
,
3522 symbolic_name_filling_guide
,
3533 questionable_branch_names
,
3534 questionable_tag_names
,
3535 revision_reorder_bug
,
3537 vendor_branch_delete_add
,
3539 resync_pass2_pull_forward
,
3542 XFail(double_fill2
),
3543 resync_pass2_push_backward
,
3546 nested_ttb_directories
,
3547 auto_props_ignore_case
,
3548 ctrl_char_in_filename
,
3550 commit_dependencies
,
3553 multiply_defined_symbols
,
3554 multiply_defined_symbols_renamed
,
3555 multiply_defined_symbols_ignored
,
3556 repeatedly_defined_symbols
,
3557 double_branch_delete
,
3559 overlook_symbol_mismatches
,
3564 unblock_blocked_excludes
,
3565 regexp_force_symbols
,
3566 heuristic_symbol_default
,
3567 branch_symbol_default
,
3574 parent_hints_invalid
,
3575 parent_hints_wildcards
,
3584 tag_with_no_revision
,
3588 XFail(tagging_after_delete
),
3590 file_directory_conflict
,
3591 attic_directory_conflict
,
3594 internal_co_exclude
,
3595 internal_co_trunk_only
,
3596 internal_co_keywords
,
3598 requires_internal_co
,
3602 preferred_parent_cycle
,
3603 branch_from_empty_dir
,
3606 branch_from_deleted_1_1
,
3612 invalid_symbol_ignore
,
3616 EOLVariants('CRLF'),
3617 EOLVariants('native'),
3619 mirror_keyerror_test
,
3621 mirror_keyerror2_test
,
3622 mirror_keyerror3_test
,
3623 XFail(add_cvsignore_to_branch_test
),
3627 if __name__
== '__main__':
3629 # Configure the environment for reproducable output from svn, etc.
3630 # I have no idea if this works on Windows too.
3631 os
.environ
["LC_ALL"] = "C"
3632 os
.environ
["TZ"] = "UTC"
3634 # The Subversion test suite code assumes it's being invoked from
3635 # within a working copy of the Subversion sources, and tries to use
3636 # the binaries in that tree. Since the cvs2svn tree never contains
3637 # a Subversion build, we just use the system's installed binaries.
3638 svntest
.main
.svn_binary
= 'svn'
3639 svntest
.main
.svnlook_binary
= 'svnlook'
3640 svntest
.main
.svnadmin_binary
= 'svnadmin'
3641 svntest
.main
.svnversion_binary
= 'svnversion'
3643 run_tests(test_list
)