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-2009 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 ######################################################################
49 from hashlib
import md5
52 from difflib
import Differ
54 # Make sure that a supported version of Python is being used:
55 if not (0x02040000 <= sys
.hexversion
< 0x03000000):
57 'error: Python 2, version 2.4 or higher required.\n'
61 # This script needs to run in the correct directory. Make sure we're there.
62 if not (os
.path
.exists('cvs2svn') and os
.path
.exists('test-data')):
63 sys
.stderr
.write("error: I need to be run in the directory containing "
64 "'cvs2svn' and 'test-data'.\n")
67 # Load the Subversion test framework.
69 from svntest
import Failure
70 from svntest
.main
import safe_rmtree
71 from svntest
.testcase
import TestCase
72 from svntest
.testcase
import XFail_deco
74 # Test if Mercurial >= 1.1 is available.
76 from mercurial
import context
79 except (ImportError, AttributeError):
82 cvs2svn
= os
.path
.abspath('cvs2svn')
83 cvs2git
= os
.path
.abspath('cvs2git')
84 cvs2hg
= os
.path
.abspath('cvs2hg')
86 # We use the installed svn and svnlook binaries, instead of using
87 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
88 # behavior -- or even existence -- of local builds shouldn't affect
89 # the cvs2svn test suite.
91 svnlook_binary
= 'svnlook'
92 svnadmin_binary
= 'svnadmin'
93 svnversion_binary
= 'svnversion'
95 test_data_dir
= 'test-data'
96 tmp_dir
= 'cvs2svn-tmp'
99 #----------------------------------------------------------------------
101 #----------------------------------------------------------------------
104 # The value to expect for svn:keywords if it is set:
105 KEYWORDS
= 'Author Date Id Revision'
108 class RunProgramException(Failure
):
112 class MissingErrorException(Failure
):
113 def __init__(self
, error_re
):
115 self
, "Test failed because no error matched '%s'" % (error_re
,)
119 def run_program(program
, error_re
, *varargs
):
120 """Run PROGRAM with VARARGS, return stdout as a list of lines.
122 If there is any stderr and ERROR_RE is None, raise
123 RunProgramException, and log the stderr lines via
124 svntest.main.logger.info().
126 If ERROR_RE is not None, it is a string regular expression that must
127 match some line of stderr. If it fails to match, raise
128 MissingErrorExpection."""
130 # FIXME: exit_code is currently ignored.
131 exit_code
, out
, err
= svntest
.main
.run_command(program
, 1, 0, *varargs
)
134 # Specified error expected on stderr.
136 raise MissingErrorException(error_re
)
139 if re
.match(error_re
, line
):
141 raise MissingErrorException(error_re
)
145 log
= svntest
.main
.logger
.info
146 log('%s said:' % program
)
148 log(' ' + line
.rstrip())
149 raise RunProgramException()
154 def run_script(script
, error_re
, *varargs
):
155 """Run Python script SCRIPT with VARARGS, returning stdout as a list
158 If there is any stderr and ERROR_RE is None, raise
159 RunProgramException, and log the stderr lines via
160 svntest.main.logger.info().
162 If ERROR_RE is not None, it is a string regular expression that must
163 match some line of stderr. If it fails to match, raise
164 MissingErrorException."""
166 # Use the same python that is running this script
167 return run_program(sys
.executable
, error_re
, script
, *varargs
)
168 # On Windows, for an unknown reason, the cmd.exe process invoked by
169 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
170 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
171 # this. Therefore, the redirection of the output to the .s-revs file fails.
172 # We no longer use the problematic invocation on any system, but this
173 # comment remains to warn about this problem.
176 def run_svn(*varargs
):
177 """Run svn with VARARGS; return stdout as a list of lines.
178 If there is any stderr, raise RunProgramException, and log the
179 stderr lines via svntest.main.logger.info()."""
180 return run_program(svn_binary
, None, *varargs
)
183 def repos_to_url(path_to_svn_repos
):
184 """This does what you think it does."""
185 rpath
= os
.path
.abspath(path_to_svn_repos
)
188 return 'file://%s' % rpath
.replace(os
.sep
, '/')
191 def svn_strptime(timestr
):
192 return time
.strptime(timestr
, '%Y-%m-%d %H:%M:%S')
196 def __init__(self
, revision
, author
, date
, symbols
):
197 self
.revision
= revision
200 # Internally, we represent the date as seconds since epoch (UTC).
201 # Since standard subversion log output shows dates in localtime
203 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
205 # and time.mktime() converts from localtime, it all works out very
207 self
.date
= time
.mktime(svn_strptime(date
[0:19]))
209 # The following symbols are used for string interpolation when
211 self
.symbols
= symbols
213 # The changed paths will be accumulated later, as log data is read.
214 # Keys here are paths such as '/trunk/foo/bar', values are letter
215 # codes such as 'M', 'A', and 'D'.
216 self
.changed_paths
= { }
218 # The msg will be accumulated later, as log data is read.
221 def absorb_changed_paths(self
, out
):
222 'Read changed paths from OUT into self, until no more.'
224 line
= out
.readline()
225 if len(line
) == 1: return
227 op_portion
= line
[3:4]
228 path_portion
= line
[5:]
229 # If we're running on Windows we get backslashes instead of
231 path_portion
= path_portion
.replace('\\', '/')
232 # # We could parse out history information, but currently we
233 # # just leave it in the path portion because that's how some
236 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
238 # path_portion = m.group(1)
239 self
.changed_paths
[path_portion
] = op_portion
241 def __cmp__(self
, other
):
242 return cmp(self
.revision
, other
.revision
) or \
243 cmp(self
.author
, other
.author
) or cmp(self
.date
, other
.date
) or \
244 cmp(self
.changed_paths
, other
.changed_paths
) or \
245 cmp(self
.msg
, other
.msg
)
247 def get_path_op(self
, path
):
248 """Return the operator for the change involving PATH.
250 PATH is allowed to include string interpolation directives (e.g.,
251 '%(trunk)s'), which are interpolated against self.symbols. Return
252 None if there is no record for PATH."""
253 return self
.changed_paths
.get(path
% self
.symbols
)
255 def check_msg(self
, msg
):
256 """Verify that this Log's message starts with the specified MSG."""
257 if self
.msg
.find(msg
) != 0:
259 "Revision %d log message was:\n%s\n\n"
260 "It should have begun with:\n%s\n\n"
261 % (self
.revision
, self
.msg
, msg
,)
264 def check_change(self
, path
, op
):
265 """Verify that this Log includes a change for PATH with operator OP.
267 PATH is allowed to include string interpolation directives (e.g.,
268 '%(trunk)s'), which are interpolated against self.symbols."""
270 path
= path
% self
.symbols
271 found_op
= self
.changed_paths
.get(path
, None)
274 "Revision %d does not include change for path %s "
275 "(it should have been %s).\n"
276 % (self
.revision
, path
, op
,)
280 "Revision %d path %s had op %s (it should have been %s)\n"
281 % (self
.revision
, path
, found_op
, op
,)
284 def check_changes(self
, changed_paths
):
285 """Verify that this Log has precisely the CHANGED_PATHS specified.
287 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
288 strings are allowed to include string interpolation directives
289 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
292 for (path
, op
) in changed_paths
:
293 cp
[path
% self
.symbols
] = op
295 if self
.changed_paths
!= cp
:
297 "Revision %d changed paths list was:\n%s\n\n"
298 "It should have been:\n%s\n\n"
299 % (self
.revision
, self
.changed_paths
, cp
,)
302 def check(self
, msg
, changed_paths
):
303 """Verify that this Log has the MSG and CHANGED_PATHS specified.
305 Convenience function to check two things at once. MSG is passed
306 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
309 self
.check_changes(changed_paths
)
312 def parse_log(svn_repos
, symbols
):
313 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
315 Initialize the Logs' symbols with SYMBOLS."""
318 'Make a list of lines behave like an open file handle.'
320 def __init__(self
, lines
):
321 self
.lines
= list(reversed(lines
))
324 if len(self
.lines
) > 0:
325 return self
.lines
.pop()
329 def absorb_message_body(out
, num_lines
, log
):
330 """Read NUM_LINES of log message body from OUT into Log item LOG."""
332 for i
in range(num_lines
):
333 log
.msg
+= out
.readline()
335 log_start_re
= re
.compile('^r(?P<rev>[0-9]+) \| '
336 '(?P<author>[^\|]+) \| '
338 '\| (?P<lines>[0-9]+) (line|lines)$')
340 log_separator
= '-' * 72
344 out
= LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos
)))
348 line
= out
.readline()
352 if line
.find(log_separator
) == 0:
353 line
= out
.readline()
356 m
= log_start_re
.match(line
)
359 int(m
.group('rev')), m
.group('author'), m
.group('date'), symbols
)
360 line
= out
.readline()
364 elif line
.startswith('Changed paths:'):
365 this_log
.absorb_changed_paths(out
)
367 print 'unexpected log output'
368 print "Line: '%s'" % line
371 absorb_message_body(out
, int(m
.group('lines')), this_log
)
372 logs
[this_log
.revision
] = this_log
374 break # We've reached the end of the log output.
376 print 'unexpected log output (missing revision line)'
377 print "Line: '%s'" % line
380 print 'unexpected log output (missing log separator)'
381 print "Line: '%s'" % line
388 """Unconditionally remove PATH and its subtree, if any. PATH may be
389 non-existent, a file or symlink, or a directory."""
390 if os
.path
.isdir(path
):
392 elif os
.path
.exists(path
):
396 log_msg_text_wrapper
= textwrap
.TextWrapper(width
=76, break_long_words
=False)
398 def sym_log_msg(symbolic_name
, is_tag
=None):
399 """Return the expected log message for a cvs2svn-synthesized revision
400 creating branch or tag SYMBOLIC_NAME."""
402 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
408 return log_msg_text_wrapper
.fill(
409 "This commit was manufactured by cvs2svn to create %s '%s'."
410 % (type, symbolic_name
)
414 def make_conversion_id(
415 name
, args
, passbypass
, options_file
=None, symbol_hints_file
=None
417 """Create an identifying tag for a conversion.
419 The return value can also be used as part of a filesystem path.
421 NAME is the name of the CVS repository.
423 ARGS are the extra arguments to be passed to cvs2svn.
425 PASSBYPASS is a boolean indicating whether the conversion is to be
426 run one pass at a time.
428 If OPTIONS_FILE is specified, it is an options file that will be
429 used for the conversion.
431 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
432 will be used for the conversion.
434 The 1-to-1 mapping between cvs2svn command parameters and
435 conversion_ids allows us to avoid running the same conversion more
436 than once, when multiple tests use exactly the same conversion."""
443 args
.append('--passbypass')
445 if symbol_hints_file
is not None:
446 args
.append('--symbol-hints=%s' % (symbol_hints_file
,))
448 # There are some characters that are forbidden in filenames, and
449 # there is a limit on the total length of a path to a file. So use
450 # a hash of the parameters rather than concatenating the parameters
453 conv_id
+= "-" + md5('\0'.join(args
)).hexdigest()
455 # Some options-file based tests rely on knowing the paths to which
456 # the repository should be written, so we handle that option as a
457 # predictable string:
458 if options_file
is not None:
459 conv_id
+= '--options=%s' % (options_file
,)
465 """A record of a cvs2svn conversion.
469 conv_id -- the conversion id for this Conversion.
471 name -- a one-word name indicating the involved repositories.
473 dumpfile -- the name of the SVN dumpfile created by the conversion
474 (if the DUMPFILE constructor argument was used); otherwise,
477 repos -- the path to the svn repository. Unset if DUMPFILE was
480 logs -- a dictionary of Log instances, as returned by parse_log().
481 Unset if DUMPFILE was specified.
483 symbols -- a dictionary of symbols used for string interpolation
486 stdout -- a list of lines written by cvs2svn to stdout
488 _wc -- the basename of the svn working copy (within tmp_dir).
489 Unset if DUMPFILE was specified.
491 _wc_path -- the path to the svn working copy, if it has already
492 been created; otherwise, None. (The working copy is created
493 lazily when get_wc() is called.) Unset if DUMPFILE was
496 _wc_tree -- the tree built from the svn working copy, if it has
497 already been created; otherwise, None. The tree is created
498 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
501 _svnrepos -- the basename of the svn repository (within tmp_dir).
502 Unset if DUMPFILE was specified."""
504 # The number of the last cvs2svn pass (determined lazily by
509 def get_last_pass(cls
):
510 """Return the number of cvs2svn's last pass."""
512 if cls
.last_pass
is None:
513 out
= run_script(cvs2svn
, None, '--help-passes')
514 cls
.last_pass
= int(out
[-1].split()[0])
518 self
, conv_id
, name
, error_re
, passbypass
, symbols
, args
,
519 verbosity
=None, options_file
=None, symbol_hints_file
=None, dumpfile
=None,
521 self
.conv_id
= conv_id
523 self
.symbols
= symbols
524 if not os
.path
.isdir(tmp_dir
):
527 cvsrepos
= os
.path
.join(test_data_dir
, '%s-cvsrepos' % self
.name
)
530 self
.dumpfile
= os
.path
.join(tmp_dir
, dumpfile
)
531 # Clean up from any previous invocations of this script.
535 self
.repos
= os
.path
.join(tmp_dir
, '%s-svnrepos' % self
.conv_id
)
536 self
._wc
= os
.path
.join(tmp_dir
, '%s-wc' % self
.conv_id
)
540 # Clean up from any previous invocations of this script.
545 if svntest
.main
.svnadmin_binary
!= 'svnadmin':
547 '--svnadmin=%s' % (svntest
.main
.svnadmin_binary
,),
550 self
.options_file
= os
.path
.join(cvsrepos
, options_file
)
552 '--options=%s' % self
.options_file
,
554 args
.append(verbosity
or '-qqqqqq')
555 assert not symbol_hints_file
557 self
.options_file
= None
559 '--tmpdir=%s' % tmp_dir
,
562 args
.append(verbosity
or '-qqqqqq')
564 if symbol_hints_file
:
565 self
.symbol_hints_file
= os
.path
.join(cvsrepos
, symbol_hints_file
)
567 '--symbol-hints=%s' % self
.symbol_hints_file
,
571 args
.extend(['--dumpfile=%s' % (self
.dumpfile
,)])
573 args
.extend(['-s', self
.repos
])
574 args
.extend([cvsrepos
])
578 for p
in range(1, self
.get_last_pass() + 1):
579 self
.stdout
+= run_script(cvs2svn
, error_re
, '-p', str(p
), *args
)
581 self
.stdout
= run_script(cvs2svn
, error_re
, *args
)
584 if not os
.path
.isfile(self
.dumpfile
):
586 "Dumpfile not created: '%s'"
587 % os
.path
.join(os
.getcwd(), self
.dumpfile
)
590 if os
.path
.isdir(self
.repos
):
591 self
.logs
= parse_log(self
.repos
, self
.symbols
)
592 elif error_re
is None:
594 "Repository not created: '%s'"
595 % os
.path
.join(os
.getcwd(), self
.repos
)
598 def output_found(self
, pattern
):
599 """Return True if PATTERN matches any line in self.stdout.
601 PATTERN is a regular expression pattern as a string.
604 pattern_re
= re
.compile(pattern
)
606 for line
in self
.stdout
:
607 if pattern_re
.match(line
):
608 # We found the pattern that we were looking for.
613 def find_tag_log(self
, tagname
):
614 """Search LOGS for a log message containing 'TAGNAME' and return the
615 log in which it was found."""
616 for i
in xrange(len(self
.logs
), 0, -1):
617 if self
.logs
[i
].msg
.find("'"+tagname
+"'") != -1:
619 raise ValueError("Tag %s not found in logs" % tagname
)
621 def get_wc(self
, *args
):
622 """Return the path to the svn working copy, or a path within the WC.
624 If a working copy has not been created yet, create it now.
626 If ARGS are specified, then they should be strings that form
627 fragments of a path within the WC. They are joined using
628 os.path.join() and appended to the WC path."""
630 if self
._wc
_path
is None:
631 run_svn('co', repos_to_url(self
.repos
), self
._wc
)
632 self
._wc
_path
= self
._wc
633 return os
.path
.join(self
._wc
_path
, *args
)
635 def get_wc_tree(self
):
636 if self
._wc
_tree
is None:
637 self
._wc
_tree
= svntest
.tree
.build_tree_from_wc(self
.get_wc(), 1)
640 def path_exists(self
, *args
):
641 """Return True if the specified path exists within the repository.
643 (The strings in ARGS are first joined into a path using
646 return os
.path
.exists(self
.get_wc(*args
))
648 def check_props(self
, keys
, checks
):
649 """Helper function for checking lots of properties. For a list of
650 files in the conversion, check that the values of the properties
651 listed in KEYS agree with those listed in CHECKS. CHECKS is a
652 list of tuples: [ (filename, [value, value, ...]), ...], where the
653 values are listed in the same order as the key names are listed in
656 for (file, values
) in checks
:
657 assert len(values
) == len(keys
)
658 props
= props_for_path(self
.get_wc_tree(), file)
659 for i
in range(len(keys
)):
660 if props
.get(keys
[i
]) != values
[i
]:
662 "File %s has property %s set to \"%s\" "
663 "(it should have been \"%s\").\n"
664 % (file, keys
[i
], props
.get(keys
[i
]), values
[i
],)
669 """A record of a cvs2svn conversion.
673 name -- a one-word name indicating the CVS repository to be converted.
675 stdout -- a list of lines written by cvs2svn to stdout."""
677 def __init__(self
, name
, error_re
, args
, verbosity
=None, options_file
=None):
679 if not os
.path
.isdir(tmp_dir
):
682 cvsrepos
= os
.path
.join(test_data_dir
, '%s-cvsrepos' % self
.name
)
686 self
.options_file
= os
.path
.join(cvsrepos
, options_file
)
688 '--options=%s' % self
.options_file
,
691 self
.options_file
= None
693 args
.append(verbosity
or '-qqqqqq')
695 self
.stdout
= run_script(cvs2git
, error_re
, *args
)
698 # Cache of conversions that have already been done. Keys are conv_id;
699 # values are Conversion instances.
700 already_converted
= { }
702 def ensure_conversion(
703 name
, error_re
=None, passbypass
=None,
704 trunk
=None, branches
=None, tags
=None,
705 args
=None, verbosity
=None,
706 options_file
=None, symbol_hints_file
=None, dumpfile
=None,
708 """Convert CVS repository NAME to Subversion, but only if it has not
709 been converted before by this invocation of this script. If it has
710 been converted before, return the Conversion object from the
713 If no error, return a Conversion instance.
715 If ERROR_RE is a string, it is a regular expression expected to
716 match some line of stderr printed by the conversion. If there is an
717 error and ERROR_RE is not set, then raise Failure.
719 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
720 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
722 NAME is just one word. For example, 'main' would mean to convert
723 './test-data/main-cvsrepos', and after the conversion, the resulting
724 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
725 a checked out head working copy in './cvs2svn-tmp/main-wc'.
727 Any other options to pass to cvs2svn should be in ARGS, each element
728 being one option, e.g., '--trunk-only'. If the option takes an
729 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
730 are passed to cvs2svn in the order that they appear in ARGS.
732 If VERBOSITY is set, then it is passed to cvs2svn as an option.
733 Otherwise, the verbosity is turned way down so that only error
734 messages are emitted.
736 If OPTIONS_FILE is specified, then it should be the name of a file
737 within the main directory of the cvs repository associated with this
738 test. It is passed to cvs2svn using the --options option (which
739 suppresses some other options that are incompatible with --options).
741 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
742 file within the main directory of the cvs repository associated with
743 this test. It is passed to cvs2svn using the --symbol-hints option.
745 If DUMPFILE is specified, then it is the name of a dumpfile within
746 the temporary directory to which the conversion output should be
757 args
.append('--trunk=%s' % (trunk
,))
760 branches
= 'branches'
762 args
.append('--branches=%s' % (branches
,))
767 args
.append('--tags=%s' % (tags
,))
769 conv_id
= make_conversion_id(
770 name
, args
, passbypass
, options_file
, symbol_hints_file
773 if conv_id
not in already_converted
:
775 # Run the conversion and store the result for the rest of this
777 already_converted
[conv_id
] = Conversion(
778 conv_id
, name
, error_re
, passbypass
,
779 {'trunk' : trunk
, 'branches' : branches
, 'tags' : tags
},
780 args
, verbosity
, options_file
, symbol_hints_file
, dumpfile
,
783 # Remember the failure so that a future attempt to run this conversion
784 # does not bother to retry, but fails immediately.
785 already_converted
[conv_id
] = None
788 conv
= already_converted
[conv_id
]
794 class Cvs2SvnTestFunction(TestCase
):
795 """A TestCase based on a naked Python function object.
797 FUNC should be a function that returns None on success and throws an
798 svntest.Failure exception on failure. It should have a brief
799 docstring describing what it does (and fulfilling certain
800 conditions). FUNC must take no arguments.
802 This class is almost identical to svntest.testcase.FunctionTestCase,
803 except that the test function does not require a sandbox and does
804 not accept any parameter (not even sandbox=None).
806 This class can be used as an annotation on a Python function.
810 def __init__(self
, func
):
811 # it better be a function that accepts no parameters and has a
813 assert isinstance(func
, types
.FunctionType
)
815 name
= func
.func_name
817 assert func
.func_code
.co_argcount
== 0, \
818 '%s must not take any arguments' % name
820 doc
= func
.__doc
__.strip()
821 assert doc
, '%s must have a docstring' % name
823 # enforce stylistic guidelines for the function docstrings:
824 # - no longer than 50 characters
825 # - should not end in a period
826 # - should not be capitalized
827 assert len(doc
) <= 50, \
828 "%s's docstring must be 50 characters or less" % name
829 assert doc
[-1] != '.', \
830 "%s's docstring should not end in a period" % name
831 assert doc
[0].lower() == doc
[0], \
832 "%s's docstring should not be capitalized" % name
834 TestCase
.__init
__(self
, doc
=doc
)
837 def get_function_name(self
):
838 return self
.func
.func_name
840 def get_sandbox_name(self
):
843 def run(self
, sandbox
):
847 class Cvs2HgTestFunction(Cvs2SvnTestFunction
):
848 """Same as Cvs2SvnTestFunction, but for test cases that should be
849 skipped if Mercurial is not available.
851 def run(self
, sandbox
):
858 class Cvs2SvnTestCase(TestCase
):
860 self
, name
, doc
=None, variant
=None,
861 error_re
=None, passbypass
=None,
862 trunk
=None, branches
=None, tags
=None,
864 options_file
=None, symbol_hints_file
=None, dumpfile
=None,
869 # By default, use the first line of the class docstring as the
871 doc
= self
.__doc
__.splitlines()[0]
873 if variant
is not None:
874 # Modify doc to show the variant. Trim doc first if necessary
875 # to stay within the 50-character limit.
876 suffix
= '...variant %s' % (variant
,)
877 doc
= doc
[:50 - len(suffix
)] + suffix
879 TestCase
.__init
__(self
, doc
=doc
)
881 self
.error_re
= error_re
882 self
.passbypass
= passbypass
884 self
.branches
= branches
887 self
.options_file
= options_file
888 self
.symbol_hints_file
= symbol_hints_file
889 self
.dumpfile
= dumpfile
891 def ensure_conversion(self
):
892 return ensure_conversion(
894 error_re
=self
.error_re
, passbypass
=self
.passbypass
,
895 trunk
=self
.trunk
, branches
=self
.branches
, tags
=self
.tags
,
897 options_file
=self
.options_file
,
898 symbol_hints_file
=self
.symbol_hints_file
,
899 dumpfile
=self
.dumpfile
,
902 def get_sandbox_name(self
):
906 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase
):
907 """Test properties resulting from a conversion."""
909 def __init__(self
, name
, props_to_test
, expected_props
, **kw
):
910 """Initialize an instance of Cvs2SvnPropertiesTestCase.
912 NAME is the name of the test, passed to Cvs2SvnTestCase.
913 PROPS_TO_TEST is a list of the names of svn properties that should
914 be tested. EXPECTED_PROPS is a list of tuples [(filename,
915 [value,...])], where the second item in each tuple is a list of
916 values expected for the properties listed in PROPS_TO_TEST for the
917 specified filename. If a property must *not* be set, then its
918 value should be listed as None."""
920 Cvs2SvnTestCase
.__init
__(self
, name
, **kw
)
921 self
.props_to_test
= props_to_test
922 self
.expected_props
= expected_props
925 conv
= self
.ensure_conversion()
926 conv
.check_props(self
.props_to_test
, self
.expected_props
)
929 #----------------------------------------------------------------------
931 #----------------------------------------------------------------------
936 "cvs2svn with no arguments shows usage"
937 out
= run_script(cvs2svn
, None)
938 if (len(out
) > 2 and out
[0].find('ERROR:') == 0
939 and out
[1].find('DBM module')):
940 print 'cvs2svn cannot execute due to lack of proper DBM module.'
941 print 'Exiting without running any further tests.'
943 if out
[0].find('Usage:') < 0:
944 raise Failure('Basic cvs2svn invocation failed.')
948 def cvs2svn_manpage():
949 "generate a manpage for cvs2svn"
950 out
= run_script(cvs2svn
, None, '--man')
954 def cvs2git_manpage():
955 "generate a manpage for cvs2git"
956 out
= run_script(cvs2git
, None, '--man')
961 def cvs2hg_manpage():
962 "generate a manpage for cvs2hg"
963 out
= run_script(cvs2hg
, None, '--man')
967 def show_help_passes():
968 "cvs2svn --help-passes shows pass information"
969 out
= run_script(cvs2svn
, None, '--help-passes')
970 if out
[0].find('PASSES') < 0:
971 raise Failure('cvs2svn --help-passes failed.')
976 "detection of the executable flag"
977 if sys
.platform
== 'win32':
979 conv
= ensure_conversion('main')
980 st
= os
.stat(conv
.get_wc('trunk', 'single-files', 'attr-exec'))
981 if not st
.st_mode
& stat
.S_IXUSR
:
987 "conversion of filename with a space"
988 conv
= ensure_conversion('main')
989 if not conv
.path_exists('trunk', 'single-files', 'space fname'):
995 "two commits in quick succession"
996 conv
= ensure_conversion('main')
998 os
.path
.join(conv
.repos
, 'trunk', 'single-files', 'twoquick'), {})
1003 class PruneWithCare(Cvs2SvnTestCase
):
1004 "prune, but never too much"
1006 def __init__(self
, **kw
):
1007 Cvs2SvnTestCase
.__init
__(self
, 'main', **kw
)
1009 def run(self
, sbox
):
1010 # Robert Pluim encountered this lovely one while converting the
1011 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
1012 # repository (see issue #1302). Step 4 is the doozy:
1014 # revision 1: adds trunk/blah/, adds trunk/blah/first
1015 # revision 2: adds trunk/blah/second
1016 # revision 3: deletes trunk/blah/first
1017 # revision 4: deletes blah [re-deleting trunk/blah/first pruned blah!]
1018 # revision 5: does nothing
1020 # After fixing cvs2svn, the sequence (correctly) looks like this:
1022 # revision 1: adds trunk/blah/, adds trunk/blah/first
1023 # revision 2: adds trunk/blah/second
1024 # revision 3: deletes trunk/blah/first
1025 # revision 4: does nothing [because trunk/blah/first already deleted]
1026 # revision 5: deletes blah
1028 # The difference is in 4 and 5. In revision 4, it's not correct
1029 # to prune blah/, because second is still in there, so revision 4
1030 # does nothing now. But when we delete second in 5, that should
1031 # bubble up and prune blah/ instead.
1033 # ### Note that empty revisions like 4 are probably going to become
1034 # ### at least optional, if not banished entirely from cvs2svn's
1035 # ### output. Hmmm, or they may stick around, with an extra
1036 # ### revision property explaining what happened. Need to think
1037 # ### about that. In some sense, it's a bug in Subversion itself,
1038 # ### that such revisions don't show up in 'svn log' output.
1040 conv
= self
.ensure_conversion()
1042 # Confirm that revision 4 removes '/trunk/full-prune/first',
1043 # and that revision 6 removes '/trunk/full-prune'.
1045 # Also confirm similar things about '/full-prune-reappear/...',
1046 # which is similar, except that later on it reappears, restored
1047 # from pruneland, because a file gets added to it.
1049 # And finally, a similar thing for '/partial-prune/...', except that
1050 # in its case, a permanent file on the top level prevents the
1051 # pruning from going farther than the subdirectory containing first
1054 for path
in ('full-prune/first',
1055 'full-prune-reappear/sub/first',
1056 'partial-prune/sub/first'):
1057 conv
.logs
[5].check_change('/%(trunk)s/' + path
, 'D')
1059 for path
in ('full-prune',
1060 'full-prune-reappear',
1061 'partial-prune/sub'):
1062 conv
.logs
[7].check_change('/%(trunk)s/' + path
, 'D')
1064 for path
in ('full-prune-reappear',
1065 'full-prune-reappear/appears-later'):
1066 conv
.logs
[33].check_change('/%(trunk)s/' + path
, 'A')
1069 @Cvs2SvnTestFunction
1070 def interleaved_commits():
1071 "two interleaved trunk commits, different log msgs"
1072 # See test-data/main-cvsrepos/proj/README.
1073 conv
= ensure_conversion('main')
1075 # The initial import.
1077 conv
.logs
[rev
].check('Initial import.', (
1078 ('/%(trunk)s/interleaved', 'A'),
1079 ('/%(trunk)s/interleaved/1', 'A'),
1080 ('/%(trunk)s/interleaved/2', 'A'),
1081 ('/%(trunk)s/interleaved/3', 'A'),
1082 ('/%(trunk)s/interleaved/4', 'A'),
1083 ('/%(trunk)s/interleaved/5', 'A'),
1084 ('/%(trunk)s/interleaved/a', 'A'),
1085 ('/%(trunk)s/interleaved/b', 'A'),
1086 ('/%(trunk)s/interleaved/c', 'A'),
1087 ('/%(trunk)s/interleaved/d', 'A'),
1088 ('/%(trunk)s/interleaved/e', 'A'),
1091 def check_letters(rev
):
1092 """Check if REV is the rev where only letters were committed."""
1094 conv
.logs
[rev
].check('Committing letters only.', (
1095 ('/%(trunk)s/interleaved/a', 'M'),
1096 ('/%(trunk)s/interleaved/b', 'M'),
1097 ('/%(trunk)s/interleaved/c', 'M'),
1098 ('/%(trunk)s/interleaved/d', 'M'),
1099 ('/%(trunk)s/interleaved/e', 'M'),
1102 def check_numbers(rev
):
1103 """Check if REV is the rev where only numbers were committed."""
1105 conv
.logs
[rev
].check('Committing numbers only.', (
1106 ('/%(trunk)s/interleaved/1', 'M'),
1107 ('/%(trunk)s/interleaved/2', 'M'),
1108 ('/%(trunk)s/interleaved/3', 'M'),
1109 ('/%(trunk)s/interleaved/4', 'M'),
1110 ('/%(trunk)s/interleaved/5', 'M'),
1113 # One of the commits was letters only, the other was numbers only.
1114 # But they happened "simultaneously", so we don't assume anything
1115 # about which commit appeared first, so we just try both ways.
1119 check_numbers(rev
+ 1)
1122 check_letters(rev
+ 1)
1125 @Cvs2SvnTestFunction
1126 def simple_commits():
1127 "simple trunk commits"
1128 # See test-data/main-cvsrepos/proj/README.
1129 conv
= ensure_conversion('main')
1131 # The initial import.
1132 conv
.logs
[13].check('Initial import.', (
1133 ('/%(trunk)s/proj', 'A'),
1134 ('/%(trunk)s/proj/default', 'A'),
1135 ('/%(trunk)s/proj/sub1', 'A'),
1136 ('/%(trunk)s/proj/sub1/default', 'A'),
1137 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1138 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1139 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1140 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1141 ('/%(trunk)s/proj/sub2', 'A'),
1142 ('/%(trunk)s/proj/sub2/default', 'A'),
1143 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1144 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1145 ('/%(trunk)s/proj/sub3', 'A'),
1146 ('/%(trunk)s/proj/sub3/default', 'A'),
1150 conv
.logs
[18].check('First commit to proj, affecting two files.', (
1151 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1152 ('/%(trunk)s/proj/sub3/default', 'M'),
1155 # The second commit.
1156 conv
.logs
[19].check('Second commit to proj, affecting all 7 files.', (
1157 ('/%(trunk)s/proj/default', 'M'),
1158 ('/%(trunk)s/proj/sub1/default', 'M'),
1159 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1160 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1161 ('/%(trunk)s/proj/sub2/default', 'M'),
1162 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1163 ('/%(trunk)s/proj/sub3/default', 'M')
1167 class SimpleTags(Cvs2SvnTestCase
):
1168 "simple tags and branches, no commits"
1170 def __init__(self
, **kw
):
1171 # See test-data/main-cvsrepos/proj/README.
1172 Cvs2SvnTestCase
.__init
__(self
, 'main', **kw
)
1174 def run(self
, sbox
):
1175 conv
= self
.ensure_conversion()
1177 # Verify the copy source for the tags we are about to check
1178 # No need to verify the copyfrom revision, as simple_commits did that
1179 conv
.logs
[13].check('Initial import.', (
1180 ('/%(trunk)s/proj', 'A'),
1181 ('/%(trunk)s/proj/default', 'A'),
1182 ('/%(trunk)s/proj/sub1', 'A'),
1183 ('/%(trunk)s/proj/sub1/default', 'A'),
1184 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1185 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1186 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1187 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1188 ('/%(trunk)s/proj/sub2', 'A'),
1189 ('/%(trunk)s/proj/sub2/default', 'A'),
1190 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1191 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1192 ('/%(trunk)s/proj/sub3', 'A'),
1193 ('/%(trunk)s/proj/sub3/default', 'A'),
1196 # Tag on rev 1.1.1.1 of all files in proj
1197 conv
.logs
[16].check(sym_log_msg('B_FROM_INITIALS'), (
1198 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1199 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1200 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1203 # The same, as a tag
1204 log
= conv
.find_tag_log('T_ALL_INITIAL_FILES')
1205 log
.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1206 ('/%(tags)s/T_ALL_INITIAL_FILES (from /%(trunk)s:13)', 'A'),
1207 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
1208 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
1211 # Tag on rev 1.1.1.1 of all files in proj, except one
1212 log
= conv
.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1213 log
.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1214 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE (from /%(trunk)s:13)', 'A'),
1215 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
1216 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
1217 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1220 # The same, as a branch
1221 conv
.logs
[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1222 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE (from /%(trunk)s:13)', 'A'),
1223 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1224 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
1225 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
1229 @Cvs2SvnTestFunction
1230 def simple_branch_commits():
1231 "simple branch commits"
1232 # See test-data/main-cvsrepos/proj/README.
1233 conv
= ensure_conversion('main')
1235 conv
.logs
[23].check('Modify three files, on branch B_MIXED.', (
1236 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1237 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1238 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1242 @Cvs2SvnTestFunction
1243 def mixed_time_tag():
1245 # See test-data/main-cvsrepos/proj/README.
1246 conv
= ensure_conversion('main')
1248 log
= conv
.find_tag_log('T_MIXED')
1250 ('/%(tags)s/T_MIXED (from /%(trunk)s:19)', 'A'),
1251 ('/%(tags)s/T_MIXED/single-files', 'D'),
1252 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
1253 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
1254 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1255 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1259 @Cvs2SvnTestFunction
1260 def mixed_time_branch_with_added_file():
1261 "mixed-time branch, and a file added to the branch"
1262 # See test-data/main-cvsrepos/proj/README.
1263 conv
= ensure_conversion('main')
1265 # A branch from the same place as T_MIXED in the previous test,
1266 # plus a file added directly to the branch
1267 conv
.logs
[21].check(sym_log_msg('B_MIXED'), (
1268 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1269 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1270 ('/%(branches)s/B_MIXED/single-files', 'D'),
1271 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1272 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1273 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1276 conv
.logs
[22].check('Add a file on branch B_MIXED.', (
1277 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1281 @Cvs2SvnTestFunction
1283 "a commit affecting both trunk and a branch"
1284 # See test-data/main-cvsrepos/proj/README.
1285 conv
= ensure_conversion('main')
1287 conv
.logs
[24].check(
1288 'A single commit affecting one file on branch B_MIXED '
1289 'and one on trunk.', (
1290 ('/%(trunk)s/proj/sub2/default', 'M'),
1291 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1295 @Cvs2SvnTestFunction
1296 def split_time_branch():
1297 "branch some trunk files, and later branch the rest"
1298 # See test-data/main-cvsrepos/proj/README.
1299 conv
= ensure_conversion('main')
1301 # First change on the branch, creating it
1302 conv
.logs
[25].check(sym_log_msg('B_SPLIT'), (
1303 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1304 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1305 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1306 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1309 conv
.logs
[29].check('First change on branch B_SPLIT.', (
1310 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1311 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1312 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1313 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1314 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1317 # A trunk commit for the file which was not branched
1318 conv
.logs
[30].check('A trunk change to sub1/subsubB/default. '
1319 'This was committed about an', (
1320 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1323 # Add the file not already branched to the branch, with modification:w
1324 conv
.logs
[31].check(sym_log_msg('B_SPLIT'), (
1325 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1326 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1329 conv
.logs
[32].check('This change affects sub3/default and '
1330 'sub1/subsubB/default, on branch', (
1331 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1332 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1336 @Cvs2SvnTestFunction
1337 def multiple_tags():
1338 "multiple tags referring to same revision"
1339 conv
= ensure_conversion('main')
1340 if not conv
.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1342 if not conv
.path_exists(
1343 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1347 @Cvs2SvnTestFunction
1348 def multiply_defined_symbols():
1349 "multiple definitions of symbol names"
1351 # We can only check one line of the error output at a time, so test
1352 # twice. (The conversion only have to be done once because the
1353 # results are cached.)
1354 conv
= ensure_conversion(
1355 'multiply-defined-symbols',
1357 r
"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1361 conv
= ensure_conversion(
1362 'multiply-defined-symbols',
1364 r
"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1370 @Cvs2SvnTestFunction
1371 def multiply_defined_symbols_renamed():
1372 "rename multiply defined symbols"
1374 conv
= ensure_conversion(
1375 'multiply-defined-symbols',
1376 options_file
='cvs2svn-rename.options',
1380 @Cvs2SvnTestFunction
1381 def multiply_defined_symbols_ignored():
1382 "ignore multiply defined symbols"
1384 conv
= ensure_conversion(
1385 'multiply-defined-symbols',
1386 options_file
='cvs2svn-ignore.options',
1390 @Cvs2SvnTestFunction
1391 def repeatedly_defined_symbols():
1392 "multiple identical definitions of symbol names"
1394 # If a symbol is defined multiple times but has the same value each
1395 # time, that should not be an error.
1397 conv
= ensure_conversion('repeatedly-defined-symbols')
1400 @Cvs2SvnTestFunction
1402 "conversion of invalid symbolic names"
1403 conv
= ensure_conversion('bogus-tag')
1406 @Cvs2SvnTestFunction
1407 def overlapping_branch():
1408 "ignore a file with a branch with two names"
1409 conv
= ensure_conversion(
1410 'overlapping-branch',
1412 error_re
='.*cannot also have name \'vendorB\'',
1415 conv
.logs
[2].check('imported', (
1416 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1417 ('/%(trunk)s/overlapping-branch', 'A'),
1420 if len(conv
.logs
) != 2:
1424 class PhoenixBranch(Cvs2SvnTestCase
):
1425 "convert a branch file rooted in a 'dead' revision"
1427 def __init__(self
, **kw
):
1428 Cvs2SvnTestCase
.__init
__(self
, 'phoenix', **kw
)
1430 def run(self
, sbox
):
1431 conv
= self
.ensure_conversion()
1432 conv
.logs
[8].check('This file was supplied by Jack Moffitt', (
1433 ('/%(branches)s/volsung_20010721', 'A'),
1434 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1436 conv
.logs
[9].check('This file was supplied by Jack Moffitt', (
1437 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1441 ###TODO: We check for 4 changed paths here to accomodate creating tags
1442 ###and branches in rev 1, but that will change, so this will
1443 ###eventually change back.
1444 @Cvs2SvnTestFunction
1445 def ctrl_char_in_log():
1446 "handle a control char in a log message"
1447 # This was issue #1106.
1449 conv
= ensure_conversion('ctrl-char-in-log')
1450 conv
.logs
[rev
].check_changes((
1451 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1453 if conv
.logs
[rev
].msg
.find('\x04') < 0:
1455 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1458 @Cvs2SvnTestFunction
1460 "handle tags rooted in a redeleted revision"
1461 conv
= ensure_conversion('overdead')
1464 class NoTrunkPrune(Cvs2SvnTestCase
):
1465 "ensure that trunk doesn't get pruned"
1467 def __init__(self
, **kw
):
1468 Cvs2SvnTestCase
.__init
__(self
, 'overdead', **kw
)
1470 def run(self
, sbox
):
1471 conv
= self
.ensure_conversion()
1472 for rev
in conv
.logs
.keys():
1473 rev_logs
= conv
.logs
[rev
]
1474 if rev_logs
.get_path_op('/%(trunk)s') == 'D':
1478 @Cvs2SvnTestFunction
1479 def double_delete():
1480 "file deleted twice, in the root of the repository"
1481 # This really tests several things: how we handle a file that's
1482 # removed (state 'dead') in two successive revisions; how we
1483 # handle a file in the root of the repository (there were some
1484 # bugs in cvs2svn's svn path construction for top-level files); and
1485 # the --no-prune option.
1486 conv
= ensure_conversion(
1487 'double-delete', args
=['--trunk-only', '--no-prune'])
1489 path
= '/%(trunk)s/twice-removed'
1491 conv
.logs
[rev
].check('Updated CVS', (
1494 conv
.logs
[rev
+ 1].check('Remove this file for the first time.', (
1497 conv
.logs
[rev
+ 2].check('Remove this file for the second time,', (
1501 @Cvs2SvnTestFunction
1503 "branch created from both trunk and another branch"
1504 # See test-data/split-branch-cvsrepos/README.
1506 # The conversion will fail if the bug is present, and
1507 # ensure_conversion will raise Failure.
1508 conv
= ensure_conversion('split-branch')
1511 @Cvs2SvnTestFunction
1512 def resync_misgroups():
1513 "resyncing should not misorder commit groups"
1514 # See test-data/resync-misgroups-cvsrepos/README.
1516 # The conversion will fail if the bug is present, and
1517 # ensure_conversion will raise Failure.
1518 conv
= ensure_conversion('resync-misgroups')
1521 class TaggedBranchAndTrunk(Cvs2SvnTestCase
):
1522 "allow tags with mixed trunk and branch sources"
1524 def __init__(self
, **kw
):
1525 Cvs2SvnTestCase
.__init
__(self
, 'tagged-branch-n-trunk', **kw
)
1527 def run(self
, sbox
):
1528 conv
= self
.ensure_conversion()
1530 tags
= conv
.symbols
.get('tags', 'tags')
1532 a_path
= conv
.get_wc(tags
, 'some-tag', 'a.txt')
1533 b_path
= conv
.get_wc(tags
, 'some-tag', 'b.txt')
1534 if not (os
.path
.exists(a_path
) and os
.path
.exists(b_path
)):
1536 if (open(a_path
, 'r').read().find('1.24') == -1) \
1537 or (open(b_path
, 'r').read().find('1.5') == -1):
1541 @Cvs2SvnTestFunction
1543 "never use the rev-in-progress as a copy source"
1545 # See issue #1427 and r8544.
1546 conv
= ensure_conversion('enroot-race')
1548 conv
.logs
[rev
].check_changes((
1549 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1550 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1551 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1553 conv
.logs
[rev
+ 1].check_changes((
1554 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1555 ('/%(trunk)s/proj/a.txt', 'M'),
1556 ('/%(trunk)s/proj/b.txt', 'M'),
1560 @Cvs2SvnTestFunction
1561 def enroot_race_obo():
1562 "do use the last completed rev as a copy source"
1563 conv
= ensure_conversion('enroot-race-obo')
1564 conv
.logs
[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1565 if not len(conv
.logs
) == 3:
1569 class BranchDeleteFirst(Cvs2SvnTestCase
):
1570 "correctly handle deletion as initial branch action"
1572 def __init__(self
, **kw
):
1573 Cvs2SvnTestCase
.__init
__(self
, 'branch-delete-first', **kw
)
1575 def run(self
, sbox
):
1576 # See test-data/branch-delete-first-cvsrepos/README.
1578 # The conversion will fail if the bug is present, and
1579 # ensure_conversion would raise Failure.
1580 conv
= self
.ensure_conversion()
1582 branches
= conv
.symbols
.get('branches', 'branches')
1584 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1585 if conv
.path_exists(branches
, 'branch-1', 'file'):
1587 if conv
.path_exists(branches
, 'branch-2', 'file'):
1589 if not conv
.path_exists(branches
, 'branch-3', 'file'):
1593 @Cvs2SvnTestFunction
1594 def nonascii_cvsignore():
1595 "non ascii files in .cvsignore"
1597 # The output seems to be in the C locale, where it looks like this
1598 # (at least on one test system):
1600 'Sp?\\195?\\164tzle\n'
1601 'Cr?\\195?\\168meBr?\\195?\\187l?\\195?\\169e\n'
1602 'Jam?\\195?\\179nIb?\\195?\\169rico\n'
1603 'Am?\\195?\\170ijoas?\\195?\\128Bulh?\\195?\\163oPato\n'
1606 conv
= ensure_conversion('non-ascii', args
=['--encoding=latin1'])
1607 props
= props_for_path(conv
.get_wc_tree(), 'trunk/single-files')
1609 if props
['svn:ignore'] != expected
:
1613 @Cvs2SvnTestFunction
1614 def nonascii_filenames():
1615 "non ascii files converted incorrectly"
1618 # on a en_US.iso-8859-1 machine this test fails with
1619 # svn: Can't recode ...
1621 # as described in the issue
1623 # on a en_US.UTF-8 machine this test fails with
1624 # svn: Malformed XML ...
1626 # which means at least it fails. Unfortunately it won't fail
1627 # with the same error...
1629 # mangle current locale settings so we know we're not running
1630 # a UTF-8 locale (which does not exhibit this problem)
1631 current_locale
= locale
.getlocale()
1632 new_locale
= 'en_US.ISO8859-1'
1633 locale_changed
= None
1635 # From http://docs.python.org/lib/module-sys.html
1637 # getfilesystemencoding():
1639 # Return the name of the encoding used to convert Unicode filenames
1640 # into system file names, or None if the system default encoding is
1641 # used. The result value depends on the operating system:
1643 # - On Windows 9x, the encoding is ``mbcs''.
1644 # - On Mac OS X, the encoding is ``utf-8''.
1645 # - On Unix, the encoding is the user's preference according to the
1646 # result of nl_langinfo(CODESET), or None if the
1647 # nl_langinfo(CODESET) failed.
1648 # - On Windows NT+, file names are Unicode natively, so no conversion is
1651 # So we're going to skip this test on Mac OS X for now.
1652 if sys
.platform
== "darwin":
1653 raise svntest
.Skip()
1656 # change locale to non-UTF-8 locale to generate latin1 names
1657 locale
.setlocale(locale
.LC_ALL
, # this might be too broad?
1660 except locale
.Error
:
1661 raise svntest
.Skip()
1664 srcrepos_path
= os
.path
.join(test_data_dir
, 'non-ascii-cvsrepos')
1665 dstrepos_path
= os
.path
.join(test_data_dir
, 'non-ascii-copy-cvsrepos')
1666 if not os
.path
.exists(dstrepos_path
):
1667 # create repos from existing main repos
1668 shutil
.copytree(srcrepos_path
, dstrepos_path
)
1669 base_path
= os
.path
.join(dstrepos_path
, 'single-files')
1670 os
.remove(os
.path
.join(base_path
, '.cvsignore,v'))
1671 shutil
.copyfile(os
.path
.join(base_path
, 'twoquick,v'),
1672 os
.path
.join(base_path
, 'two\366uick,v'))
1673 new_path
= os
.path
.join(dstrepos_path
, 'single\366files')
1674 os
.rename(base_path
, new_path
)
1676 conv
= ensure_conversion('non-ascii-copy', args
=['--encoding=latin1'])
1679 locale
.setlocale(locale
.LC_ALL
, current_locale
)
1680 safe_rmtree(dstrepos_path
)
1683 class UnicodeTest(Cvs2SvnTestCase
):
1684 "metadata contains Unicode"
1686 warning_pattern
= r
'ERROR\: There were warnings converting .* messages'
1688 def __init__(self
, name
, warning_expected
, **kw
):
1689 if warning_expected
:
1690 error_re
= self
.warning_pattern
1694 Cvs2SvnTestCase
.__init
__(self
, name
, error_re
=error_re
, **kw
)
1695 self
.warning_expected
= warning_expected
1697 def run(self
, sbox
):
1699 # ensure the availability of the "utf_8" encoding:
1700 u
'a'.encode('utf_8').decode('utf_8')
1702 raise svntest
.Skip()
1704 self
.ensure_conversion()
1707 class UnicodeAuthor(UnicodeTest
):
1708 "author name contains Unicode"
1710 def __init__(self
, warning_expected
, **kw
):
1711 UnicodeTest
.__init
__(self
, 'unicode-author', warning_expected
, **kw
)
1714 class UnicodeLog(UnicodeTest
):
1715 "log message contains Unicode"
1717 def __init__(self
, warning_expected
, **kw
):
1718 UnicodeTest
.__init
__(self
, 'unicode-log', warning_expected
, **kw
)
1721 @Cvs2SvnTestFunction
1722 def vendor_branch_sameness():
1723 "avoid spurious changes for initial revs"
1724 conv
= ensure_conversion(
1725 'vendor-branch-sameness', args
=['--keep-trivial-imports']
1728 # The following files are in this repository:
1730 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1731 # the same contents, the file's default branch is 1.1.1,
1732 # and both revisions are in state 'Exp'.
1734 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1735 # 1.1 (the addition of a line of text).
1737 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1739 # d.txt: This file was created by 'cvs add' instead of import, so
1740 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1741 # The timestamp on the add is exactly the same as for the
1742 # imports of the other files.
1744 # e.txt: Like a.txt, except that the log message for revision 1.1
1745 # is not the standard import log message.
1747 # (Aside from e.txt, the log messages for the same revisions are the
1748 # same in all files.)
1750 # We expect that only a.txt is recognized as an import whose 1.1
1751 # revision can be omitted. The other files should be added on trunk
1752 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1753 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1754 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1755 # and c.txt should be 'D'eleted.
1758 conv
.logs
[rev
].check('Initial revision', (
1759 ('/%(trunk)s/proj', 'A'),
1760 ('/%(trunk)s/proj/b.txt', 'A'),
1761 ('/%(trunk)s/proj/c.txt', 'A'),
1762 ('/%(trunk)s/proj/d.txt', 'A'),
1765 conv
.logs
[rev
+ 1].check(sym_log_msg('vbranchA'), (
1766 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1767 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1770 conv
.logs
[rev
+ 2].check('First vendor branch revision.', (
1771 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1772 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1773 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1776 conv
.logs
[rev
+ 3].check('This commit was generated by cvs2svn '
1777 'to compensate for changes in r4,', (
1778 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1779 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1780 ('/%(trunk)s/proj/c.txt', 'D'),
1784 conv
.logs
[rev
].check('This log message is not the standard', (
1785 ('/%(trunk)s/proj/e.txt', 'A'),
1788 conv
.logs
[rev
+ 2].check('First vendor branch revision', (
1789 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1792 conv
.logs
[rev
+ 3].check('This commit was generated by cvs2svn '
1793 'to compensate for changes in r9,', (
1794 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1798 @Cvs2SvnTestFunction
1799 def vendor_branch_trunk_only():
1800 "handle vendor branches with --trunk-only"
1801 conv
= ensure_conversion('vendor-branch-sameness', args
=['--trunk-only'])
1804 conv
.logs
[rev
].check('Initial revision', (
1805 ('/%(trunk)s/proj', 'A'),
1806 ('/%(trunk)s/proj/b.txt', 'A'),
1807 ('/%(trunk)s/proj/c.txt', 'A'),
1808 ('/%(trunk)s/proj/d.txt', 'A'),
1811 conv
.logs
[rev
+ 1].check('First vendor branch revision', (
1812 ('/%(trunk)s/proj/a.txt', 'A'),
1813 ('/%(trunk)s/proj/b.txt', 'M'),
1814 ('/%(trunk)s/proj/c.txt', 'D'),
1817 conv
.logs
[rev
+ 2].check('This log message is not the standard', (
1818 ('/%(trunk)s/proj/e.txt', 'A'),
1821 conv
.logs
[rev
+ 3].check('First vendor branch revision', (
1822 ('/%(trunk)s/proj/e.txt', 'M'),
1826 @Cvs2SvnTestFunction
1827 def default_branches():
1828 "handle default branches correctly"
1829 conv
= ensure_conversion('default-branches')
1831 # There are seven files in the repository:
1834 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1835 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1836 # committed (thus losing the default branch "1.1.1"), then
1837 # 1.1.1.4 was imported. All vendor import release tags are
1841 # Like a.txt, but without rev 1.2.
1844 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1847 # Same as the previous two, but 1.1.1 branch is unlabeled.
1850 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1852 # deleted-on-vendor-branch.txt,v:
1853 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1855 # added-then-imported.txt,v:
1856 # Added with 'cvs add' to create 1.1, then imported with
1857 # completely different contents to create 1.1.1.1, therefore
1858 # never had a default branch.
1861 conv
.logs
[2].check("Import (vbranchA, vtag-1).", (
1862 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1863 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1864 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1865 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1866 ('/%(branches)s/vbranchA', 'A'),
1867 ('/%(branches)s/vbranchA/proj', 'A'),
1868 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1869 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1870 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1871 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1874 conv
.logs
[3].check("This commit was generated by cvs2svn "
1875 "to compensate for changes in r2,", (
1876 ('/%(trunk)s/proj', 'A'),
1877 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1878 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1879 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1880 ('/%(trunk)s/proj/d.txt '
1881 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1882 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1883 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1884 ('/%(trunk)s/proj/e.txt '
1885 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1888 conv
.logs
[4].check(sym_log_msg('vtag-1',1), (
1889 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1890 ('/%(tags)s/vtag-1/proj/d.txt '
1891 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1894 conv
.logs
[5].check("Import (vbranchA, vtag-2).", (
1895 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1896 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1897 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1898 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1899 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1900 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1903 conv
.logs
[6].check("This commit was generated by cvs2svn "
1904 "to compensate for changes in r5,", (
1905 ('/%(trunk)s/proj/a.txt '
1906 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1907 ('/%(trunk)s/proj/b.txt '
1908 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1909 ('/%(trunk)s/proj/c.txt '
1910 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1911 ('/%(trunk)s/proj/d.txt '
1912 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1913 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1914 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1916 ('/%(trunk)s/proj/e.txt '
1917 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1920 conv
.logs
[7].check(sym_log_msg('vtag-2',1), (
1921 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1922 ('/%(tags)s/vtag-2/proj/d.txt '
1923 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1926 conv
.logs
[8].check("Import (vbranchA, vtag-3).", (
1927 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1928 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1929 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1930 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1931 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1932 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1935 conv
.logs
[9].check("This commit was generated by cvs2svn "
1936 "to compensate for changes in r8,", (
1937 ('/%(trunk)s/proj/a.txt '
1938 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1939 ('/%(trunk)s/proj/b.txt '
1940 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1941 ('/%(trunk)s/proj/c.txt '
1942 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1943 ('/%(trunk)s/proj/d.txt '
1944 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1945 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1946 ('/%(trunk)s/proj/e.txt '
1947 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1950 conv
.logs
[10].check(sym_log_msg('vtag-3',1), (
1951 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1952 ('/%(tags)s/vtag-3/proj/d.txt '
1953 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1954 ('/%(tags)s/vtag-3/proj/e.txt '
1955 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1958 conv
.logs
[11].check("First regular commit, to a.txt, on vtag-3.", (
1959 ('/%(trunk)s/proj/a.txt', 'M'),
1962 conv
.logs
[12].check("Add a file to the working copy.", (
1963 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1966 conv
.logs
[13].check(sym_log_msg('vbranchA'), (
1967 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1968 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1971 conv
.logs
[14].check("Import (vbranchA, vtag-4).", (
1972 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1973 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1974 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1975 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1976 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1977 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1978 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1981 conv
.logs
[15].check("This commit was generated by cvs2svn "
1982 "to compensate for changes in r14,", (
1983 ('/%(trunk)s/proj/b.txt '
1984 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1985 ('/%(trunk)s/proj/c.txt '
1986 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1987 ('/%(trunk)s/proj/d.txt '
1988 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1989 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1990 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1992 ('/%(trunk)s/proj/e.txt '
1993 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1996 conv
.logs
[16].check(sym_log_msg('vtag-4',1), (
1997 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1998 ('/%(tags)s/vtag-4/proj/d.txt '
1999 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
2003 @Cvs2SvnTestFunction
2004 def default_branches_trunk_only():
2005 "handle default branches with --trunk-only"
2007 conv
= ensure_conversion('default-branches', args
=['--trunk-only'])
2009 conv
.logs
[2].check("Import (vbranchA, vtag-1).", (
2010 ('/%(trunk)s/proj', 'A'),
2011 ('/%(trunk)s/proj/a.txt', 'A'),
2012 ('/%(trunk)s/proj/b.txt', 'A'),
2013 ('/%(trunk)s/proj/c.txt', 'A'),
2014 ('/%(trunk)s/proj/d.txt', 'A'),
2015 ('/%(trunk)s/proj/e.txt', 'A'),
2016 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2019 conv
.logs
[3].check("Import (vbranchA, vtag-2).", (
2020 ('/%(trunk)s/proj/a.txt', 'M'),
2021 ('/%(trunk)s/proj/b.txt', 'M'),
2022 ('/%(trunk)s/proj/c.txt', 'M'),
2023 ('/%(trunk)s/proj/d.txt', 'M'),
2024 ('/%(trunk)s/proj/e.txt', 'M'),
2025 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
2028 conv
.logs
[4].check("Import (vbranchA, vtag-3).", (
2029 ('/%(trunk)s/proj/a.txt', 'M'),
2030 ('/%(trunk)s/proj/b.txt', 'M'),
2031 ('/%(trunk)s/proj/c.txt', 'M'),
2032 ('/%(trunk)s/proj/d.txt', 'M'),
2033 ('/%(trunk)s/proj/e.txt', 'M'),
2034 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
2037 conv
.logs
[5].check("First regular commit, to a.txt, on vtag-3.", (
2038 ('/%(trunk)s/proj/a.txt', 'M'),
2041 conv
.logs
[6].check("Add a file to the working copy.", (
2042 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
2045 conv
.logs
[7].check("Import (vbranchA, vtag-4).", (
2046 ('/%(trunk)s/proj/b.txt', 'M'),
2047 ('/%(trunk)s/proj/c.txt', 'M'),
2048 ('/%(trunk)s/proj/d.txt', 'M'),
2049 ('/%(trunk)s/proj/e.txt', 'M'),
2050 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2054 @Cvs2SvnTestFunction
2055 def default_branch_and_1_2():
2056 "do not allow 1.2 revision with default branch"
2058 conv
= ensure_conversion(
2059 'default-branch-and-1-2',
2061 r
'.*File \'.*\' has default branch
=1\
.1\
.1 but also a revision
1\
.2'
2066 @Cvs2SvnTestFunction
2067 def compose_tag_three_sources():
2068 "compose a tag from three sources"
2069 conv = ensure_conversion('compose
-tag
-three
-sources
')
2071 conv.logs[2].check("Add on trunk", (
2072 ('/%(trunk)s/tagged
-on
-trunk
-1.1', 'A
'),
2073 ('/%(trunk)s/tagged
-on
-trunk
-1.2-a
', 'A
'),
2074 ('/%(trunk)s/tagged
-on
-trunk
-1.2-b
', 'A
'),
2075 ('/%(trunk)s/tagged
-on
-b1
', 'A
'),
2076 ('/%(trunk)s/tagged
-on
-b2
', 'A
'),
2079 conv.logs[3].check(sym_log_msg('b1
'), (
2080 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A
'),
2083 conv.logs[4].check(sym_log_msg('b2
'), (
2084 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A
'),
2087 conv.logs[5].check("Commit on branch b1", (
2088 ('/%(branches)s/b1
/tagged
-on
-trunk
-1.1', 'M
'),
2089 ('/%(branches)s/b1
/tagged
-on
-trunk
-1.2-a
', 'M
'),
2090 ('/%(branches)s/b1
/tagged
-on
-trunk
-1.2-b
', 'M
'),
2091 ('/%(branches)s/b1
/tagged
-on
-b1
', 'M
'),
2092 ('/%(branches)s/b1
/tagged
-on
-b2
', 'M
'),
2095 conv.logs[6].check("Commit on branch b2", (
2096 ('/%(branches)s/b2
/tagged
-on
-trunk
-1.1', 'M
'),
2097 ('/%(branches)s/b2
/tagged
-on
-trunk
-1.2-a
', 'M
'),
2098 ('/%(branches)s/b2
/tagged
-on
-trunk
-1.2-b
', 'M
'),
2099 ('/%(branches)s/b2
/tagged
-on
-b1
', 'M
'),
2100 ('/%(branches)s/b2
/tagged
-on
-b2
', 'M
'),
2103 conv.logs[7].check("Commit again on trunk", (
2104 ('/%(trunk)s/tagged
-on
-trunk
-1.2-a
', 'M
'),
2105 ('/%(trunk)s/tagged
-on
-trunk
-1.2-b
', 'M
'),
2106 ('/%(trunk)s/tagged
-on
-trunk
-1.1', 'M
'),
2107 ('/%(trunk)s/tagged
-on
-b1
', 'M
'),
2108 ('/%(trunk)s/tagged
-on
-b2
', 'M
'),
2111 conv.logs[8].check(sym_log_msg('T
',1), (
2112 ('/%(tags)s/T (from /%(trunk)s:7)', 'A
'),
2113 ('/%(tags)s/T
/tagged
-on
-trunk
-1.1 '
2114 '(from /%(trunk)s/tagged
-on
-trunk
-1.1:2)', 'R
'),
2115 ('/%(tags)s/T
/tagged
-on
-b1 (from /%(branches)s/b1
/tagged
-on
-b1
:5)', 'R
'),
2116 ('/%(tags)s/T
/tagged
-on
-b2 (from /%(branches)s/b2
/tagged
-on
-b2
:6)', 'R
'),
2120 @Cvs2SvnTestFunction
2121 def pass5_when_to_fill():
2122 "reserve a svn revnum for a fill only when required"
2123 # The conversion will fail if the bug is present, and
2124 # ensure_conversion would raise Failure.
2125 conv = ensure_conversion('pass5
-when
-to
-fill
')
2128 class EmptyTrunk(Cvs2SvnTestCase):
2129 "don't
break when the trunk
is empty
"
2131 def __init__(self, **kw):
2132 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
2134 def run(self, sbox):
2135 # The conversion will fail if the bug is present, and
2136 # ensure_conversion would raise Failure.
2137 conv = self.ensure_conversion()
2140 @Cvs2SvnTestFunction
2141 def no_spurious_svn_commits():
2142 "ensure that we don
't create any spurious commits"
2143 conv = ensure_conversion('phoenix
')
2145 # Check spurious commit that could be created in
2146 # SVNCommitCreator._pre_commit()
2148 # (When you add a file on a branch, CVS creates a trunk revision
2149 # in state 'dead
'. If the log message of that commit is equal to
2150 # the one that CVS generates, we do not ever create a 'fill
'
2151 # SVNCommit for it.)
2153 # and spurious commit that could be created in
2154 # SVNCommitCreator._commit()
2156 # (When you add a file on a branch, CVS creates a trunk revision
2157 # in state 'dead
'. If the log message of that commit is equal to
2158 # the one that CVS generates, we do not create a primary SVNCommit
2160 conv.logs[17].check('File added on branch xiphophorus
', (
2161 ('/%(branches)s/xiphophorus
/added
-on
-branch
.txt
', 'A
'),
2164 # Check to make sure that a commit *is* generated:
2165 # (When you add a file on a branch, CVS creates a trunk revision
2166 # in state 'dead
'. If the log message of that commit is NOT equal
2167 # to the one that CVS generates, we create a primary SVNCommit to
2168 # serve as a home for the log message in question.
2169 conv.logs[18].check('file added
-on
-branch2
.txt was initially added on
'
2170 + 'branch xiphophorus
,\nand this log message was tweaked
', ())
2172 # Check spurious commit that could be created in
2173 # SVNCommitCreator._commit_symbols().
2174 conv.logs[19].check('This
file was also added on branch xiphophorus
,', (
2175 ('/%(branches)s/xiphophorus
/added
-on
-branch2
.txt
', 'A
'),
2179 class PeerPathPruning(Cvs2SvnTestCase):
2180 "make sure that filling prunes paths correctly"
2182 def __init__(self, **kw):
2183 Cvs2SvnTestCase.__init__(self, 'peer
-path
-pruning
', **kw)
2185 def run(self, sbox):
2186 conv = self.ensure_conversion()
2187 conv.logs[6].check(sym_log_msg('BRANCH
'), (
2188 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A
'),
2189 ('/%(branches)s/BRANCH
/bar
', 'D
'),
2190 ('/%(branches)s/BRANCH
/foo (from /%(trunk)s/foo
:5)', 'R
'),
2194 @Cvs2SvnTestFunction
2195 def invalid_closings_on_trunk():
2196 "verify correct revs are copied to default branches"
2197 # The conversion will fail if the bug is present, and
2198 # ensure_conversion would raise Failure.
2199 conv = ensure_conversion('invalid
-closings
-on
-trunk
')
2202 @Cvs2SvnTestFunction
2203 def individual_passes():
2204 "run each pass individually"
2205 conv = ensure_conversion('main
')
2206 conv2 = ensure_conversion('main
', passbypass=1)
2208 if conv.logs != conv2.logs:
2212 @Cvs2SvnTestFunction
2214 "reveal a big bug in our resync algorithm"
2215 # This will fail if the bug is present
2216 conv = ensure_conversion('resync
-bug
')
2219 @Cvs2SvnTestFunction
2220 def branch_from_default_branch():
2221 "reveal a bug in our default branch detection code"
2222 conv = ensure_conversion('branch
-from-default
-branch
')
2224 # This revision will be a default branch synchronization only
2225 # if cvs2svn is correctly determining default branch revisions.
2227 # The bug was that cvs2svn was treating revisions on branches off of
2228 # default branches as default branch revisions, resulting in
2229 # incorrectly regarding the branch off of the default branch as a
2230 # non-trunk default branch. Crystal clear? I thought so. See
2231 # issue #42 for more incoherent blathering.
2232 conv.logs[5].check("This commit was generated by cvs2svn", (
2233 ('/%(trunk)s/proj
/file.txt
'
2234 '(from /%(branches)s/upstream
/proj
/file.txt
:4)', 'R
'),
2238 @Cvs2SvnTestFunction
2239 def file_in_attic_too():
2240 "die if a file exists in and out of the attic"
2242 'file-in-attic
-too
',
2244 r'.*A CVS repository cannot contain both
'
2245 r'(.*)' + re.escape(os.sep) + r'(.*) '
2247 r'\
1' + re.escape(os.sep) + r'Attic
' + re.escape(os.sep) + r'\
2'
2252 @Cvs2SvnTestFunction
2253 def retain_file_in_attic_too():
2254 "test --retain-conflicting-attic-files option"
2255 conv = ensure_conversion(
2256 'file-in-attic
-too
', args=['--retain
-conflicting
-attic
-files
'])
2257 if not conv.path_exists('trunk
', 'file.txt
'):
2259 if not conv.path_exists('trunk
', 'Attic
', 'file.txt
'):
2263 @Cvs2SvnTestFunction
2264 def symbolic_name_filling_guide():
2265 "reveal a big bug in our SymbolFillingGuide"
2266 # This will fail if the bug is present
2267 conv = ensure_conversion('symbolic
-name
-overfill
')
2270 # Helpers for tests involving file contents and properties.
2272 class NodeTreeWalkException:
2273 "Exception class for node tree traversals."
2276 def node_for_path(node, path):
2277 "In the tree rooted under SVNTree NODE, return the node at PATH."
2278 if node.name != '__SVN_ROOT_NODE
':
2279 raise NodeTreeWalkException()
2280 path = path.strip('/')
2281 components = path.split('/')
2282 for component in components:
2283 node = svntest.tree.get_child(node, component)
2286 # Helper for tests involving properties.
2287 def props_for_path(node, path):
2288 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2289 return node_for_path(node, path).props
2292 class EOLMime(Cvs2SvnPropertiesTestCase):
2293 """eol settings and mime types together
2295 The files are as follows:
2297 trunk/foo.txt: no -kb, mime file says nothing.
2298 trunk/foo.xml: no -kb, mime file says text.
2299 trunk/foo.zip: no -kb, mime file says non-text.
2300 trunk/foo.bin: has -kb, mime file says nothing.
2301 trunk/foo.csv: has -kb, mime file says text.
2302 trunk/foo.dbf: has -kb, mime file says non-text.
2305 def __init__(self, args, **kw):
2306 # TODO: It's a bit klugey to construct this path here
. But so far
2307 # there's only one test with a mime.types file. If we have more,
2308 # we should abstract this into some helper, which would be located
2309 # near ensure_conversion(). Note that it is a convention of this
2310 # test suite for a mime.types file to be located in the top level
2311 # of the CVS repository to which it applies.
2312 self
.mime_path
= os
.path
.join(
2313 test_data_dir
, 'eol-mime-cvsrepos', 'mime.types')
2315 Cvs2SvnPropertiesTestCase
.__init
__(
2317 props_to_test
=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2318 args
=['--mime-types=%s' % self
.mime_path
] + args
,
2322 # We do four conversions. Each time, we pass --mime-types=FILE with
2323 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2324 # Thus there's one conversion with neither flag, one with just the
2325 # former, one with just the latter, and one with both.
2328 # Neither --no-default-eol nor --eol-from-mime-type:
2329 eol_mime1
= EOLMime(
2333 ('trunk/foo.txt', [None, None, None]),
2334 ('trunk/foo.xml', [None, 'text/xml', None]),
2335 ('trunk/foo.zip', [None, 'application/zip', None]),
2336 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2337 ('trunk/foo.csv', [None, 'text/csv', None]),
2338 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2342 # Just --no-default-eol, not --eol-from-mime-type:
2343 eol_mime2
= EOLMime(
2345 args
=['--default-eol=native'],
2347 ('trunk/foo.txt', ['native', None, KEYWORDS
]),
2348 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS
]),
2349 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS
]),
2350 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2351 ('trunk/foo.csv', [None, 'text/csv', None]),
2352 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2356 # Just --eol-from-mime-type, not --no-default-eol:
2357 eol_mime3
= EOLMime(
2359 args
=['--eol-from-mime-type'],
2361 ('trunk/foo.txt', [None, None, None]),
2362 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS
]),
2363 ('trunk/foo.zip', [None, 'application/zip', None]),
2364 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2365 ('trunk/foo.csv', [None, 'text/csv', None]),
2366 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2370 # Both --no-default-eol and --eol-from-mime-type:
2371 eol_mime4
= EOLMime(
2373 args
=['--eol-from-mime-type', '--default-eol=native'],
2375 ('trunk/foo.txt', ['native', None, KEYWORDS
]),
2376 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS
]),
2377 ('trunk/foo.zip', [None, 'application/zip', None]),
2378 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2379 ('trunk/foo.csv', [None, 'text/csv', None]),
2380 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2384 cvs_revnums_off
= Cvs2SvnPropertiesTestCase(
2386 doc
='test non-setting of cvs2svn:cvs-rev property',
2388 props_to_test
=['cvs2svn:cvs-rev'],
2390 ('trunk/foo.txt', [None]),
2391 ('trunk/foo.xml', [None]),
2392 ('trunk/foo.zip', [None]),
2393 ('trunk/foo.bin', [None]),
2394 ('trunk/foo.csv', [None]),
2395 ('trunk/foo.dbf', [None]),
2399 cvs_revnums_on
= Cvs2SvnPropertiesTestCase(
2401 doc
='test setting of cvs2svn:cvs-rev property',
2402 args
=['--cvs-revnums'],
2403 props_to_test
=['cvs2svn:cvs-rev'],
2405 ('trunk/foo.txt', ['1.2']),
2406 ('trunk/foo.xml', ['1.2']),
2407 ('trunk/foo.zip', ['1.2']),
2408 ('trunk/foo.bin', ['1.2']),
2409 ('trunk/foo.csv', ['1.2']),
2410 ('trunk/foo.dbf', ['1.2']),
2414 keywords
= Cvs2SvnPropertiesTestCase(
2416 doc
='test setting of svn:keywords property among others',
2417 args
=['--default-eol=native'],
2418 props_to_test
=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2420 ('trunk/foo.default', [KEYWORDS
, 'native', None]),
2421 ('trunk/foo.kkvl', [KEYWORDS
, 'native', None]),
2422 ('trunk/foo.kkv', [KEYWORDS
, 'native', None]),
2423 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2424 ('trunk/foo.kk', [None, 'native', None]),
2425 ('trunk/foo.ko', [None, 'native', None]),
2426 ('trunk/foo.kv', [None, 'native', None]),
2430 @Cvs2SvnTestFunction
2432 "test setting of svn:ignore property"
2433 conv
= ensure_conversion('cvsignore')
2434 wc_tree
= conv
.get_wc_tree()
2435 topdir_props
= props_for_path(wc_tree
, 'trunk/proj')
2436 subdir_props
= props_for_path(wc_tree
, '/trunk/proj/subdir')
2438 if topdir_props
['svn:ignore'] != \
2439 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2442 if subdir_props
['svn:ignore'] != \
2443 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2447 @Cvs2SvnTestFunction
2449 "test that CVS can still do what RCS can't"
2450 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2451 conv
= ensure_conversion(
2452 'requires-cvs', args
=['--use-cvs', '--default-eol=native'],
2455 atsign_contents
= file(conv
.get_wc("trunk", "atsign-add")).read()
2456 cl_contents
= file(conv
.get_wc("trunk", "client_lock.idl")).read()
2458 if atsign_contents
[-1:] == "@":
2460 if cl_contents
.find("gregh\n//\n//Integration for locks") < 0:
2463 if not (conv
.logs
[6].author
== "William Lyon Phelps III" and
2464 conv
.logs
[5].author
== "j random"):
2468 @Cvs2SvnTestFunction
2469 def questionable_branch_names():
2470 "test that we can handle weird branch names"
2471 conv
= ensure_conversion('questionable-symbols')
2472 # If the conversion succeeds, then we're okay. We could check the
2473 # actual branch paths, too, but the main thing is to know that the
2474 # conversion doesn't fail.
2477 @Cvs2SvnTestFunction
2478 def questionable_tag_names():
2479 "test that we can handle weird tag names"
2480 conv
= ensure_conversion('questionable-symbols')
2481 conv
.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2482 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2484 conv
.find_tag_log('TagWith/Backslash_E').check(
2485 sym_log_msg('TagWith/Backslash_E',1),
2487 ('/%(tags)s/TagWith', 'A'),
2488 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2491 conv
.find_tag_log('TagWith/Slash_Z').check(
2492 sym_log_msg('TagWith/Slash_Z',1),
2494 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2499 @Cvs2SvnTestFunction
2500 def revision_reorder_bug():
2501 "reveal a bug that reorders file revisions"
2502 conv
= ensure_conversion('revision-reorder-bug')
2503 # If the conversion succeeds, then we're okay. We could check the
2504 # actual revisions, too, but the main thing is to know that the
2505 # conversion doesn't fail.
2508 @Cvs2SvnTestFunction
2510 "test that exclude really excludes everything"
2511 conv
= ensure_conversion('main', args
=['--exclude=.*'])
2512 for log
in conv
.logs
.values():
2513 for item
in log
.changed_paths
.keys():
2514 if item
.startswith('/branches/') or item
.startswith('/tags/'):
2518 @Cvs2SvnTestFunction
2519 def vendor_branch_delete_add():
2520 "add trunk file that was deleted on vendor branch"
2521 # This will error if the bug is present
2522 conv
= ensure_conversion('vendor-branch-delete-add')
2525 @Cvs2SvnTestFunction
2526 def resync_pass2_pull_forward():
2527 "ensure pass2 doesn't pull rev too far forward"
2528 conv
= ensure_conversion('resync-pass2-pull-forward')
2529 # If the conversion succeeds, then we're okay. We could check the
2530 # actual revisions, too, but the main thing is to know that the
2531 # conversion doesn't fail.
2534 @Cvs2SvnTestFunction
2536 "only LFs for svn:eol-style=native files"
2537 conv
= ensure_conversion('native-eol', args
=['--default-eol=native'])
2538 lines
= run_program(svntest
.main
.svnadmin_binary
, None, 'dump', '-q',
2540 # Verify that all files in the dump have LF EOLs. We're actually
2541 # testing the whole dump file, but the dump file itself only uses
2542 # LF EOLs, so we're safe.
2544 if line
[-1] != '\n' or line
[:-1].find('\r') != -1:
2548 @Cvs2SvnTestFunction
2550 "reveal a bug that created a branch twice"
2551 conv
= ensure_conversion('double-fill')
2552 # If the conversion succeeds, then we're okay. We could check the
2553 # actual revisions, too, but the main thing is to know that the
2554 # conversion doesn't fail.
2558 @Cvs2SvnTestFunction
2560 "reveal a second bug that created a branch twice"
2561 conv
= ensure_conversion('double-fill2')
2562 conv
.logs
[6].check_msg(sym_log_msg('BRANCH1'))
2563 conv
.logs
[7].check_msg(sym_log_msg('BRANCH2'))
2565 # This check should fail:
2566 conv
.logs
[8].check_msg(sym_log_msg('BRANCH2'))
2570 raise Failure('Symbol filled twice in a row')
2573 @Cvs2SvnTestFunction
2574 def resync_pass2_push_backward():
2575 "ensure pass2 doesn't push rev too far backward"
2576 conv
= ensure_conversion('resync-pass2-push-backward')
2577 # If the conversion succeeds, then we're okay. We could check the
2578 # actual revisions, too, but the main thing is to know that the
2579 # conversion doesn't fail.
2582 @Cvs2SvnTestFunction
2584 "reveal a bug that added a branch file twice"
2585 conv
= ensure_conversion('double-add')
2586 # If the conversion succeeds, then we're okay. We could check the
2587 # actual revisions, too, but the main thing is to know that the
2588 # conversion doesn't fail.
2591 @Cvs2SvnTestFunction
2592 def bogus_branch_copy():
2593 "reveal a bug that copies a branch file wrongly"
2594 conv
= ensure_conversion('bogus-branch-copy')
2595 # If the conversion succeeds, then we're okay. We could check the
2596 # actual revisions, too, but the main thing is to know that the
2597 # conversion doesn't fail.
2600 @Cvs2SvnTestFunction
2601 def nested_ttb_directories():
2602 "require error if ttb directories are not disjoint"
2604 {'trunk' : 'a', 'branches' : 'a',},
2605 {'trunk' : 'a', 'tags' : 'a',},
2606 {'branches' : 'a', 'tags' : 'a',},
2607 # This option conflicts with the default trunk path:
2608 {'branches' : 'trunk',},
2609 # Try some nested directories:
2610 {'trunk' : 'a', 'branches' : 'a/b',},
2611 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2612 {'branches' : 'a', 'tags' : 'a/b',},
2615 for opts
in opts_list
:
2617 'main', error_re
=r
'The following paths are not disjoint\:', **opts
2621 class AutoProps(Cvs2SvnPropertiesTestCase
):
2624 The files are as follows:
2626 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2627 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2628 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2629 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2630 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2631 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2632 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2633 trunk/foo.UPCASE1: no -kb, no mime type.
2634 trunk/foo.UPCASE2: no -kb, no mime type.
2637 def __init__(self
, args
, **kw
):
2638 ### TODO: It's a bit klugey to construct this path here. See also
2639 ### the comment in eol_mime().
2640 auto_props_path
= os
.path
.join(
2641 test_data_dir
, 'eol-mime-cvsrepos', 'auto-props')
2643 Cvs2SvnPropertiesTestCase
.__init
__(
2653 '--auto-props=%s' % auto_props_path
,
2654 '--eol-from-mime-type'
2659 auto_props_ignore_case
= AutoProps(
2660 doc
="test auto-props",
2661 args
=['--default-eol=native'],
2663 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS
, None]),
2664 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS
, None]),
2665 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2666 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2668 ['bin', None, 'application/octet-stream', None, '']),
2669 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2671 ['dbf', None, 'application/what-is-dbf', None, None]),
2672 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS
, None]),
2673 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS
, None]),
2677 @Cvs2SvnTestFunction
2678 def ctrl_char_in_filename():
2679 "do not allow control characters in filenames"
2682 srcrepos_path
= os
.path
.join(test_data_dir
,'main-cvsrepos')
2683 dstrepos_path
= os
.path
.join(test_data_dir
,'ctrl-char-filename-cvsrepos')
2684 if os
.path
.exists(dstrepos_path
):
2685 safe_rmtree(dstrepos_path
)
2687 # create repos from existing main repos
2688 shutil
.copytree(srcrepos_path
, dstrepos_path
)
2689 base_path
= os
.path
.join(dstrepos_path
, 'single-files')
2691 shutil
.copyfile(os
.path
.join(base_path
, 'twoquick,v'),
2692 os
.path
.join(base_path
, 'two\rquick,v'))
2694 # Operating systems that don't allow control characters in
2695 # filenames will hopefully have thrown an exception; in that
2696 # case, just skip this test.
2697 raise svntest
.Skip()
2699 conv
= ensure_conversion(
2700 'ctrl-char-filename',
2701 error_re
=(r
'.*Subversion does not allow character .*.'),
2704 safe_rmtree(dstrepos_path
)
2707 @Cvs2SvnTestFunction
2708 def commit_dependencies():
2709 "interleaved and multi-branch commits to same files"
2710 conv
= ensure_conversion("commit-dependencies")
2711 conv
.logs
[2].check('adding', (
2712 ('/%(trunk)s/interleaved', 'A'),
2713 ('/%(trunk)s/interleaved/file1', 'A'),
2714 ('/%(trunk)s/interleaved/file2', 'A'),
2716 conv
.logs
[3].check('big commit', (
2717 ('/%(trunk)s/interleaved/file1', 'M'),
2718 ('/%(trunk)s/interleaved/file2', 'M'),
2720 conv
.logs
[4].check('dependant small commit', (
2721 ('/%(trunk)s/interleaved/file1', 'M'),
2723 conv
.logs
[5].check('adding', (
2724 ('/%(trunk)s/multi-branch', 'A'),
2725 ('/%(trunk)s/multi-branch/file1', 'A'),
2726 ('/%(trunk)s/multi-branch/file2', 'A'),
2728 conv
.logs
[6].check(sym_log_msg("branch"), (
2729 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2730 ('/%(branches)s/branch/interleaved', 'D'),
2732 conv
.logs
[7].check('multi-branch-commit', (
2733 ('/%(trunk)s/multi-branch/file1', 'M'),
2734 ('/%(trunk)s/multi-branch/file2', 'M'),
2735 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2736 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2740 @Cvs2SvnTestFunction
2741 def double_branch_delete():
2742 "fill branches before modifying files on them"
2743 conv
= ensure_conversion('double-branch-delete')
2745 # Test for issue #102. The file IMarshalledValue.java is branched,
2746 # deleted, readded on the branch, and then deleted again. If the
2747 # fill for the file on the branch is postponed until after the
2748 # modification, the file will end up live on the branch instead of
2749 # dead! Make sure it happens at the right time.
2751 conv
.logs
[6].check('JBAS-2436 - Adding LGPL Header2', (
2752 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2755 conv
.logs
[7].check('JBAS-3025 - Removing dependency', (
2756 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2760 @Cvs2SvnTestFunction
2761 def symbol_mismatches():
2762 "error for conflicting tag/branch"
2766 args
=['--symbol-default=strict'],
2767 error_re
=r
'.*Problems determining how symbols should be converted',
2771 @Cvs2SvnTestFunction
2772 def overlook_symbol_mismatches():
2773 "overlook conflicting tag/branch when --trunk-only"
2775 # This is a test for issue #85.
2777 ensure_conversion('symbol-mess', args
=['--trunk-only'])
2780 @Cvs2SvnTestFunction
2781 def force_symbols():
2782 "force symbols to be tags/branches"
2784 conv
= ensure_conversion(
2786 args
=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2787 if conv
.path_exists('tags', 'BRANCH') \
2788 or not conv
.path_exists('branches', 'BRANCH'):
2790 if not conv
.path_exists('tags', 'TAG') \
2791 or conv
.path_exists('branches', 'TAG'):
2793 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2794 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2796 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2797 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2801 @Cvs2SvnTestFunction
2802 def commit_blocks_tags():
2803 "commit prevents forced tag"
2805 basic_args
= ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2808 args
=(basic_args
+ ['--force-tag=BRANCH_WITH_COMMIT']),
2810 r
'.*The following branches cannot be forced to be tags '
2811 r
'because they have commits'
2816 @Cvs2SvnTestFunction
2817 def blocked_excludes():
2818 "error for blocked excludes"
2820 basic_args
= ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2821 for blocker
in ['BRANCH', 'COMMIT', 'UNNAMED']:
2825 args
=(basic_args
+ ['--exclude=BLOCKED_BY_%s' % blocker
]))
2826 raise MissingErrorException()
2831 @Cvs2SvnTestFunction
2832 def unblock_blocked_excludes():
2833 "excluding blocker removes blockage"
2835 basic_args
= ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2836 for blocker
in ['BRANCH', 'COMMIT']:
2839 args
=(basic_args
+ ['--exclude=BLOCKED_BY_%s' % blocker
,
2840 '--exclude=BLOCKING_%s' % blocker
]))
2843 @Cvs2SvnTestFunction
2844 def regexp_force_symbols():
2845 "force symbols via regular expressions"
2847 conv
= ensure_conversion(
2849 args
=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2850 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2851 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2853 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2854 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2858 @Cvs2SvnTestFunction
2859 def heuristic_symbol_default():
2860 "test 'heuristic' symbol default"
2862 conv
= ensure_conversion(
2863 'symbol-mess', args
=['--symbol-default=heuristic'])
2864 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2865 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2867 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2868 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2872 @Cvs2SvnTestFunction
2873 def branch_symbol_default():
2874 "test 'branch' symbol default"
2876 conv
= ensure_conversion(
2877 'symbol-mess', args
=['--symbol-default=branch'])
2878 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2879 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2881 if conv
.path_exists('tags', 'MOSTLY_TAG') \
2882 or not conv
.path_exists('branches', 'MOSTLY_TAG'):
2886 @Cvs2SvnTestFunction
2887 def tag_symbol_default():
2888 "test 'tag' symbol default"
2890 conv
= ensure_conversion(
2891 'symbol-mess', args
=['--symbol-default=tag'])
2892 if not conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2893 or conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2895 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2896 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2900 @Cvs2SvnTestFunction
2901 def symbol_transform():
2902 "test --symbol-transform"
2904 conv
= ensure_conversion(
2907 '--symbol-default=heuristic',
2908 '--symbol-transform=BRANCH:branch',
2909 '--symbol-transform=TAG:tag',
2910 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2912 if not conv
.path_exists('branches', 'branch'):
2914 if not conv
.path_exists('tags', 'tag'):
2916 if not conv
.path_exists('branches', 'MOSTLY.BRANCH'):
2918 if not conv
.path_exists('tags', 'MOSTLY.TAG'):
2922 @Cvs2SvnTestFunction
2923 def write_symbol_info():
2924 "test --write-symbol-info"
2928 'trunk', 'trunk', '.'],
2929 ['0', 'BLOCKED_BY_UNNAMED',
2930 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2931 ['0', 'BLOCKING_COMMIT',
2932 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2933 ['0', 'BLOCKED_BY_COMMIT',
2934 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2935 ['0', 'BLOCKING_BRANCH',
2936 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2937 ['0', 'BLOCKED_BY_BRANCH',
2938 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2939 ['0', 'MOSTLY_BRANCH',
2943 ['0', 'BRANCH_WITH_COMMIT',
2944 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2946 'branch', 'branches/BRANCH', '.trunk.'],
2948 'tag', 'tags/TAG', '.trunk.'],
2949 ['0', 'unlabeled-1.1.12.1.2',
2950 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2952 expected_lines
.sort()
2954 symbol_info_file
= os
.path
.join(tmp_dir
, 'symbol-mess-symbol-info.txt')
2959 '--symbol-default=strict',
2960 '--write-symbol-info=%s' % (symbol_info_file
,),
2961 '--passes=:CollateSymbolsPass',
2964 raise MissingErrorException()
2968 comment_re
= re
.compile(r
'^\s*\#')
2969 for l
in open(symbol_info_file
, 'r'):
2970 if comment_re
.match(l
):
2972 lines
.append(l
.strip().split())
2974 if lines
!= expected_lines
:
2975 s
= ['Symbol info incorrect\n']
2977 for diffline
in differ
.compare(
2978 [' '.join(line
) + '\n' for line
in expected_lines
],
2979 [' '.join(line
) + '\n' for line
in lines
],
2982 raise Failure(''.join(s
))
2985 @Cvs2SvnTestFunction
2987 "test --symbol-hints for setting branch/tag"
2989 conv
= ensure_conversion(
2990 'symbol-mess', symbol_hints_file
='symbol-mess-symbol-hints.txt',
2992 if not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2994 if not conv
.path_exists('tags', 'MOSTLY_TAG'):
2996 conv
.logs
[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2997 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2999 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3000 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
3002 conv
.logs
[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3003 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
3007 @Cvs2SvnTestFunction
3009 "test --symbol-hints for setting parent"
3011 conv
= ensure_conversion(
3012 'symbol-mess', symbol_hints_file
='symbol-mess-parent-hints.txt',
3014 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3015 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3019 @Cvs2SvnTestFunction
3020 def parent_hints_invalid():
3021 "test --symbol-hints with an invalid parent"
3023 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3024 # this symbol hints file sets the preferred parent to BRANCH
3026 conv
= ensure_conversion(
3027 'symbol-mess', symbol_hints_file
='symbol-mess-parent-hints-invalid.txt',
3029 r
"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
3034 @Cvs2SvnTestFunction
3035 def parent_hints_wildcards():
3036 "test --symbol-hints wildcards"
3038 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3039 # this symbol hints file sets the preferred parent to BRANCH
3041 conv
= ensure_conversion(
3043 symbol_hints_file
='symbol-mess-parent-hints-wildcards.txt',
3045 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3046 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3050 @Cvs2SvnTestFunction
3052 "test --symbol-hints for setting svn paths"
3054 conv
= ensure_conversion(
3055 'symbol-mess', symbol_hints_file
='symbol-mess-path-hints.txt',
3057 conv
.logs
[1].check('Standard project directories initialized by cvs2svn.', (
3060 ('/a/strange', 'A'),
3061 ('/a/strange/trunk', 'A'),
3062 ('/a/strange/trunk/path', 'A'),
3066 conv
.logs
[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3068 ('/special/tag', 'A'),
3069 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
3071 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3072 ('/special/other', 'A'),
3073 ('/special/other/branch', 'A'),
3074 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
3076 conv
.logs
[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3077 ('/special/branch', 'A'),
3078 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
3082 @Cvs2SvnTestFunction
3084 "test problem from issue 99"
3086 conv
= ensure_conversion('issue-99')
3089 @Cvs2SvnTestFunction
3091 "test problem from issue 100"
3093 conv
= ensure_conversion('issue-100')
3094 file1
= conv
.get_wc('trunk', 'file1.txt')
3095 if file(file1
).read() != 'file1.txt<1.2>\n':
3099 @Cvs2SvnTestFunction
3101 "test problem from issue 106"
3103 conv
= ensure_conversion('issue-106')
3106 @Cvs2SvnTestFunction
3107 def options_option():
3108 "use of the --options option"
3110 conv
= ensure_conversion('main', options_file
='cvs2svn.options')
3113 @Cvs2SvnTestFunction
3115 "multiproject conversion"
3117 conv
= ensure_conversion(
3118 'main', options_file
='cvs2svn-multiproject.options'
3120 conv
.logs
[1].check('Standard project directories initialized by cvs2svn.', (
3121 ('/partial-prune', 'A'),
3122 ('/partial-prune/trunk', 'A'),
3123 ('/partial-prune/branches', 'A'),
3124 ('/partial-prune/tags', 'A'),
3125 ('/partial-prune/releases', 'A'),
3129 @Cvs2SvnTestFunction
3131 "multiproject conversion with cross-project commits"
3133 conv
= ensure_conversion(
3134 'main', options_file
='cvs2svn-crossproject.options'
3138 @Cvs2SvnTestFunction
3139 def tag_with_no_revision():
3140 "tag defined but revision is deleted"
3142 conv
= ensure_conversion('tag-with-no-revision')
3145 @Cvs2SvnTestFunction
3146 def delete_cvsignore():
3147 "svn:ignore should vanish when .cvsignore does"
3149 # This is issue #81.
3151 conv
= ensure_conversion('delete-cvsignore')
3153 wc_tree
= conv
.get_wc_tree()
3154 props
= props_for_path(wc_tree
, 'trunk/proj')
3156 if props
.has_key('svn:ignore'):
3160 @Cvs2SvnTestFunction
3161 def repeated_deltatext():
3162 "ignore repeated deltatext blocks with warning"
3164 conv
= ensure_conversion(
3165 'repeated-deltatext',
3167 error_re
=r
'.*Deltatext block for revision 1.1 appeared twice',
3171 @Cvs2SvnTestFunction
3173 "process some nasty dependency graphs"
3175 # It's not how well the bear can dance, but that the bear can dance
3177 conv
= ensure_conversion('nasty-graphs')
3181 @Cvs2SvnTestFunction
3182 def tagging_after_delete():
3183 "optimal tag after deleting files"
3185 conv
= ensure_conversion('tagging-after-delete')
3187 # tag should be 'clean', no deletes
3188 log
= conv
.find_tag_log('tag1')
3190 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
3192 log
.check_changes(expected
)
3195 @Cvs2SvnTestFunction
3196 def crossed_branches():
3197 "branches created in inconsistent orders"
3199 conv
= ensure_conversion('crossed-branches')
3202 @Cvs2SvnTestFunction
3203 def file_directory_conflict():
3204 "error when filename conflicts with directory name"
3206 conv
= ensure_conversion(
3207 'file-directory-conflict',
3208 error_re
=r
'.*Directory name conflicts with filename',
3212 @Cvs2SvnTestFunction
3213 def attic_directory_conflict():
3214 "error when attic filename conflicts with dirname"
3216 # This tests the problem reported in issue #105.
3218 conv
= ensure_conversion(
3219 'attic-directory-conflict',
3220 error_re
=r
'.*Directory name conflicts with filename',
3224 @Cvs2SvnTestFunction
3226 "verify that --use-rcs and --use-internal-co agree"
3228 rcs_conv
= ensure_conversion(
3229 'main', args
=['--use-rcs', '--default-eol=native'], dumpfile
='use-rcs-rcs.dump',
3231 conv
= ensure_conversion(
3232 'main', args
=['--default-eol=native'], dumpfile
='use-rcs-int.dump',
3234 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3236 rcs_lines
= list(open(rcs_conv
.dumpfile
, 'rb'))
3237 lines
= list(open(conv
.dumpfile
, 'rb'))
3238 # Compare all lines following the repository UUID:
3239 if lines
[3:] != rcs_lines
[3:]:
3243 @Cvs2SvnTestFunction
3244 def internal_co_exclude():
3245 "verify that --use-internal-co --exclude=... works"
3247 rcs_conv
= ensure_conversion(
3249 args
=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3250 dumpfile
='internal-co-exclude-rcs.dump',
3252 conv
= ensure_conversion(
3254 args
=['--exclude=BRANCH', '--default-eol=native'],
3255 dumpfile
='internal-co-exclude-int.dump',
3257 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3259 rcs_lines
= list(open(rcs_conv
.dumpfile
, 'rb'))
3260 lines
= list(open(conv
.dumpfile
, 'rb'))
3261 # Compare all lines following the repository UUID:
3262 if lines
[3:] != rcs_lines
[3:]:
3266 @Cvs2SvnTestFunction
3267 def internal_co_trunk_only():
3268 "verify that --use-internal-co --trunk-only works"
3270 conv
= ensure_conversion(
3272 args
=['--trunk-only', '--default-eol=native'],
3274 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3278 @Cvs2SvnTestFunction
3279 def leftover_revs():
3280 "check for leftover checked-out revisions"
3282 conv
= ensure_conversion(
3284 args
=['--exclude=BRANCH', '--default-eol=native'],
3286 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3290 @Cvs2SvnTestFunction
3291 def requires_internal_co():
3292 "test that internal co can do more than RCS"
3293 # See issues 4, 11 for the bugs whose regression we're testing for.
3294 # Unlike in requires_cvs above, issue 29 is not covered.
3295 conv
= ensure_conversion('requires-cvs')
3297 atsign_contents
= file(conv
.get_wc("trunk", "atsign-add")).read()
3299 if atsign_contents
[-1:] == "@":
3302 if not (conv
.logs
[6].author
== "William Lyon Phelps III" and
3303 conv
.logs
[5].author
== "j random"):
3307 @Cvs2SvnTestFunction
3308 def internal_co_keywords():
3309 "test that internal co handles keywords correctly"
3310 conv_ic
= ensure_conversion('internal-co-keywords',
3311 args
=["--keywords-off"])
3312 conv_cvs
= ensure_conversion('internal-co-keywords',
3313 args
=["--use-cvs", "--keywords-off"])
3315 ko_ic
= file(conv_ic
.get_wc('trunk', 'dir', 'ko.txt')).read()
3316 ko_cvs
= file(conv_cvs
.get_wc('trunk', 'dir', 'ko.txt')).read()
3317 kk_ic
= file(conv_ic
.get_wc('trunk', 'dir', 'kk.txt')).read()
3318 kk_cvs
= file(conv_cvs
.get_wc('trunk', 'dir', 'kk.txt')).read()
3319 kv_ic
= file(conv_ic
.get_wc('trunk', 'dir', 'kv.txt')).read()
3320 kv_cvs
= file(conv_cvs
.get_wc('trunk', 'dir', 'kv.txt')).read()
3321 # Ensure proper "/Attic" expansion of $Source$ keyword in files
3322 # which are in a deleted state in trunk
3323 del_ic
= file(conv_ic
.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3324 del_cvs
= file(conv_cvs
.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3331 if del_ic
!= del_cvs
:
3334 # The date format changed between cvs and co ('/' instead of '-').
3335 # Accept either one:
3336 date_substitution_re
= re
.compile(r
' ([0-9]*)-([0-9]*)-([0-9]*) ')
3337 if kv_ic
!= kv_cvs \
3338 and date_substitution_re
.sub(r
' \1/\2/\3 ', kv_ic
) != kv_cvs
:
3342 @Cvs2SvnTestFunction
3343 def timestamp_chaos():
3344 "test timestamp adjustments"
3346 conv
= ensure_conversion('timestamp-chaos')
3348 # The times are expressed here in UTC:
3350 '2007-01-01 21:00:00', # Initial commit
3351 '2007-01-01 21:00:00', # revision 1.1 of both files
3352 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3353 '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards
3354 '2007-01-01 22:00:00', # revision 1.3 of both files
3357 # Convert the times to seconds since the epoch, in UTC:
3358 times
= [calendar
.timegm(svn_strptime(t
)) for t
in times
]
3360 for i
in range(len(times
)):
3361 if abs(conv
.logs
[i
+ 1].date
- times
[i
]) > 0.1:
3365 @Cvs2SvnTestFunction
3367 "convert a repository that contains symlinks"
3369 # This is a test for issue #97.
3371 proj
= os
.path
.join(test_data_dir
, 'symlinks-cvsrepos', 'proj')
3374 os
.path
.join('..', 'file.txt,v'),
3375 os
.path
.join(proj
, 'dir1', 'file.txt,v'),
3379 os
.path
.join(proj
, 'dir2'),
3385 except AttributeError:
3386 # Apparently this OS doesn't support symlinks, so skip test.
3387 raise svntest
.Skip()
3390 for (src
,dst
) in links
:
3391 os
.symlink(src
, dst
)
3393 conv
= ensure_conversion('symlinks')
3394 conv
.logs
[2].check('', (
3395 ('/%(trunk)s/proj', 'A'),
3396 ('/%(trunk)s/proj/file.txt', 'A'),
3397 ('/%(trunk)s/proj/dir1', 'A'),
3398 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3399 ('/%(trunk)s/proj/dir2', 'A'),
3400 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3403 for (src
,dst
) in links
:
3407 @Cvs2SvnTestFunction
3408 def empty_trunk_path():
3409 "allow --trunk to be empty if --trunk-only"
3411 # This is a test for issue #53.
3413 conv
= ensure_conversion(
3414 'main', args
=['--trunk-only', '--trunk='],
3418 @Cvs2SvnTestFunction
3419 def preferred_parent_cycle():
3420 "handle a cycle in branch parent preferences"
3422 conv
= ensure_conversion('preferred-parent-cycle')
3425 @Cvs2SvnTestFunction
3426 def branch_from_empty_dir():
3427 "branch from an empty directory"
3429 conv
= ensure_conversion('branch-from-empty-dir')
3432 @Cvs2SvnTestFunction
3434 "add a file on a branch then on trunk"
3436 conv
= ensure_conversion('trunk-readd')
3439 @Cvs2SvnTestFunction
3440 def branch_from_deleted_1_1():
3441 "branch from a 1.1 revision that will be deleted"
3443 conv
= ensure_conversion('branch-from-deleted-1-1')
3444 conv
.logs
[5].check('Adding b.txt:1.1.2.1', (
3445 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3447 conv
.logs
[6].check('Adding b.txt:1.1.4.1', (
3448 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3450 conv
.logs
[7].check('Adding b.txt:1.2', (
3451 ('/%(trunk)s/proj/b.txt', 'A'),
3454 conv
.logs
[8].check('Adding c.txt:1.1.2.1', (
3455 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3457 conv
.logs
[9].check('Adding c.txt:1.1.4.1', (
3458 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3462 @Cvs2SvnTestFunction
3463 def add_on_branch():
3464 "add a file on a branch using newer CVS"
3466 conv
= ensure_conversion('add-on-branch')
3467 conv
.logs
[6].check('Adding b.txt:1.1', (
3468 ('/%(trunk)s/proj/b.txt', 'A'),
3470 conv
.logs
[7].check('Adding b.txt:1.1.2.2', (
3471 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3473 conv
.logs
[8].check('Adding c.txt:1.1', (
3474 ('/%(trunk)s/proj/c.txt', 'A'),
3476 conv
.logs
[9].check('Removing c.txt:1.2', (
3477 ('/%(trunk)s/proj/c.txt', 'D'),
3479 conv
.logs
[10].check('Adding c.txt:1.2.2.2', (
3480 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3482 conv
.logs
[11].check('Adding d.txt:1.1', (
3483 ('/%(trunk)s/proj/d.txt', 'A'),
3485 conv
.logs
[12].check('Adding d.txt:1.1.2.2', (
3486 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3490 @Cvs2SvnTestFunction
3492 "test output in git-fast-import format"
3494 # Note: To test importing into git, do
3496 # ./run-tests <this-test-number>
3497 # rm -rf cvs2svn-tmp/main.git
3498 # git init --bare cvs2svn-tmp/main.git
3499 # cd cvs2svn-tmp/main.git
3500 # cat ../git-{blob,dump}.dat | git fast-import
3502 # Or, to load the dumpfiles separately:
3504 # cat ../git-blob.dat | git fast-import --export-marks=../git-marks.dat
3505 # cat ../git-dump.dat | git fast-import --import-marks=../git-marks.dat
3507 # Then use "gitk --all", "git log", etc. to test the contents of the
3508 # repository or "git clone" to make a non-bare clone.
3510 # We don't have the infrastructure to check that the resulting git
3511 # repository is correct, so we just check that the conversion runs
3513 conv
= GitConversion('main', None, [
3514 '--blobfile=cvs2svn-tmp/git-blob.dat',
3515 '--dumpfile=cvs2svn-tmp/git-dump.dat',
3516 '--username=cvs2git',
3517 'test-data/main-cvsrepos',
3521 @Cvs2SvnTestFunction
3523 "test cvs2git --use-external-blob-generator option"
3525 # See comment in main_git() for more information.
3527 conv
= GitConversion('main', None, [
3528 '--use-external-blob-generator',
3529 '--blobfile=cvs2svn-tmp/blobfile.out',
3530 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3531 '--username=cvs2git',
3532 'test-data/main-cvsrepos',
3536 @Cvs2SvnTestFunction
3537 def main_git_merged():
3538 "cvs2git with no blobfile"
3540 # Note: To test importing into git, do
3542 # ./run-tests <this-test-number>
3543 # rm -rf cvs2svn-tmp/main.git
3544 # git init --bare cvs2svn-tmp/main.git
3545 # cd cvs2svn-tmp/main.git
3546 # cat ../git-dump.dat | git fast-import
3548 conv
= GitConversion('main', None, [
3549 '--dumpfile=cvs2svn-tmp/git-dump.dat',
3550 '--username=cvs2git',
3551 'test-data/main-cvsrepos',
3555 @Cvs2SvnTestFunction
3556 def main_git2_merged():
3557 "cvs2git external with no blobfile"
3559 # See comment in main_git_merged() for more information.
3561 conv
= GitConversion('main', None, [
3562 '--use-external-blob-generator',
3563 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3564 '--username=cvs2git',
3565 'test-data/main-cvsrepos',
3569 @Cvs2SvnTestFunction
3571 "test cvs2git using options file"
3573 conv
= GitConversion('main', None, [], options_file
='cvs2git.options')
3576 @Cvs2SvnTestFunction
3578 "output in git-fast-import format with inline data"
3580 # The output should be suitable for import by Mercurial.
3582 # We don't have the infrastructure to check that the resulting
3583 # Mercurial repository is correct, so we just check that the
3584 # conversion runs to completion:
3585 conv
= GitConversion('main', None, [], options_file
='cvs2hg.options')
3588 @Cvs2SvnTestFunction
3589 def invalid_symbol():
3590 "a symbol with the incorrect format"
3592 conv
= ensure_conversion(
3595 error_re
=r
".*branch 'SYMBOL' references invalid revision 1$",
3599 @Cvs2SvnTestFunction
3600 def invalid_symbol_ignore():
3601 "ignore a symbol using a SymbolMapper"
3603 conv
= ensure_conversion(
3604 'invalid-symbol', options_file
='cvs2svn-ignore.options'
3608 @Cvs2SvnTestFunction
3609 def invalid_symbol_ignore2():
3610 "ignore a symbol using an IgnoreSymbolTransform"
3612 conv
= ensure_conversion(
3613 'invalid-symbol', options_file
='cvs2svn-ignore2.options'
3617 class EOLVariants(Cvs2SvnTestCase
):
3618 "handle various --eol-style options"
3620 eol_style_strings
= {
3627 def __init__(self
, eol_style
):
3628 self
.eol_style
= eol_style
3629 self
.dumpfile
= 'eol-variants-%s.dump' % (self
.eol_style
,)
3630 Cvs2SvnTestCase
.__init
__(
3631 self
, 'eol-variants', variant
=self
.eol_style
,
3632 dumpfile
=self
.dumpfile
,
3634 '--default-eol=%s' % (self
.eol_style
,),
3638 def run(self
, sbox
):
3639 conv
= self
.ensure_conversion()
3640 dump_contents
= open(conv
.dumpfile
, 'rb').read()
3641 expected_text
= self
.eol_style_strings
[self
.eol_style
].join(
3642 ['line 1', 'line 2', '\n\n']
3644 if not dump_contents
.endswith(expected_text
):
3648 @Cvs2SvnTestFunction
3650 "handle a file with no revisions (issue #80)"
3652 conv
= ensure_conversion('no-revs-file')
3655 @Cvs2SvnTestFunction
3656 def mirror_keyerror_test():
3657 "a case that gave KeyError in SVNRepositoryMirror"
3659 conv
= ensure_conversion('mirror-keyerror')
3662 @Cvs2SvnTestFunction
3663 def exclude_ntdb_test():
3664 "exclude a non-trunk default branch"
3666 symbol_info_file
= os
.path
.join(tmp_dir
, 'exclude-ntdb-symbol-info.txt')
3667 conv
= ensure_conversion(
3670 '--write-symbol-info=%s' % (symbol_info_file
,),
3671 '--exclude=branch3',
3673 '--exclude=vendortag3',
3674 '--exclude=vendorbranch',
3679 @Cvs2SvnTestFunction
3680 def mirror_keyerror2_test():
3681 "a case that gave KeyError in RepositoryMirror"
3683 conv
= ensure_conversion('mirror-keyerror2')
3686 @Cvs2SvnTestFunction
3687 def mirror_keyerror3_test():
3688 "a case that gave KeyError in RepositoryMirror"
3690 conv
= ensure_conversion('mirror-keyerror3')
3694 @Cvs2SvnTestFunction
3695 def add_cvsignore_to_branch_test():
3696 "check adding .cvsignore to an existing branch"
3698 # This a test for issue #122.
3700 conv
= ensure_conversion('add-cvsignore-to-branch')
3701 wc_tree
= conv
.get_wc_tree()
3702 trunk_props
= props_for_path(wc_tree
, 'trunk/dir')
3703 if trunk_props
['svn:ignore'] != '*.o\n\n':
3706 branch_props
= props_for_path(wc_tree
, 'branches/BRANCH/dir')
3707 if branch_props
['svn:ignore'] != '*.o\n\n':
3711 @Cvs2SvnTestFunction
3712 def missing_deltatext():
3713 "a revision's deltatext is missing"
3715 # This is a type of RCS file corruption that has been observed.
3716 conv
= ensure_conversion(
3717 'missing-deltatext',
3719 r
"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3724 @Cvs2SvnTestFunction
3725 def transform_unlabeled_branch_name():
3726 "transform name of unlabeled branch"
3728 conv
= ensure_conversion(
3731 '--symbol-transform=unlabeled-1.1.4:BRANCH2',
3734 if conv
.path_exists('branches', 'unlabeled-1.1.4'):
3735 raise Failure('Branch unlabeled-1.1.4 not excluded')
3736 if not conv
.path_exists('branches', 'BRANCH2'):
3737 raise Failure('Branch BRANCH2 not found')
3740 @Cvs2SvnTestFunction
3741 def ignore_unlabeled_branch():
3742 "ignoring an unlabeled branch is not allowed"
3744 conv
= ensure_conversion(
3746 options_file
='cvs2svn-ignore.options',
3748 r
"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' "
3749 r
"in \'.*\' contains commits"
3754 @Cvs2SvnTestFunction
3755 def exclude_unlabeled_branch():
3756 "exclude unlabeled branch"
3758 conv
= ensure_conversion(
3760 args
=['--exclude=unlabeled-.*'],
3762 if conv
.path_exists('branches', 'unlabeled-1.1.4'):
3763 raise Failure('Branch unlabeled-1.1.4 not excluded')
3766 @Cvs2SvnTestFunction
3767 def unlabeled_branch_name_collision():
3768 "transform unlabeled branch to same name as branch"
3770 conv
= ensure_conversion(
3773 '--symbol-transform=unlabeled-1.1.4:BRANCH',
3776 r
"ERROR\: Symbol name \'BRANCH\' is already used"
3781 @Cvs2SvnTestFunction
3782 def collision_with_unlabeled_branch_name():
3783 "transform branch to same name as unlabeled branch"
3785 conv
= ensure_conversion(
3788 '--symbol-transform=BRANCH:unlabeled-1.1.4',
3791 r
"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used"
3796 @Cvs2SvnTestFunction
3798 "a repo with many removable dead revisions"
3800 conv
= ensure_conversion('many-deletes')
3801 conv
.logs
[5].check('Add files on BRANCH', (
3802 ('/%(branches)s/BRANCH/proj/b.txt', 'A'),
3804 conv
.logs
[6].check('Add files on BRANCH2', (
3805 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3806 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3807 ('/%(branches)s/BRANCH2/proj/d.txt', 'A'),
3811 cvs_description
= Cvs2SvnPropertiesTestCase(
3813 doc
='test handling of CVS file descriptions',
3814 props_to_test
=['cvs:description'],
3816 ('trunk/proj/default', ['This is an example file description.']),
3817 ('trunk/proj/sub1/default', [None]),
3821 @Cvs2SvnTestFunction
3822 def include_empty_directories():
3823 "test --include-empty-directories option"
3825 conv
= ensure_conversion(
3826 'empty-directories', args
=['--include-empty-directories'],
3828 conv
.logs
[1].check('Standard project directories', (
3829 ('/%(trunk)s', 'A'),
3830 ('/%(branches)s', 'A'),
3832 ('/%(trunk)s/root-empty-directory', 'A'),
3833 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3835 conv
.logs
[3].check('Add b.txt.', (
3836 ('/%(trunk)s/direct', 'A'),
3837 ('/%(trunk)s/direct/b.txt', 'A'),
3838 ('/%(trunk)s/direct/empty-directory', 'A'),
3839 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3841 conv
.logs
[4].check('Add c.txt.', (
3842 ('/%(trunk)s/indirect', 'A'),
3843 ('/%(trunk)s/indirect/subdirectory', 'A'),
3844 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3845 ('/%(trunk)s/indirect/empty-directory', 'A'),
3846 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3848 conv
.logs
[5].check('Remove b.txt', (
3849 ('/%(trunk)s/direct', 'D'),
3851 conv
.logs
[6].check('Remove c.txt', (
3852 ('/%(trunk)s/indirect', 'D'),
3854 conv
.logs
[7].check('Re-add b.txt.', (
3855 ('/%(trunk)s/direct', 'A'),
3856 ('/%(trunk)s/direct/b.txt', 'A'),
3857 ('/%(trunk)s/direct/empty-directory', 'A'),
3858 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3860 conv
.logs
[8].check('Re-add c.txt.', (
3861 ('/%(trunk)s/indirect', 'A'),
3862 ('/%(trunk)s/indirect/subdirectory', 'A'),
3863 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3864 ('/%(trunk)s/indirect/empty-directory', 'A'),
3865 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3867 conv
.logs
[9].check('This commit was manufactured', (
3868 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3870 conv
.logs
[10].check('This commit was manufactured', (
3871 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3873 conv
.logs
[11].check('Import d.txt.', (
3874 ('/%(branches)s/VENDORBRANCH', 'A'),
3875 ('/%(branches)s/VENDORBRANCH/import', 'A'),
3876 ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'),
3877 ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'),
3878 ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory',
3880 ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'),
3881 ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory',
3884 conv
.logs
[12].check('This commit was generated', (
3885 ('/%(trunk)s/import', 'A'),
3886 ('/%(trunk)s/import/d.txt '
3887 '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'),
3888 ('/%(trunk)s/import/empty-directory', 'A'),
3889 ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'),
3893 @Cvs2SvnTestFunction
3894 def include_empty_directories_no_prune():
3895 "test --include-empty-directories with --no-prune"
3897 conv
= ensure_conversion(
3898 'empty-directories', args
=['--include-empty-directories', '--no-prune'],
3900 conv
.logs
[1].check('Standard project directories', (
3901 ('/%(trunk)s', 'A'),
3902 ('/%(branches)s', 'A'),
3904 ('/%(trunk)s/root-empty-directory', 'A'),
3905 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3907 conv
.logs
[3].check('Add b.txt.', (
3908 ('/%(trunk)s/direct', 'A'),
3909 ('/%(trunk)s/direct/b.txt', 'A'),
3910 ('/%(trunk)s/direct/empty-directory', 'A'),
3911 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3913 conv
.logs
[4].check('Add c.txt.', (
3914 ('/%(trunk)s/indirect', 'A'),
3915 ('/%(trunk)s/indirect/subdirectory', 'A'),
3916 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3917 ('/%(trunk)s/indirect/empty-directory', 'A'),
3918 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3920 conv
.logs
[5].check('Remove b.txt', (
3921 ('/%(trunk)s/direct/b.txt', 'D'),
3923 conv
.logs
[6].check('Remove c.txt', (
3924 ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'),
3926 conv
.logs
[7].check('Re-add b.txt.', (
3927 ('/%(trunk)s/direct/b.txt', 'A'),
3929 conv
.logs
[8].check('Re-add c.txt.', (
3930 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3932 conv
.logs
[9].check('This commit was manufactured', (
3933 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3935 conv
.logs
[10].check('This commit was manufactured', (
3936 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3940 @Cvs2SvnTestFunction
3941 def exclude_symbol_default():
3942 "test 'exclude' symbol default"
3944 conv
= ensure_conversion(
3945 'symbol-mess', args
=['--symbol-default=exclude'])
3946 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
3947 or conv
.path_exists('branches', 'MOSTLY_BRANCH'):
3949 if conv
.path_exists('tags', 'MOSTLY_TAG') \
3950 or conv
.path_exists('branches', 'MOSTLY_TAG'):
3954 @Cvs2SvnTestFunction
3955 def add_on_branch2():
3956 "another add-on-branch test case"
3958 conv
= ensure_conversion('add-on-branch2')
3959 if len(conv
.logs
) != 2:
3961 conv
.logs
[2].check('add file on branch', (
3962 ('/%(branches)s/BRANCH', 'A'),
3963 ('/%(branches)s/BRANCH/file1', 'A'),
3967 @Cvs2SvnTestFunction
3968 def branch_from_vendor_branch():
3969 "branch from vendor branch"
3972 'branch-from-vendor-branch',
3973 symbol_hints_file
='branch-from-vendor-branch-symbol-hints.txt',
3977 @Cvs2SvnTestFunction
3978 def strange_default_branch():
3979 "default branch too deep in the hierarchy"
3982 'strange-default-branch',
3984 r
'ERROR\: The default branch 1\.2\.4\.3\.2\.1\.2 '
3985 r
'in file .* is not a top-level branch'
3990 @Cvs2SvnTestFunction
3992 "graft onto preferred parent that was itself moved"
3994 conv
= ensure_conversion(
3997 conv
.logs
[2].check('first', (
3998 ('/%(trunk)s/file1', 'A'),
3999 ('/%(trunk)s/file2', 'A'),
4001 conv
.logs
[3].check('This commit was manufactured', (
4002 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
4004 conv
.logs
[4].check('second', (
4005 ('/%(branches)s/b2/file1', 'M'),
4007 conv
.logs
[5].check('This commit was manufactured', (
4008 ('/%(branches)s/b1 (from /%(branches)s/b2:4)', 'A'),
4011 # b2 and b1 are equally good parents for b3, so accept either one.
4012 # (Currently, cvs2svn chooses b1 as the preferred parent because it
4013 # comes earlier than b2 in alphabetical order.)
4015 conv
.logs
[6].check('This commit was manufactured', (
4016 ('/%(branches)s/b3 (from /%(branches)s/b1:5)', 'A'),
4019 conv
.logs
[6].check('This commit was manufactured', (
4020 ('/%(branches)s/b3 (from /%(branches)s/b2:4)', 'A'),
4024 @Cvs2SvnTestFunction
4025 def log_message_eols():
4026 "nonstandard EOLs in log messages"
4028 conv
= ensure_conversion(
4031 conv
.logs
[2].check('The CRLF at the end of this line\nshould', (
4032 ('/%(trunk)s/lottalogs', 'A'),
4034 conv
.logs
[3].check('The CR at the end of this line\nshould', (
4035 ('/%(trunk)s/lottalogs', 'M'),
4039 @Cvs2SvnTestFunction
4040 def missing_vendor_branch():
4041 "default branch not present in RCS file"
4043 conv
= ensure_conversion(
4044 'missing-vendor-branch',
4046 error_re
=r
'.*vendor branch \'1\
.1\
.1\' is not present
in file and will be ignored
',
4050 @Cvs2SvnTestFunction
4052 "newphrases in RCS files"
4059 ########################################################################
4062 # list all tests here, starting with None:
4074 PruneWithCare(variant=1, trunk='a
', branches='b
', tags='c
'),
4076 PruneWithCare(variant=2, trunk='a
/1', branches='b
/1', tags='c
/1'),
4077 PruneWithCare(variant=3, trunk='a
/1', branches='a
/2', tags='a
/3'),
4078 interleaved_commits,
4081 SimpleTags(variant=1, trunk='a
', branches='b
', tags='c
'),
4082 SimpleTags(variant=2, trunk='a
/1', branches='b
/1', tags='c
/1'),
4083 SimpleTags(variant=3, trunk='a
/1', branches='a
/2', tags='a
/3'),
4084 simple_branch_commits,
4087 mixed_time_branch_with_added_file,
4093 PhoenixBranch(variant=1, trunk='a
/1', branches='b
/1', tags='c
/1'),
4098 NoTrunkPrune(variant=1, trunk='a
', branches='b
', tags='c
'),
4099 NoTrunkPrune(variant=2, trunk='a
/1', branches='b
/1', tags='c
/1'),
4100 NoTrunkPrune(variant=3, trunk='a
/1', branches='a
/2', tags='a
/3'),
4104 TaggedBranchAndTrunk(),
4105 TaggedBranchAndTrunk(variant=1, trunk='a
/1', branches='a
/2', tags='a
/3'),
4109 BranchDeleteFirst(),
4110 BranchDeleteFirst(variant=1, trunk='a
/1', branches='a
/2', tags='a
/3'),
4114 warning_expected=1),
4117 variant='encoding
', args=['--encoding
=utf_8
']),
4120 variant='fallback
-encoding
', args=['--fallback
-encoding
=utf_8
']),
4122 warning_expected=1),
4125 variant='encoding
', args=['--encoding
=utf_8
']),
4128 variant='fallback
-encoding
', args=['--fallback
-encoding
=utf_8
']),
4130 vendor_branch_sameness,
4131 vendor_branch_trunk_only,
4133 default_branches_trunk_only,
4134 default_branch_and_1_2,
4135 compose_tag_three_sources,
4138 PeerPathPruning(variant=1, trunk='a
/1', branches='a
/2', tags='a
/3'),
4141 EmptyTrunk(variant=1, trunk='a
', branches='b
', tags='c
'),
4142 EmptyTrunk(variant=2, trunk='a
/1', branches='a
/2', tags='a
/3'),
4143 no_spurious_svn_commits,
4144 invalid_closings_on_trunk,
4147 branch_from_default_branch,
4149 retain_file_in_attic_too,
4150 symbolic_name_filling_guide,
4161 questionable_branch_names,
4163 questionable_tag_names,
4164 revision_reorder_bug,
4166 vendor_branch_delete_add,
4167 resync_pass2_pull_forward,
4171 resync_pass2_push_backward,
4175 nested_ttb_directories,
4176 auto_props_ignore_case,
4177 ctrl_char_in_filename,
4178 commit_dependencies,
4181 multiply_defined_symbols,
4182 multiply_defined_symbols_renamed,
4183 multiply_defined_symbols_ignored,
4185 repeatedly_defined_symbols,
4186 double_branch_delete,
4188 overlook_symbol_mismatches,
4192 unblock_blocked_excludes,
4193 regexp_force_symbols,
4194 heuristic_symbol_default,
4196 branch_symbol_default,
4202 parent_hints_invalid,
4203 parent_hints_wildcards,
4212 tag_with_no_revision,
4216 tagging_after_delete,
4219 file_directory_conflict,
4220 attic_directory_conflict,
4222 internal_co_exclude,
4223 internal_co_trunk_only,
4224 internal_co_keywords,
4226 requires_internal_co,
4231 preferred_parent_cycle,
4232 branch_from_empty_dir,
4234 branch_from_deleted_1_1,
4244 invalid_symbol_ignore,
4245 invalid_symbol_ignore2,
4248 EOLVariants('CRLF
'),
4249 EOLVariants('native
'),
4252 mirror_keyerror_test,
4254 mirror_keyerror2_test,
4255 mirror_keyerror3_test,
4256 add_cvsignore_to_branch_test,
4258 transform_unlabeled_branch_name,
4259 ignore_unlabeled_branch,
4260 exclude_unlabeled_branch,
4262 unlabeled_branch_name_collision,
4263 collision_with_unlabeled_branch_name,
4266 include_empty_directories,
4267 include_empty_directories_no_prune,
4268 exclude_symbol_default,
4270 branch_from_vendor_branch,
4271 strange_default_branch,
4275 missing_vendor_branch,
4279 if __name__ == '__main__
':
4281 # Configure the environment for reproducable output from svn, etc.
4282 os.environ["LC_ALL"] = "C"
4284 # Unfortunately, there is no way under Windows to make Subversion
4285 # think that the local time zone is UTC, so we just work in the
4288 # The Subversion test suite code assumes it's being invoked
from
4289 # within a working copy of the Subversion sources, and tries to use
4290 # the binaries in that tree. Since the cvs2svn tree never contains
4291 # a Subversion build, we just use the system's installed binaries.
4292 svntest
.main
.svn_binary
= svn_binary
4293 svntest
.main
.svnlook_binary
= svnlook_binary
4294 svntest
.main
.svnadmin_binary
= svnadmin_binary
4295 svntest
.main
.svnversion_binary
= svnversion_binary
4297 svntest
.main
.run_tests(test_list
)