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
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 print the stderr lines if
124 svntest.main.options.verbose is true.
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 if svntest
.main
.options
.verbose
:
146 print '\n%s said:\n' % program
150 raise RunProgramException()
155 def run_script(script
, error_re
, *varargs
):
156 """Run Python script SCRIPT with VARARGS, returning stdout as a list
159 If there is any stderr and ERROR_RE is None, raise
160 RunProgramException, and print the stderr lines if
161 svntest.main.options.verbose is true.
163 If ERROR_RE is not None, it is a string regular expression that must
164 match some line of stderr. If it fails to match, raise
165 MissingErrorException."""
167 # Use the same python that is running this script
168 return run_program(sys
.executable
, error_re
, script
, *varargs
)
169 # On Windows, for an unknown reason, the cmd.exe process invoked by
170 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
171 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
172 # this. Therefore, the redirection of the output to the .s-revs file fails.
173 # We no longer use the problematic invocation on any system, but this
174 # comment remains to warn about this problem.
177 def run_svn(*varargs
):
178 """Run svn with VARARGS; return stdout as a list of lines.
179 If there is any stderr, raise RunProgramException, and print the
180 stderr lines if svntest.main.options.verbose is true."""
181 return run_program(svn_binary
, None, *varargs
)
184 def repos_to_url(path_to_svn_repos
):
185 """This does what you think it does."""
186 rpath
= os
.path
.abspath(path_to_svn_repos
)
189 return 'file://%s' % rpath
.replace(os
.sep
, '/')
192 def svn_strptime(timestr
):
193 return time
.strptime(timestr
, '%Y-%m-%d %H:%M:%S')
197 def __init__(self
, revision
, author
, date
, symbols
):
198 self
.revision
= revision
201 # Internally, we represent the date as seconds since epoch (UTC).
202 # Since standard subversion log output shows dates in localtime
204 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
206 # and time.mktime() converts from localtime, it all works out very
208 self
.date
= time
.mktime(svn_strptime(date
[0:19]))
210 # The following symbols are used for string interpolation when
212 self
.symbols
= symbols
214 # The changed paths will be accumulated later, as log data is read.
215 # Keys here are paths such as '/trunk/foo/bar', values are letter
216 # codes such as 'M', 'A', and 'D'.
217 self
.changed_paths
= { }
219 # The msg will be accumulated later, as log data is read.
222 def absorb_changed_paths(self
, out
):
223 'Read changed paths from OUT into self, until no more.'
225 line
= out
.readline()
226 if len(line
) == 1: return
228 op_portion
= line
[3:4]
229 path_portion
= line
[5:]
230 # If we're running on Windows we get backslashes instead of
232 path_portion
= path_portion
.replace('\\', '/')
233 # # We could parse out history information, but currently we
234 # # just leave it in the path portion because that's how some
237 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
239 # path_portion = m.group(1)
240 self
.changed_paths
[path_portion
] = op_portion
242 def __cmp__(self
, other
):
243 return cmp(self
.revision
, other
.revision
) or \
244 cmp(self
.author
, other
.author
) or cmp(self
.date
, other
.date
) or \
245 cmp(self
.changed_paths
, other
.changed_paths
) or \
246 cmp(self
.msg
, other
.msg
)
248 def get_path_op(self
, path
):
249 """Return the operator for the change involving PATH.
251 PATH is allowed to include string interpolation directives (e.g.,
252 '%(trunk)s'), which are interpolated against self.symbols. Return
253 None if there is no record for PATH."""
254 return self
.changed_paths
.get(path
% self
.symbols
)
256 def check_msg(self
, msg
):
257 """Verify that this Log's message starts with the specified MSG."""
258 if self
.msg
.find(msg
) != 0:
260 "Revision %d log message was:\n%s\n\n"
261 "It should have begun with:\n%s\n\n"
262 % (self
.revision
, self
.msg
, msg
,)
265 def check_change(self
, path
, op
):
266 """Verify that this Log includes a change for PATH with operator OP.
268 PATH is allowed to include string interpolation directives (e.g.,
269 '%(trunk)s'), which are interpolated against self.symbols."""
271 path
= path
% self
.symbols
272 found_op
= self
.changed_paths
.get(path
, None)
275 "Revision %d does not include change for path %s "
276 "(it should have been %s).\n"
277 % (self
.revision
, path
, op
,)
281 "Revision %d path %s had op %s (it should have been %s)\n"
282 % (self
.revision
, path
, found_op
, op
,)
285 def check_changes(self
, changed_paths
):
286 """Verify that this Log has precisely the CHANGED_PATHS specified.
288 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
289 strings are allowed to include string interpolation directives
290 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
293 for (path
, op
) in changed_paths
:
294 cp
[path
% self
.symbols
] = op
296 if self
.changed_paths
!= cp
:
298 "Revision %d changed paths list was:\n%s\n\n"
299 "It should have been:\n%s\n\n"
300 % (self
.revision
, self
.changed_paths
, cp
,)
303 def check(self
, msg
, changed_paths
):
304 """Verify that this Log has the MSG and CHANGED_PATHS specified.
306 Convenience function to check two things at once. MSG is passed
307 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
310 self
.check_changes(changed_paths
)
313 def parse_log(svn_repos
, symbols
):
314 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
316 Initialize the Logs' symbols with SYMBOLS."""
319 'Make a list of lines behave like an open file handle.'
320 def __init__(self
, lines
):
323 if len(self
.lines
) > 0:
324 return self
.lines
.pop(0)
328 def absorb_message_body(out
, num_lines
, log
):
329 """Read NUM_LINES of log message body from OUT into Log item LOG."""
331 for i
in range(num_lines
):
332 log
.msg
+= out
.readline()
334 log_start_re
= re
.compile('^r(?P<rev>[0-9]+) \| '
335 '(?P<author>[^\|]+) \| '
337 '\| (?P<lines>[0-9]+) (line|lines)$')
339 log_separator
= '-' * 72
343 out
= LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos
)))
347 line
= out
.readline()
351 if line
.find(log_separator
) == 0:
352 line
= out
.readline()
355 m
= log_start_re
.match(line
)
358 int(m
.group('rev')), m
.group('author'), m
.group('date'), symbols
)
359 line
= out
.readline()
360 if not line
.find('Changed paths:') == 0:
361 print 'unexpected log output (missing changed paths)'
362 print "Line: '%s'" % line
364 this_log
.absorb_changed_paths(out
)
365 absorb_message_body(out
, int(m
.group('lines')), this_log
)
366 logs
[this_log
.revision
] = this_log
368 break # We've reached the end of the log output.
370 print 'unexpected log output (missing revision line)'
371 print "Line: '%s'" % line
374 print 'unexpected log output (missing log separator)'
375 print "Line: '%s'" % line
382 """Unconditionally remove PATH and its subtree, if any. PATH may be
383 non-existent, a file or symlink, or a directory."""
384 if os
.path
.isdir(path
):
386 elif os
.path
.exists(path
):
390 log_msg_text_wrapper
= textwrap
.TextWrapper(width
=76, break_long_words
=False)
392 def sym_log_msg(symbolic_name
, is_tag
=None):
393 """Return the expected log message for a cvs2svn-synthesized revision
394 creating branch or tag SYMBOLIC_NAME."""
396 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
402 return log_msg_text_wrapper
.fill(
403 "This commit was manufactured by cvs2svn to create %s '%s'."
404 % (type, symbolic_name
)
408 def make_conversion_id(
409 name
, args
, passbypass
, options_file
=None, symbol_hints_file
=None
411 """Create an identifying tag for a conversion.
413 The return value can also be used as part of a filesystem path.
415 NAME is the name of the CVS repository.
417 ARGS are the extra arguments to be passed to cvs2svn.
419 PASSBYPASS is a boolean indicating whether the conversion is to be
420 run one pass at a time.
422 If OPTIONS_FILE is specified, it is an options file that will be
423 used for the conversion.
425 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
426 will be used for the conversion.
428 The 1-to-1 mapping between cvs2svn command parameters and
429 conversion_ids allows us to avoid running the same conversion more
430 than once, when multiple tests use exactly the same conversion."""
437 args
.append('--passbypass')
439 if symbol_hints_file
is not None:
440 args
.append('--symbol-hints=%s' % (symbol_hints_file
,))
442 # There are some characters that are forbidden in filenames, and
443 # there is a limit on the total length of a path to a file. So use
444 # a hash of the parameters rather than concatenating the parameters
447 conv_id
+= "-" + md5('\0'.join(args
)).hexdigest()
449 # Some options-file based tests rely on knowing the paths to which
450 # the repository should be written, so we handle that option as a
451 # predictable string:
452 if options_file
is not None:
453 conv_id
+= '--options=%s' % (options_file
,)
459 """A record of a cvs2svn conversion.
463 conv_id -- the conversion id for this Conversion.
465 name -- a one-word name indicating the involved repositories.
467 dumpfile -- the name of the SVN dumpfile created by the conversion
468 (if the DUMPFILE constructor argument was used); otherwise,
471 repos -- the path to the svn repository. Unset if DUMPFILE was
474 logs -- a dictionary of Log instances, as returned by parse_log().
475 Unset if DUMPFILE was specified.
477 symbols -- a dictionary of symbols used for string interpolation
480 stdout -- a list of lines written by cvs2svn to stdout
482 _wc -- the basename of the svn working copy (within tmp_dir).
483 Unset if DUMPFILE was specified.
485 _wc_path -- the path to the svn working copy, if it has already
486 been created; otherwise, None. (The working copy is created
487 lazily when get_wc() is called.) Unset if DUMPFILE was
490 _wc_tree -- the tree built from the svn working copy, if it has
491 already been created; otherwise, None. The tree is created
492 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
495 _svnrepos -- the basename of the svn repository (within tmp_dir).
496 Unset if DUMPFILE was specified."""
498 # The number of the last cvs2svn pass (determined lazily by
503 def get_last_pass(cls
):
504 """Return the number of cvs2svn's last pass."""
506 if cls
.last_pass
is None:
507 out
= run_script(cvs2svn
, None, '--help-passes')
508 cls
.last_pass
= int(out
[-1].split()[0])
512 self
, conv_id
, name
, error_re
, passbypass
, symbols
, args
,
513 options_file
=None, symbol_hints_file
=None, dumpfile
=None,
515 self
.conv_id
= conv_id
517 self
.symbols
= symbols
518 if not os
.path
.isdir(tmp_dir
):
521 cvsrepos
= os
.path
.join(test_data_dir
, '%s-cvsrepos' % self
.name
)
524 self
.dumpfile
= os
.path
.join(tmp_dir
, dumpfile
)
525 # Clean up from any previous invocations of this script.
529 self
.repos
= os
.path
.join(tmp_dir
, '%s-svnrepos' % self
.conv_id
)
530 self
._wc
= os
.path
.join(tmp_dir
, '%s-wc' % self
.conv_id
)
534 # Clean up from any previous invocations of this script.
540 '--svnadmin=%s' % (svntest
.main
.svnadmin_binary
,),
543 self
.options_file
= os
.path
.join(cvsrepos
, options_file
)
545 '--options=%s' % self
.options_file
,
547 assert not symbol_hints_file
549 self
.options_file
= None
550 if tmp_dir
!= 'cvs2svn-tmp':
551 # Only include this argument if it differs from cvs2svn's default:
553 '--tmpdir=%s' % tmp_dir
,
556 if symbol_hints_file
:
557 self
.symbol_hints_file
= os
.path
.join(cvsrepos
, symbol_hints_file
)
559 '--symbol-hints=%s' % self
.symbol_hints_file
,
563 args
.extend(['--dumpfile=%s' % (self
.dumpfile
,)])
565 args
.extend(['-s', self
.repos
])
566 args
.extend([cvsrepos
])
570 for p
in range(1, self
.get_last_pass() + 1):
571 self
.stdout
+= run_script(cvs2svn
, error_re
, '-p', str(p
), *args
)
573 self
.stdout
= run_script(cvs2svn
, error_re
, *args
)
576 if not os
.path
.isfile(self
.dumpfile
):
578 "Dumpfile not created: '%s'"
579 % os
.path
.join(os
.getcwd(), self
.dumpfile
)
582 if os
.path
.isdir(self
.repos
):
583 self
.logs
= parse_log(self
.repos
, self
.symbols
)
584 elif error_re
is None:
586 "Repository not created: '%s'"
587 % os
.path
.join(os
.getcwd(), self
.repos
)
590 def output_found(self
, pattern
):
591 """Return True if PATTERN matches any line in self.stdout.
593 PATTERN is a regular expression pattern as a string.
596 pattern_re
= re
.compile(pattern
)
598 for line
in self
.stdout
:
599 if pattern_re
.match(line
):
600 # We found the pattern that we were looking for.
605 def find_tag_log(self
, tagname
):
606 """Search LOGS for a log message containing 'TAGNAME' and return the
607 log in which it was found."""
608 for i
in xrange(len(self
.logs
), 0, -1):
609 if self
.logs
[i
].msg
.find("'"+tagname
+"'") != -1:
611 raise ValueError("Tag %s not found in logs" % tagname
)
613 def get_wc(self
, *args
):
614 """Return the path to the svn working copy, or a path within the WC.
616 If a working copy has not been created yet, create it now.
618 If ARGS are specified, then they should be strings that form
619 fragments of a path within the WC. They are joined using
620 os.path.join() and appended to the WC path."""
622 if self
._wc
_path
is None:
623 run_svn('co', repos_to_url(self
.repos
), self
._wc
)
624 self
._wc
_path
= self
._wc
625 return os
.path
.join(self
._wc
_path
, *args
)
627 def get_wc_tree(self
):
628 if self
._wc
_tree
is None:
629 self
._wc
_tree
= svntest
.tree
.build_tree_from_wc(self
.get_wc(), 1)
632 def path_exists(self
, *args
):
633 """Return True if the specified path exists within the repository.
635 (The strings in ARGS are first joined into a path using
638 return os
.path
.exists(self
.get_wc(*args
))
640 def check_props(self
, keys
, checks
):
641 """Helper function for checking lots of properties. For a list of
642 files in the conversion, check that the values of the properties
643 listed in KEYS agree with those listed in CHECKS. CHECKS is a
644 list of tuples: [ (filename, [value, value, ...]), ...], where the
645 values are listed in the same order as the key names are listed in
648 for (file, values
) in checks
:
649 assert len(values
) == len(keys
)
650 props
= props_for_path(self
.get_wc_tree(), file)
651 for i
in range(len(keys
)):
652 if props
.get(keys
[i
]) != values
[i
]:
654 "File %s has property %s set to \"%s\" "
655 "(it should have been \"%s\").\n"
656 % (file, keys
[i
], props
.get(keys
[i
]), values
[i
],)
661 """A record of a cvs2svn conversion.
665 name -- a one-word name indicating the CVS repository to be converted.
667 stdout -- a list of lines written by cvs2svn to stdout."""
669 def __init__(self
, name
, error_re
, args
, options_file
=None):
671 if not os
.path
.isdir(tmp_dir
):
674 cvsrepos
= os
.path
.join(test_data_dir
, '%s-cvsrepos' % self
.name
)
678 self
.options_file
= os
.path
.join(cvsrepos
, options_file
)
680 '--options=%s' % self
.options_file
,
683 self
.options_file
= None
685 self
.stdout
= run_script(cvs2git
, error_re
, *args
)
688 # Cache of conversions that have already been done. Keys are conv_id;
689 # values are Conversion instances.
690 already_converted
= { }
692 def ensure_conversion(
693 name
, error_re
=None, passbypass
=None,
694 trunk
=None, branches
=None, tags
=None,
695 args
=None, options_file
=None, symbol_hints_file
=None, dumpfile
=None,
697 """Convert CVS repository NAME to Subversion, but only if it has not
698 been converted before by this invocation of this script. If it has
699 been converted before, return the Conversion object from the
702 If no error, return a Conversion instance.
704 If ERROR_RE is a string, it is a regular expression expected to
705 match some line of stderr printed by the conversion. If there is an
706 error and ERROR_RE is not set, then raise Failure.
708 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
709 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
711 NAME is just one word. For example, 'main' would mean to convert
712 './test-data/main-cvsrepos', and after the conversion, the resulting
713 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
714 a checked out head working copy in './cvs2svn-tmp/main-wc'.
716 Any other options to pass to cvs2svn should be in ARGS, each element
717 being one option, e.g., '--trunk-only'. If the option takes an
718 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
719 are passed to cvs2svn in the order that they appear in ARGS.
721 If OPTIONS_FILE is specified, then it should be the name of a file
722 within the main directory of the cvs repository associated with this
723 test. It is passed to cvs2svn using the --options option (which
724 suppresses some other options that are incompatible with --options).
726 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
727 file within the main directory of the cvs repository associated with
728 this test. It is passed to cvs2svn using the --symbol-hints option.
730 If DUMPFILE is specified, then it is the name of a dumpfile within
731 the temporary directory to which the conversion output should be
742 args
.append('--trunk=%s' % (trunk
,))
745 branches
= 'branches'
747 args
.append('--branches=%s' % (branches
,))
752 args
.append('--tags=%s' % (tags
,))
754 conv_id
= make_conversion_id(
755 name
, args
, passbypass
, options_file
, symbol_hints_file
758 if conv_id
not in already_converted
:
760 # Run the conversion and store the result for the rest of this
762 already_converted
[conv_id
] = Conversion(
763 conv_id
, name
, error_re
, passbypass
,
764 {'trunk' : trunk
, 'branches' : branches
, 'tags' : tags
},
765 args
, options_file
, symbol_hints_file
, dumpfile
,
768 # Remember the failure so that a future attempt to run this conversion
769 # does not bother to retry, but fails immediately.
770 already_converted
[conv_id
] = None
773 conv
= already_converted
[conv_id
]
779 class Cvs2SvnTestFunction(TestCase
):
780 """A TestCase based on a naked Python function object.
782 FUNC should be a function that returns None on success and throws an
783 svntest.Failure exception on failure. It should have a brief
784 docstring describing what it does (and fulfilling certain
785 conditions). FUNC must take no arguments.
787 This class is almost identical to svntest.testcase.FunctionTestCase,
788 except that the test function does not require a sandbox and does
789 not accept any parameter (not even sandbox=None).
791 This class can be used as an annotation on a Python function.
795 def __init__(self
, func
):
796 # it better be a function that accepts no parameters and has a
798 assert isinstance(func
, types
.FunctionType
)
800 name
= func
.func_name
802 assert func
.func_code
.co_argcount
== 0, \
803 '%s must not take any arguments' % name
805 doc
= func
.__doc
__.strip()
806 assert doc
, '%s must have a docstring' % name
808 # enforce stylistic guidelines for the function docstrings:
809 # - no longer than 50 characters
810 # - should not end in a period
811 # - should not be capitalized
812 assert len(doc
) <= 50, \
813 "%s's docstring must be 50 characters or less" % name
814 assert doc
[-1] != '.', \
815 "%s's docstring should not end in a period" % name
816 assert doc
[0].lower() == doc
[0], \
817 "%s's docstring should not be capitalized" % name
819 TestCase
.__init
__(self
, doc
=doc
)
822 def get_function_name(self
):
823 return self
.func
.func_name
825 def get_sandbox_name(self
):
828 def run(self
, sandbox
):
832 class Cvs2HgTestFunction(Cvs2SvnTestFunction
):
833 """Same as Cvs2SvnTestFunction, but for test cases that should be
834 skipped if Mercurial is not available.
836 def run(self
, sandbox
):
843 class Cvs2SvnTestCase(TestCase
):
845 self
, name
, doc
=None, variant
=None,
846 error_re
=None, passbypass
=None,
847 trunk
=None, branches
=None, tags
=None,
849 options_file
=None, symbol_hints_file
=None, dumpfile
=None,
854 # By default, use the first line of the class docstring as the
856 doc
= self
.__doc
__.splitlines()[0]
858 if variant
is not None:
859 # Modify doc to show the variant. Trim doc first if necessary
860 # to stay within the 50-character limit.
861 suffix
= '...variant %s' % (variant
,)
862 doc
= doc
[:50 - len(suffix
)] + suffix
864 TestCase
.__init
__(self
, doc
=doc
)
866 self
.error_re
= error_re
867 self
.passbypass
= passbypass
869 self
.branches
= branches
872 self
.options_file
= options_file
873 self
.symbol_hints_file
= symbol_hints_file
874 self
.dumpfile
= dumpfile
876 def ensure_conversion(self
):
877 return ensure_conversion(
879 error_re
=self
.error_re
, passbypass
=self
.passbypass
,
880 trunk
=self
.trunk
, branches
=self
.branches
, tags
=self
.tags
,
882 options_file
=self
.options_file
,
883 symbol_hints_file
=self
.symbol_hints_file
,
884 dumpfile
=self
.dumpfile
,
887 def get_sandbox_name(self
):
891 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase
):
892 """Test properties resulting from a conversion."""
894 def __init__(self
, name
, props_to_test
, expected_props
, **kw
):
895 """Initialize an instance of Cvs2SvnPropertiesTestCase.
897 NAME is the name of the test, passed to Cvs2SvnTestCase.
898 PROPS_TO_TEST is a list of the names of svn properties that should
899 be tested. EXPECTED_PROPS is a list of tuples [(filename,
900 [value,...])], where the second item in each tuple is a list of
901 values expected for the properties listed in PROPS_TO_TEST for the
902 specified filename. If a property must *not* be set, then its
903 value should be listed as None."""
905 Cvs2SvnTestCase
.__init
__(self
, name
, **kw
)
906 self
.props_to_test
= props_to_test
907 self
.expected_props
= expected_props
910 conv
= self
.ensure_conversion()
911 conv
.check_props(self
.props_to_test
, self
.expected_props
)
914 #----------------------------------------------------------------------
916 #----------------------------------------------------------------------
921 "cvs2svn with no arguments shows usage"
922 out
= run_script(cvs2svn
, None)
923 if (len(out
) > 2 and out
[0].find('ERROR:') == 0
924 and out
[1].find('DBM module')):
925 print 'cvs2svn cannot execute due to lack of proper DBM module.'
926 print 'Exiting without running any further tests.'
928 if out
[0].find('Usage:') < 0:
929 raise Failure('Basic cvs2svn invocation failed.')
933 def cvs2svn_manpage():
934 "generate a manpage for cvs2svn"
935 out
= run_script(cvs2svn
, None, '--man')
939 def cvs2git_manpage():
940 "generate a manpage for cvs2git"
941 out
= run_script(cvs2git
, None, '--man')
945 def cvs2hg_manpage():
946 "generate a manpage for cvs2hg"
947 out
= run_script(cvs2hg
, None, '--man')
951 def show_help_passes():
952 "cvs2svn --help-passes shows pass information"
953 out
= run_script(cvs2svn
, None, '--help-passes')
954 if out
[0].find('PASSES') < 0:
955 raise Failure('cvs2svn --help-passes failed.')
960 "detection of the executable flag"
961 if sys
.platform
== 'win32':
963 conv
= ensure_conversion('main')
964 st
= os
.stat(conv
.get_wc('trunk', 'single-files', 'attr-exec'))
965 if not st
.st_mode
& stat
.S_IXUSR
:
971 "conversion of filename with a space"
972 conv
= ensure_conversion('main')
973 if not conv
.path_exists('trunk', 'single-files', 'space fname'):
979 "two commits in quick succession"
980 conv
= ensure_conversion('main')
982 os
.path
.join(conv
.repos
, 'trunk', 'single-files', 'twoquick'), {})
987 class PruneWithCare(Cvs2SvnTestCase
):
988 "prune, but never too much"
990 def __init__(self
, **kw
):
991 Cvs2SvnTestCase
.__init
__(self
, 'main', **kw
)
994 # Robert Pluim encountered this lovely one while converting the
995 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
996 # repository (see issue #1302). Step 4 is the doozy:
998 # revision 1: adds trunk/blah/, adds trunk/blah/first
999 # revision 2: adds trunk/blah/second
1000 # revision 3: deletes trunk/blah/first
1001 # revision 4: deletes blah [re-deleting trunk/blah/first pruned blah!]
1002 # revision 5: does nothing
1004 # After fixing cvs2svn, the sequence (correctly) looks like this:
1006 # revision 1: adds trunk/blah/, adds trunk/blah/first
1007 # revision 2: adds trunk/blah/second
1008 # revision 3: deletes trunk/blah/first
1009 # revision 4: does nothing [because trunk/blah/first already deleted]
1010 # revision 5: deletes blah
1012 # The difference is in 4 and 5. In revision 4, it's not correct
1013 # to prune blah/, because second is still in there, so revision 4
1014 # does nothing now. But when we delete second in 5, that should
1015 # bubble up and prune blah/ instead.
1017 # ### Note that empty revisions like 4 are probably going to become
1018 # ### at least optional, if not banished entirely from cvs2svn's
1019 # ### output. Hmmm, or they may stick around, with an extra
1020 # ### revision property explaining what happened. Need to think
1021 # ### about that. In some sense, it's a bug in Subversion itself,
1022 # ### that such revisions don't show up in 'svn log' output.
1024 conv
= self
.ensure_conversion()
1026 # Confirm that revision 4 removes '/trunk/full-prune/first',
1027 # and that revision 6 removes '/trunk/full-prune'.
1029 # Also confirm similar things about '/full-prune-reappear/...',
1030 # which is similar, except that later on it reappears, restored
1031 # from pruneland, because a file gets added to it.
1033 # And finally, a similar thing for '/partial-prune/...', except that
1034 # in its case, a permanent file on the top level prevents the
1035 # pruning from going farther than the subdirectory containing first
1038 for path
in ('full-prune/first',
1039 'full-prune-reappear/sub/first',
1040 'partial-prune/sub/first'):
1041 conv
.logs
[5].check_change('/%(trunk)s/' + path
, 'D')
1043 for path
in ('full-prune',
1044 'full-prune-reappear',
1045 'partial-prune/sub'):
1046 conv
.logs
[7].check_change('/%(trunk)s/' + path
, 'D')
1048 for path
in ('full-prune-reappear',
1049 'full-prune-reappear/appears-later'):
1050 conv
.logs
[33].check_change('/%(trunk)s/' + path
, 'A')
1053 @Cvs2SvnTestFunction
1054 def interleaved_commits():
1055 "two interleaved trunk commits, different log msgs"
1056 # See test-data/main-cvsrepos/proj/README.
1057 conv
= ensure_conversion('main')
1059 # The initial import.
1061 conv
.logs
[rev
].check('Initial import.', (
1062 ('/%(trunk)s/interleaved', 'A'),
1063 ('/%(trunk)s/interleaved/1', 'A'),
1064 ('/%(trunk)s/interleaved/2', 'A'),
1065 ('/%(trunk)s/interleaved/3', 'A'),
1066 ('/%(trunk)s/interleaved/4', 'A'),
1067 ('/%(trunk)s/interleaved/5', 'A'),
1068 ('/%(trunk)s/interleaved/a', 'A'),
1069 ('/%(trunk)s/interleaved/b', 'A'),
1070 ('/%(trunk)s/interleaved/c', 'A'),
1071 ('/%(trunk)s/interleaved/d', 'A'),
1072 ('/%(trunk)s/interleaved/e', 'A'),
1075 def check_letters(rev
):
1076 """Check if REV is the rev where only letters were committed."""
1078 conv
.logs
[rev
].check('Committing letters only.', (
1079 ('/%(trunk)s/interleaved/a', 'M'),
1080 ('/%(trunk)s/interleaved/b', 'M'),
1081 ('/%(trunk)s/interleaved/c', 'M'),
1082 ('/%(trunk)s/interleaved/d', 'M'),
1083 ('/%(trunk)s/interleaved/e', 'M'),
1086 def check_numbers(rev
):
1087 """Check if REV is the rev where only numbers were committed."""
1089 conv
.logs
[rev
].check('Committing numbers only.', (
1090 ('/%(trunk)s/interleaved/1', 'M'),
1091 ('/%(trunk)s/interleaved/2', 'M'),
1092 ('/%(trunk)s/interleaved/3', 'M'),
1093 ('/%(trunk)s/interleaved/4', 'M'),
1094 ('/%(trunk)s/interleaved/5', 'M'),
1097 # One of the commits was letters only, the other was numbers only.
1098 # But they happened "simultaneously", so we don't assume anything
1099 # about which commit appeared first, so we just try both ways.
1103 check_numbers(rev
+ 1)
1106 check_letters(rev
+ 1)
1109 @Cvs2SvnTestFunction
1110 def simple_commits():
1111 "simple trunk commits"
1112 # See test-data/main-cvsrepos/proj/README.
1113 conv
= ensure_conversion('main')
1115 # The initial import.
1116 conv
.logs
[13].check('Initial import.', (
1117 ('/%(trunk)s/proj', 'A'),
1118 ('/%(trunk)s/proj/default', 'A'),
1119 ('/%(trunk)s/proj/sub1', 'A'),
1120 ('/%(trunk)s/proj/sub1/default', 'A'),
1121 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1122 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1123 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1124 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1125 ('/%(trunk)s/proj/sub2', 'A'),
1126 ('/%(trunk)s/proj/sub2/default', 'A'),
1127 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1128 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1129 ('/%(trunk)s/proj/sub3', 'A'),
1130 ('/%(trunk)s/proj/sub3/default', 'A'),
1134 conv
.logs
[18].check('First commit to proj, affecting two files.', (
1135 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1136 ('/%(trunk)s/proj/sub3/default', 'M'),
1139 # The second commit.
1140 conv
.logs
[19].check('Second commit to proj, affecting all 7 files.', (
1141 ('/%(trunk)s/proj/default', 'M'),
1142 ('/%(trunk)s/proj/sub1/default', 'M'),
1143 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1144 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1145 ('/%(trunk)s/proj/sub2/default', 'M'),
1146 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1147 ('/%(trunk)s/proj/sub3/default', 'M')
1151 class SimpleTags(Cvs2SvnTestCase
):
1152 "simple tags and branches, no commits"
1154 def __init__(self
, **kw
):
1155 # See test-data/main-cvsrepos/proj/README.
1156 Cvs2SvnTestCase
.__init
__(self
, 'main', **kw
)
1158 def run(self
, sbox
):
1159 conv
= self
.ensure_conversion()
1161 # Verify the copy source for the tags we are about to check
1162 # No need to verify the copyfrom revision, as simple_commits did that
1163 conv
.logs
[13].check('Initial import.', (
1164 ('/%(trunk)s/proj', 'A'),
1165 ('/%(trunk)s/proj/default', 'A'),
1166 ('/%(trunk)s/proj/sub1', 'A'),
1167 ('/%(trunk)s/proj/sub1/default', 'A'),
1168 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1169 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1170 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1171 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1172 ('/%(trunk)s/proj/sub2', 'A'),
1173 ('/%(trunk)s/proj/sub2/default', 'A'),
1174 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1175 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1176 ('/%(trunk)s/proj/sub3', 'A'),
1177 ('/%(trunk)s/proj/sub3/default', 'A'),
1180 fromstr
= ' (from /%(branches)s/B_FROM_INITIALS:14)'
1182 # Tag on rev 1.1.1.1 of all files in proj
1183 conv
.logs
[14].check(sym_log_msg('B_FROM_INITIALS'), (
1184 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1185 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1186 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1189 # The same, as a tag
1190 log
= conv
.find_tag_log('T_ALL_INITIAL_FILES')
1191 log
.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1192 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr
, 'A'),
1195 # Tag on rev 1.1.1.1 of all files in proj, except one
1196 log
= conv
.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1197 log
.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1198 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr
, 'A'),
1199 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1202 # The same, as a branch
1203 conv
.logs
[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1204 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE (from /%(trunk)s:13)', 'A'),
1205 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1206 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
1207 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
1211 @Cvs2SvnTestFunction
1212 def simple_branch_commits():
1213 "simple branch commits"
1214 # See test-data/main-cvsrepos/proj/README.
1215 conv
= ensure_conversion('main')
1217 conv
.logs
[23].check('Modify three files, on branch B_MIXED.', (
1218 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1219 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1220 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1224 @Cvs2SvnTestFunction
1225 def mixed_time_tag():
1227 # See test-data/main-cvsrepos/proj/README.
1228 conv
= ensure_conversion('main')
1230 log
= conv
.find_tag_log('T_MIXED')
1232 ('/%(tags)s/T_MIXED (from /%(branches)s/B_MIXED:20)', 'A'),
1236 @Cvs2SvnTestFunction
1237 def mixed_time_branch_with_added_file():
1238 "mixed-time branch, and a file added to the branch"
1239 # See test-data/main-cvsrepos/proj/README.
1240 conv
= ensure_conversion('main')
1242 # A branch from the same place as T_MIXED in the previous test,
1243 # plus a file added directly to the branch
1244 conv
.logs
[20].check(sym_log_msg('B_MIXED'), (
1245 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1246 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1247 ('/%(branches)s/B_MIXED/single-files', 'D'),
1248 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1249 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1250 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1253 conv
.logs
[22].check('Add a file on branch B_MIXED.', (
1254 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1258 @Cvs2SvnTestFunction
1260 "a commit affecting both trunk and a branch"
1261 # See test-data/main-cvsrepos/proj/README.
1262 conv
= ensure_conversion('main')
1264 conv
.logs
[24].check(
1265 'A single commit affecting one file on branch B_MIXED '
1266 'and one on trunk.', (
1267 ('/%(trunk)s/proj/sub2/default', 'M'),
1268 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1272 @Cvs2SvnTestFunction
1273 def split_time_branch():
1274 "branch some trunk files, and later branch the rest"
1275 # See test-data/main-cvsrepos/proj/README.
1276 conv
= ensure_conversion('main')
1278 # First change on the branch, creating it
1279 conv
.logs
[25].check(sym_log_msg('B_SPLIT'), (
1280 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1281 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1282 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1283 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1286 conv
.logs
[29].check('First change on branch B_SPLIT.', (
1287 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1288 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1289 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1290 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1291 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1294 # A trunk commit for the file which was not branched
1295 conv
.logs
[30].check('A trunk change to sub1/subsubB/default. '
1296 'This was committed about an', (
1297 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1300 # Add the file not already branched to the branch, with modification:w
1301 conv
.logs
[31].check(sym_log_msg('B_SPLIT'), (
1302 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1303 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1306 conv
.logs
[32].check('This change affects sub3/default and '
1307 'sub1/subsubB/default, on branch', (
1308 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1309 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1313 @Cvs2SvnTestFunction
1314 def multiple_tags():
1315 "multiple tags referring to same revision"
1316 conv
= ensure_conversion('main')
1317 if not conv
.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1319 if not conv
.path_exists(
1320 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1324 @Cvs2SvnTestFunction
1325 def multiply_defined_symbols():
1326 "multiple definitions of symbol names"
1328 # We can only check one line of the error output at a time, so test
1329 # twice. (The conversion only have to be done once because the
1330 # results are cached.)
1331 conv
= ensure_conversion(
1332 'multiply-defined-symbols',
1334 r
"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1338 conv
= ensure_conversion(
1339 'multiply-defined-symbols',
1341 r
"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1347 @Cvs2SvnTestFunction
1348 def multiply_defined_symbols_renamed():
1349 "rename multiply defined symbols"
1351 conv
= ensure_conversion(
1352 'multiply-defined-symbols',
1353 options_file
='cvs2svn-rename.options',
1357 @Cvs2SvnTestFunction
1358 def multiply_defined_symbols_ignored():
1359 "ignore multiply defined symbols"
1361 conv
= ensure_conversion(
1362 'multiply-defined-symbols',
1363 options_file
='cvs2svn-ignore.options',
1367 @Cvs2SvnTestFunction
1368 def repeatedly_defined_symbols():
1369 "multiple identical definitions of symbol names"
1371 # If a symbol is defined multiple times but has the same value each
1372 # time, that should not be an error.
1374 conv
= ensure_conversion('repeatedly-defined-symbols')
1377 @Cvs2SvnTestFunction
1379 "conversion of invalid symbolic names"
1380 conv
= ensure_conversion('bogus-tag')
1383 @Cvs2SvnTestFunction
1384 def overlapping_branch():
1385 "ignore a file with a branch with two names"
1386 conv
= ensure_conversion('overlapping-branch')
1388 if not conv
.output_found('.*cannot also have name \'vendorB\''):
1391 conv
.logs
[2].check('imported', (
1392 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1393 ('/%(trunk)s/overlapping-branch', 'A'),
1396 if len(conv
.logs
) != 2:
1400 class PhoenixBranch(Cvs2SvnTestCase
):
1401 "convert a branch file rooted in a 'dead' revision"
1403 def __init__(self
, **kw
):
1404 Cvs2SvnTestCase
.__init
__(self
, 'phoenix', **kw
)
1406 def run(self
, sbox
):
1407 conv
= self
.ensure_conversion()
1408 conv
.logs
[8].check('This file was supplied by Jack Moffitt', (
1409 ('/%(branches)s/volsung_20010721', 'A'),
1410 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1412 conv
.logs
[9].check('This file was supplied by Jack Moffitt', (
1413 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1417 ###TODO: We check for 4 changed paths here to accomodate creating tags
1418 ###and branches in rev 1, but that will change, so this will
1419 ###eventually change back.
1420 @Cvs2SvnTestFunction
1421 def ctrl_char_in_log():
1422 "handle a control char in a log message"
1423 # This was issue #1106.
1425 conv
= ensure_conversion('ctrl-char-in-log')
1426 conv
.logs
[rev
].check_changes((
1427 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1429 if conv
.logs
[rev
].msg
.find('\x04') < 0:
1431 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1434 @Cvs2SvnTestFunction
1436 "handle tags rooted in a redeleted revision"
1437 conv
= ensure_conversion('overdead')
1440 class NoTrunkPrune(Cvs2SvnTestCase
):
1441 "ensure that trunk doesn't get pruned"
1443 def __init__(self
, **kw
):
1444 Cvs2SvnTestCase
.__init
__(self
, 'overdead', **kw
)
1446 def run(self
, sbox
):
1447 conv
= self
.ensure_conversion()
1448 for rev
in conv
.logs
.keys():
1449 rev_logs
= conv
.logs
[rev
]
1450 if rev_logs
.get_path_op('/%(trunk)s') == 'D':
1454 @Cvs2SvnTestFunction
1455 def double_delete():
1456 "file deleted twice, in the root of the repository"
1457 # This really tests several things: how we handle a file that's
1458 # removed (state 'dead') in two successive revisions; how we
1459 # handle a file in the root of the repository (there were some
1460 # bugs in cvs2svn's svn path construction for top-level files); and
1461 # the --no-prune option.
1462 conv
= ensure_conversion(
1463 'double-delete', args
=['--trunk-only', '--no-prune'])
1465 path
= '/%(trunk)s/twice-removed'
1467 conv
.logs
[rev
].check('Updated CVS', (
1470 conv
.logs
[rev
+ 1].check('Remove this file for the first time.', (
1473 conv
.logs
[rev
+ 2].check('Remove this file for the second time,', (
1477 @Cvs2SvnTestFunction
1479 "branch created from both trunk and another branch"
1480 # See test-data/split-branch-cvsrepos/README.
1482 # The conversion will fail if the bug is present, and
1483 # ensure_conversion will raise Failure.
1484 conv
= ensure_conversion('split-branch')
1487 @Cvs2SvnTestFunction
1488 def resync_misgroups():
1489 "resyncing should not misorder commit groups"
1490 # See test-data/resync-misgroups-cvsrepos/README.
1492 # The conversion will fail if the bug is present, and
1493 # ensure_conversion will raise Failure.
1494 conv
= ensure_conversion('resync-misgroups')
1497 class TaggedBranchAndTrunk(Cvs2SvnTestCase
):
1498 "allow tags with mixed trunk and branch sources"
1500 def __init__(self
, **kw
):
1501 Cvs2SvnTestCase
.__init
__(self
, 'tagged-branch-n-trunk', **kw
)
1503 def run(self
, sbox
):
1504 conv
= self
.ensure_conversion()
1506 tags
= conv
.symbols
.get('tags', 'tags')
1508 a_path
= conv
.get_wc(tags
, 'some-tag', 'a.txt')
1509 b_path
= conv
.get_wc(tags
, 'some-tag', 'b.txt')
1510 if not (os
.path
.exists(a_path
) and os
.path
.exists(b_path
)):
1512 if (open(a_path
, 'r').read().find('1.24') == -1) \
1513 or (open(b_path
, 'r').read().find('1.5') == -1):
1517 @Cvs2SvnTestFunction
1519 "never use the rev-in-progress as a copy source"
1521 # See issue #1427 and r8544.
1522 conv
= ensure_conversion('enroot-race')
1524 conv
.logs
[rev
].check_changes((
1525 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1526 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1527 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1529 conv
.logs
[rev
+ 1].check_changes((
1530 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1531 ('/%(trunk)s/proj/a.txt', 'M'),
1532 ('/%(trunk)s/proj/b.txt', 'M'),
1536 @Cvs2SvnTestFunction
1537 def enroot_race_obo():
1538 "do use the last completed rev as a copy source"
1539 conv
= ensure_conversion('enroot-race-obo')
1540 conv
.logs
[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1541 if not len(conv
.logs
) == 3:
1545 class BranchDeleteFirst(Cvs2SvnTestCase
):
1546 "correctly handle deletion as initial branch action"
1548 def __init__(self
, **kw
):
1549 Cvs2SvnTestCase
.__init
__(self
, 'branch-delete-first', **kw
)
1551 def run(self
, sbox
):
1552 # See test-data/branch-delete-first-cvsrepos/README.
1554 # The conversion will fail if the bug is present, and
1555 # ensure_conversion would raise Failure.
1556 conv
= self
.ensure_conversion()
1558 branches
= conv
.symbols
.get('branches', 'branches')
1560 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1561 if conv
.path_exists(branches
, 'branch-1', 'file'):
1563 if conv
.path_exists(branches
, 'branch-2', 'file'):
1565 if not conv
.path_exists(branches
, 'branch-3', 'file'):
1569 @Cvs2SvnTestFunction
1570 def nonascii_filenames():
1571 "non ascii files converted incorrectly"
1574 # on a en_US.iso-8859-1 machine this test fails with
1575 # svn: Can't recode ...
1577 # as described in the issue
1579 # on a en_US.UTF-8 machine this test fails with
1580 # svn: Malformed XML ...
1582 # which means at least it fails. Unfortunately it won't fail
1583 # with the same error...
1585 # mangle current locale settings so we know we're not running
1586 # a UTF-8 locale (which does not exhibit this problem)
1587 current_locale
= locale
.getlocale()
1588 new_locale
= 'en_US.ISO8859-1'
1589 locale_changed
= None
1591 # From http://docs.python.org/lib/module-sys.html
1593 # getfilesystemencoding():
1595 # Return the name of the encoding used to convert Unicode filenames
1596 # into system file names, or None if the system default encoding is
1597 # used. The result value depends on the operating system:
1599 # - On Windows 9x, the encoding is ``mbcs''.
1600 # - On Mac OS X, the encoding is ``utf-8''.
1601 # - On Unix, the encoding is the user's preference according to the
1602 # result of nl_langinfo(CODESET), or None if the
1603 # nl_langinfo(CODESET) failed.
1604 # - On Windows NT+, file names are Unicode natively, so no conversion is
1607 # So we're going to skip this test on Mac OS X for now.
1608 if sys
.platform
== "darwin":
1609 raise svntest
.Skip()
1612 # change locale to non-UTF-8 locale to generate latin1 names
1613 locale
.setlocale(locale
.LC_ALL
, # this might be too broad?
1616 except locale
.Error
:
1617 raise svntest
.Skip()
1620 srcrepos_path
= os
.path
.join(test_data_dir
,'main-cvsrepos')
1621 dstrepos_path
= os
.path
.join(test_data_dir
,'non-ascii-cvsrepos')
1622 if not os
.path
.exists(dstrepos_path
):
1623 # create repos from existing main repos
1624 shutil
.copytree(srcrepos_path
, dstrepos_path
)
1625 base_path
= os
.path
.join(dstrepos_path
, 'single-files')
1626 shutil
.copyfile(os
.path
.join(base_path
, 'twoquick,v'),
1627 os
.path
.join(base_path
, 'two\366uick,v'))
1628 new_path
= os
.path
.join(dstrepos_path
, 'single\366files')
1629 os
.rename(base_path
, new_path
)
1631 conv
= ensure_conversion('non-ascii', args
=['--encoding=latin1'])
1634 locale
.setlocale(locale
.LC_ALL
, current_locale
)
1635 safe_rmtree(dstrepos_path
)
1638 class UnicodeTest(Cvs2SvnTestCase
):
1639 "metadata contains Unicode"
1641 warning_pattern
= r
'ERROR\: There were warnings converting .* messages'
1643 def __init__(self
, name
, warning_expected
, **kw
):
1644 if warning_expected
:
1645 error_re
= self
.warning_pattern
1649 Cvs2SvnTestCase
.__init
__(self
, name
, error_re
=error_re
, **kw
)
1650 self
.warning_expected
= warning_expected
1652 def run(self
, sbox
):
1654 # ensure the availability of the "utf_8" encoding:
1655 u
'a'.encode('utf_8').decode('utf_8')
1657 raise svntest
.Skip()
1659 self
.ensure_conversion()
1662 class UnicodeAuthor(UnicodeTest
):
1663 "author name contains Unicode"
1665 def __init__(self
, warning_expected
, **kw
):
1666 UnicodeTest
.__init
__(self
, 'unicode-author', warning_expected
, **kw
)
1669 class UnicodeLog(UnicodeTest
):
1670 "log message contains Unicode"
1672 def __init__(self
, warning_expected
, **kw
):
1673 UnicodeTest
.__init
__(self
, 'unicode-log', warning_expected
, **kw
)
1676 @Cvs2SvnTestFunction
1677 def vendor_branch_sameness():
1678 "avoid spurious changes for initial revs"
1679 conv
= ensure_conversion(
1680 'vendor-branch-sameness', args
=['--keep-trivial-imports']
1683 # The following files are in this repository:
1685 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1686 # the same contents, the file's default branch is 1.1.1,
1687 # and both revisions are in state 'Exp'.
1689 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1690 # 1.1 (the addition of a line of text).
1692 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1694 # d.txt: This file was created by 'cvs add' instead of import, so
1695 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1696 # The timestamp on the add is exactly the same as for the
1697 # imports of the other files.
1699 # e.txt: Like a.txt, except that the log message for revision 1.1
1700 # is not the standard import log message.
1702 # (Aside from e.txt, the log messages for the same revisions are the
1703 # same in all files.)
1705 # We expect that only a.txt is recognized as an import whose 1.1
1706 # revision can be omitted. The other files should be added on trunk
1707 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1708 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1709 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1710 # and c.txt should be 'D'eleted.
1713 conv
.logs
[rev
].check('Initial revision', (
1714 ('/%(trunk)s/proj', 'A'),
1715 ('/%(trunk)s/proj/b.txt', 'A'),
1716 ('/%(trunk)s/proj/c.txt', 'A'),
1717 ('/%(trunk)s/proj/d.txt', 'A'),
1720 conv
.logs
[rev
+ 1].check(sym_log_msg('vbranchA'), (
1721 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1722 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1725 conv
.logs
[rev
+ 2].check('First vendor branch revision.', (
1726 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1727 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1728 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1731 conv
.logs
[rev
+ 3].check('This commit was generated by cvs2svn '
1732 'to compensate for changes in r4,', (
1733 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1734 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1735 ('/%(trunk)s/proj/c.txt', 'D'),
1739 conv
.logs
[rev
].check('This log message is not the standard', (
1740 ('/%(trunk)s/proj/e.txt', 'A'),
1743 conv
.logs
[rev
+ 2].check('First vendor branch revision', (
1744 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1747 conv
.logs
[rev
+ 3].check('This commit was generated by cvs2svn '
1748 'to compensate for changes in r9,', (
1749 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1753 @Cvs2SvnTestFunction
1754 def vendor_branch_trunk_only():
1755 "handle vendor branches with --trunk-only"
1756 conv
= ensure_conversion('vendor-branch-sameness', args
=['--trunk-only'])
1759 conv
.logs
[rev
].check('Initial revision', (
1760 ('/%(trunk)s/proj', 'A'),
1761 ('/%(trunk)s/proj/b.txt', 'A'),
1762 ('/%(trunk)s/proj/c.txt', 'A'),
1763 ('/%(trunk)s/proj/d.txt', 'A'),
1766 conv
.logs
[rev
+ 1].check('First vendor branch revision', (
1767 ('/%(trunk)s/proj/a.txt', 'A'),
1768 ('/%(trunk)s/proj/b.txt', 'M'),
1769 ('/%(trunk)s/proj/c.txt', 'D'),
1772 conv
.logs
[rev
+ 2].check('This log message is not the standard', (
1773 ('/%(trunk)s/proj/e.txt', 'A'),
1776 conv
.logs
[rev
+ 3].check('First vendor branch revision', (
1777 ('/%(trunk)s/proj/e.txt', 'M'),
1781 @Cvs2SvnTestFunction
1782 def default_branches():
1783 "handle default branches correctly"
1784 conv
= ensure_conversion('default-branches')
1786 # There are seven files in the repository:
1789 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1790 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1791 # committed (thus losing the default branch "1.1.1"), then
1792 # 1.1.1.4 was imported. All vendor import release tags are
1796 # Like a.txt, but without rev 1.2.
1799 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1802 # Same as the previous two, but 1.1.1 branch is unlabeled.
1805 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1807 # deleted-on-vendor-branch.txt,v:
1808 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1810 # added-then-imported.txt,v:
1811 # Added with 'cvs add' to create 1.1, then imported with
1812 # completely different contents to create 1.1.1.1, therefore
1813 # never had a default branch.
1816 conv
.logs
[2].check("Import (vbranchA, vtag-1).", (
1817 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1818 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1819 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1820 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1821 ('/%(branches)s/vbranchA', 'A'),
1822 ('/%(branches)s/vbranchA/proj', 'A'),
1823 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1824 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1825 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1826 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1829 conv
.logs
[3].check("This commit was generated by cvs2svn "
1830 "to compensate for changes in r2,", (
1831 ('/%(trunk)s/proj', 'A'),
1832 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1833 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1834 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1835 ('/%(trunk)s/proj/d.txt '
1836 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1837 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1838 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1839 ('/%(trunk)s/proj/e.txt '
1840 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1843 conv
.logs
[4].check(sym_log_msg('vtag-1',1), (
1844 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1845 ('/%(tags)s/vtag-1/proj/d.txt '
1846 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1849 conv
.logs
[5].check("Import (vbranchA, vtag-2).", (
1850 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1851 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1852 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1853 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1854 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1855 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1858 conv
.logs
[6].check("This commit was generated by cvs2svn "
1859 "to compensate for changes in r5,", (
1860 ('/%(trunk)s/proj/a.txt '
1861 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1862 ('/%(trunk)s/proj/b.txt '
1863 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1864 ('/%(trunk)s/proj/c.txt '
1865 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1866 ('/%(trunk)s/proj/d.txt '
1867 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1868 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1869 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1871 ('/%(trunk)s/proj/e.txt '
1872 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1875 conv
.logs
[7].check(sym_log_msg('vtag-2',1), (
1876 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1877 ('/%(tags)s/vtag-2/proj/d.txt '
1878 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1881 conv
.logs
[8].check("Import (vbranchA, vtag-3).", (
1882 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1883 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1884 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1885 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1886 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1887 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1890 conv
.logs
[9].check("This commit was generated by cvs2svn "
1891 "to compensate for changes in r8,", (
1892 ('/%(trunk)s/proj/a.txt '
1893 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1894 ('/%(trunk)s/proj/b.txt '
1895 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1896 ('/%(trunk)s/proj/c.txt '
1897 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1898 ('/%(trunk)s/proj/d.txt '
1899 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1900 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1901 ('/%(trunk)s/proj/e.txt '
1902 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1905 conv
.logs
[10].check(sym_log_msg('vtag-3',1), (
1906 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1907 ('/%(tags)s/vtag-3/proj/d.txt '
1908 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1909 ('/%(tags)s/vtag-3/proj/e.txt '
1910 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1913 conv
.logs
[11].check("First regular commit, to a.txt, on vtag-3.", (
1914 ('/%(trunk)s/proj/a.txt', 'M'),
1917 conv
.logs
[12].check("Add a file to the working copy.", (
1918 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1921 conv
.logs
[13].check(sym_log_msg('vbranchA'), (
1922 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1923 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1926 conv
.logs
[14].check("Import (vbranchA, vtag-4).", (
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/added-then-imported.txt', 'M'), # CHECK!!!
1931 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1932 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1933 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1936 conv
.logs
[15].check("This commit was generated by cvs2svn "
1937 "to compensate for changes in r14,", (
1938 ('/%(trunk)s/proj/b.txt '
1939 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1940 ('/%(trunk)s/proj/c.txt '
1941 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1942 ('/%(trunk)s/proj/d.txt '
1943 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1944 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1945 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1947 ('/%(trunk)s/proj/e.txt '
1948 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1951 conv
.logs
[16].check(sym_log_msg('vtag-4',1), (
1952 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1953 ('/%(tags)s/vtag-4/proj/d.txt '
1954 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
1958 @Cvs2SvnTestFunction
1959 def default_branches_trunk_only():
1960 "handle default branches with --trunk-only"
1962 conv
= ensure_conversion('default-branches', args
=['--trunk-only'])
1964 conv
.logs
[2].check("Import (vbranchA, vtag-1).", (
1965 ('/%(trunk)s/proj', 'A'),
1966 ('/%(trunk)s/proj/a.txt', 'A'),
1967 ('/%(trunk)s/proj/b.txt', 'A'),
1968 ('/%(trunk)s/proj/c.txt', 'A'),
1969 ('/%(trunk)s/proj/d.txt', 'A'),
1970 ('/%(trunk)s/proj/e.txt', 'A'),
1971 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1974 conv
.logs
[3].check("Import (vbranchA, vtag-2).", (
1975 ('/%(trunk)s/proj/a.txt', 'M'),
1976 ('/%(trunk)s/proj/b.txt', 'M'),
1977 ('/%(trunk)s/proj/c.txt', 'M'),
1978 ('/%(trunk)s/proj/d.txt', 'M'),
1979 ('/%(trunk)s/proj/e.txt', 'M'),
1980 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
1983 conv
.logs
[4].check("Import (vbranchA, vtag-3).", (
1984 ('/%(trunk)s/proj/a.txt', 'M'),
1985 ('/%(trunk)s/proj/b.txt', 'M'),
1986 ('/%(trunk)s/proj/c.txt', 'M'),
1987 ('/%(trunk)s/proj/d.txt', 'M'),
1988 ('/%(trunk)s/proj/e.txt', 'M'),
1989 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1992 conv
.logs
[5].check("First regular commit, to a.txt, on vtag-3.", (
1993 ('/%(trunk)s/proj/a.txt', 'M'),
1996 conv
.logs
[6].check("Add a file to the working copy.", (
1997 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
2000 conv
.logs
[7].check("Import (vbranchA, vtag-4).", (
2001 ('/%(trunk)s/proj/b.txt', 'M'),
2002 ('/%(trunk)s/proj/c.txt', 'M'),
2003 ('/%(trunk)s/proj/d.txt', 'M'),
2004 ('/%(trunk)s/proj/e.txt', 'M'),
2005 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2009 @Cvs2SvnTestFunction
2010 def default_branch_and_1_2():
2011 "do not allow 1.2 revision with default branch"
2013 conv
= ensure_conversion(
2014 'default-branch-and-1-2',
2016 r
'.*File \'.*\' has default branch
=1\
.1\
.1 but also a revision
1\
.2'
2021 @Cvs2SvnTestFunction
2022 def compose_tag_three_sources():
2023 "compose a tag from three sources"
2024 conv = ensure_conversion('compose
-tag
-three
-sources
')
2026 conv.logs[2].check("Add on trunk", (
2027 ('/%(trunk)s/tagged
-on
-trunk
-1.1', 'A
'),
2028 ('/%(trunk)s/tagged
-on
-trunk
-1.2-a
', 'A
'),
2029 ('/%(trunk)s/tagged
-on
-trunk
-1.2-b
', 'A
'),
2030 ('/%(trunk)s/tagged
-on
-b1
', 'A
'),
2031 ('/%(trunk)s/tagged
-on
-b2
', 'A
'),
2034 conv.logs[3].check(sym_log_msg('b1
'), (
2035 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A
'),
2038 conv.logs[4].check(sym_log_msg('b2
'), (
2039 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A
'),
2042 conv.logs[5].check("Commit on branch b1", (
2043 ('/%(branches)s/b1
/tagged
-on
-trunk
-1.1', 'M
'),
2044 ('/%(branches)s/b1
/tagged
-on
-trunk
-1.2-a
', 'M
'),
2045 ('/%(branches)s/b1
/tagged
-on
-trunk
-1.2-b
', 'M
'),
2046 ('/%(branches)s/b1
/tagged
-on
-b1
', 'M
'),
2047 ('/%(branches)s/b1
/tagged
-on
-b2
', 'M
'),
2050 conv.logs[6].check("Commit on branch b2", (
2051 ('/%(branches)s/b2
/tagged
-on
-trunk
-1.1', 'M
'),
2052 ('/%(branches)s/b2
/tagged
-on
-trunk
-1.2-a
', 'M
'),
2053 ('/%(branches)s/b2
/tagged
-on
-trunk
-1.2-b
', 'M
'),
2054 ('/%(branches)s/b2
/tagged
-on
-b1
', 'M
'),
2055 ('/%(branches)s/b2
/tagged
-on
-b2
', 'M
'),
2058 conv.logs[7].check("Commit again on trunk", (
2059 ('/%(trunk)s/tagged
-on
-trunk
-1.2-a
', 'M
'),
2060 ('/%(trunk)s/tagged
-on
-trunk
-1.2-b
', 'M
'),
2061 ('/%(trunk)s/tagged
-on
-trunk
-1.1', 'M
'),
2062 ('/%(trunk)s/tagged
-on
-b1
', 'M
'),
2063 ('/%(trunk)s/tagged
-on
-b2
', 'M
'),
2066 conv.logs[8].check(sym_log_msg('T
',1), (
2067 ('/%(tags)s/T (from /%(trunk)s:7)', 'A
'),
2068 ('/%(tags)s/T
/tagged
-on
-trunk
-1.1 '
2069 '(from /%(trunk)s/tagged
-on
-trunk
-1.1:2)', 'R
'),
2070 ('/%(tags)s/T
/tagged
-on
-b1 (from /%(branches)s/b1
/tagged
-on
-b1
:5)', 'R
'),
2071 ('/%(tags)s/T
/tagged
-on
-b2 (from /%(branches)s/b2
/tagged
-on
-b2
:6)', 'R
'),
2075 @Cvs2SvnTestFunction
2076 def pass5_when_to_fill():
2077 "reserve a svn revnum for a fill only when required"
2078 # The conversion will fail if the bug is present, and
2079 # ensure_conversion would raise Failure.
2080 conv = ensure_conversion('pass5
-when
-to
-fill
')
2083 class EmptyTrunk(Cvs2SvnTestCase):
2084 "don't
break when the trunk
is empty
"
2086 def __init__(self, **kw):
2087 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
2089 def run(self, sbox):
2090 # The conversion will fail if the bug is present, and
2091 # ensure_conversion would raise Failure.
2092 conv = self.ensure_conversion()
2095 @Cvs2SvnTestFunction
2096 def no_spurious_svn_commits():
2097 "ensure that we don
't create any spurious commits"
2098 conv = ensure_conversion('phoenix
')
2100 # Check spurious commit that could be created in
2101 # SVNCommitCreator._pre_commit()
2103 # (When you add a file on a branch, CVS creates a trunk revision
2104 # in state 'dead
'. If the log message of that commit is equal to
2105 # the one that CVS generates, we do not ever create a 'fill
'
2106 # SVNCommit for it.)
2108 # and spurious commit that could be created in
2109 # SVNCommitCreator._commit()
2111 # (When you add a file on a branch, CVS creates a trunk revision
2112 # in state 'dead
'. If the log message of that commit is equal to
2113 # the one that CVS generates, we do not create a primary SVNCommit
2115 conv.logs[17].check('File added on branch xiphophorus
', (
2116 ('/%(branches)s/xiphophorus
/added
-on
-branch
.txt
', 'A
'),
2119 # Check to make sure that a commit *is* generated:
2120 # (When you add a file on a branch, CVS creates a trunk revision
2121 # in state 'dead
'. If the log message of that commit is NOT equal
2122 # to the one that CVS generates, we create a primary SVNCommit to
2123 # serve as a home for the log message in question.
2124 conv.logs[18].check('file added
-on
-branch2
.txt was initially added on
'
2125 + 'branch xiphophorus
,\nand this log message was tweaked
', ())
2127 # Check spurious commit that could be created in
2128 # SVNCommitCreator._commit_symbols().
2129 conv.logs[19].check('This
file was also added on branch xiphophorus
,', (
2130 ('/%(branches)s/xiphophorus
/added
-on
-branch2
.txt
', 'A
'),
2134 class PeerPathPruning(Cvs2SvnTestCase):
2135 "make sure that filling prunes paths correctly"
2137 def __init__(self, **kw):
2138 Cvs2SvnTestCase.__init__(self, 'peer
-path
-pruning
', **kw)
2140 def run(self, sbox):
2141 conv = self.ensure_conversion()
2142 conv.logs[6].check(sym_log_msg('BRANCH
'), (
2143 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A
'),
2144 ('/%(branches)s/BRANCH
/bar
', 'D
'),
2145 ('/%(branches)s/BRANCH
/foo (from /%(trunk)s/foo
:5)', 'R
'),
2149 @Cvs2SvnTestFunction
2150 def invalid_closings_on_trunk():
2151 "verify correct revs are copied to default branches"
2152 # The conversion will fail if the bug is present, and
2153 # ensure_conversion would raise Failure.
2154 conv = ensure_conversion('invalid
-closings
-on
-trunk
')
2157 @Cvs2SvnTestFunction
2158 def individual_passes():
2159 "run each pass individually"
2160 conv = ensure_conversion('main
')
2161 conv2 = ensure_conversion('main
', passbypass=1)
2163 if conv.logs != conv2.logs:
2167 @Cvs2SvnTestFunction
2169 "reveal a big bug in our resync algorithm"
2170 # This will fail if the bug is present
2171 conv = ensure_conversion('resync
-bug
')
2174 @Cvs2SvnTestFunction
2175 def branch_from_default_branch():
2176 "reveal a bug in our default branch detection code"
2177 conv = ensure_conversion('branch
-from-default
-branch
')
2179 # This revision will be a default branch synchronization only
2180 # if cvs2svn is correctly determining default branch revisions.
2182 # The bug was that cvs2svn was treating revisions on branches off of
2183 # default branches as default branch revisions, resulting in
2184 # incorrectly regarding the branch off of the default branch as a
2185 # non-trunk default branch. Crystal clear? I thought so. See
2186 # issue #42 for more incoherent blathering.
2187 conv.logs[5].check("This commit was generated by cvs2svn", (
2188 ('/%(trunk)s/proj
/file.txt
'
2189 '(from /%(branches)s/upstream
/proj
/file.txt
:4)', 'R
'),
2193 @Cvs2SvnTestFunction
2194 def file_in_attic_too():
2195 "die if a file exists in and out of the attic"
2197 'file-in-attic
-too
',
2199 r'.*A CVS repository cannot contain both
'
2200 r'(.*)' + re.escape(os.sep) + r'(.*) '
2202 r'\
1' + re.escape(os.sep) + r'Attic
' + re.escape(os.sep) + r'\
2'
2207 @Cvs2SvnTestFunction
2208 def retain_file_in_attic_too():
2209 "test --retain-conflicting-attic-files option"
2210 conv = ensure_conversion(
2211 'file-in-attic
-too
', args=['--retain
-conflicting
-attic
-files
'])
2212 if not conv.path_exists('trunk
', 'file.txt
'):
2214 if not conv.path_exists('trunk
', 'Attic
', 'file.txt
'):
2218 @Cvs2SvnTestFunction
2219 def symbolic_name_filling_guide():
2220 "reveal a big bug in our SymbolFillingGuide"
2221 # This will fail if the bug is present
2222 conv = ensure_conversion('symbolic
-name
-overfill
')
2225 # Helpers for tests involving file contents and properties.
2227 class NodeTreeWalkException:
2228 "Exception class for node tree traversals."
2231 def node_for_path(node, path):
2232 "In the tree rooted under SVNTree NODE, return the node at PATH."
2233 if node.name != '__SVN_ROOT_NODE
':
2234 raise NodeTreeWalkException()
2235 path = path.strip('/')
2236 components = path.split('/')
2237 for component in components:
2238 node = svntest.tree.get_child(node, component)
2241 # Helper for tests involving properties.
2242 def props_for_path(node, path):
2243 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2244 return node_for_path(node, path).props
2247 class EOLMime(Cvs2SvnPropertiesTestCase):
2248 """eol settings and mime types together
2250 The files are as follows:
2252 trunk/foo.txt: no -kb, mime file says nothing.
2253 trunk/foo.xml: no -kb, mime file says text.
2254 trunk/foo.zip: no -kb, mime file says non-text.
2255 trunk/foo.bin: has -kb, mime file says nothing.
2256 trunk/foo.csv: has -kb, mime file says text.
2257 trunk/foo.dbf: has -kb, mime file says non-text.
2260 def __init__(self, args, **kw):
2261 # TODO: It's a bit klugey to construct this path here
. But so far
2262 # there's only one test with a mime.types file. If we have more,
2263 # we should abstract this into some helper, which would be located
2264 # near ensure_conversion(). Note that it is a convention of this
2265 # test suite for a mime.types file to be located in the top level
2266 # of the CVS repository to which it applies.
2267 self
.mime_path
= os
.path
.join(
2268 test_data_dir
, 'eol-mime-cvsrepos', 'mime.types')
2270 Cvs2SvnPropertiesTestCase
.__init
__(
2272 props_to_test
=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2273 args
=['--mime-types=%s' % self
.mime_path
] + args
,
2277 # We do four conversions. Each time, we pass --mime-types=FILE with
2278 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2279 # Thus there's one conversion with neither flag, one with just the
2280 # former, one with just the latter, and one with both.
2283 # Neither --no-default-eol nor --eol-from-mime-type:
2284 eol_mime1
= EOLMime(
2288 ('trunk/foo.txt', [None, None, None]),
2289 ('trunk/foo.xml', [None, 'text/xml', None]),
2290 ('trunk/foo.zip', [None, 'application/zip', None]),
2291 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2292 ('trunk/foo.csv', [None, 'text/csv', None]),
2293 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2297 # Just --no-default-eol, not --eol-from-mime-type:
2298 eol_mime2
= EOLMime(
2300 args
=['--default-eol=native'],
2302 ('trunk/foo.txt', ['native', None, KEYWORDS
]),
2303 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS
]),
2304 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS
]),
2305 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2306 ('trunk/foo.csv', [None, 'text/csv', None]),
2307 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2311 # Just --eol-from-mime-type, not --no-default-eol:
2312 eol_mime3
= EOLMime(
2314 args
=['--eol-from-mime-type'],
2316 ('trunk/foo.txt', [None, None, None]),
2317 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS
]),
2318 ('trunk/foo.zip', [None, 'application/zip', None]),
2319 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2320 ('trunk/foo.csv', [None, 'text/csv', None]),
2321 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2325 # Both --no-default-eol and --eol-from-mime-type:
2326 eol_mime4
= EOLMime(
2328 args
=['--eol-from-mime-type', '--default-eol=native'],
2330 ('trunk/foo.txt', ['native', None, KEYWORDS
]),
2331 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS
]),
2332 ('trunk/foo.zip', [None, 'application/zip', None]),
2333 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2334 ('trunk/foo.csv', [None, 'text/csv', None]),
2335 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2339 cvs_revnums_off
= Cvs2SvnPropertiesTestCase(
2341 doc
='test non-setting of cvs2svn:cvs-rev property',
2343 props_to_test
=['cvs2svn:cvs-rev'],
2345 ('trunk/foo.txt', [None]),
2346 ('trunk/foo.xml', [None]),
2347 ('trunk/foo.zip', [None]),
2348 ('trunk/foo.bin', [None]),
2349 ('trunk/foo.csv', [None]),
2350 ('trunk/foo.dbf', [None]),
2354 cvs_revnums_on
= Cvs2SvnPropertiesTestCase(
2356 doc
='test setting of cvs2svn:cvs-rev property',
2357 args
=['--cvs-revnums'],
2358 props_to_test
=['cvs2svn:cvs-rev'],
2360 ('trunk/foo.txt', ['1.2']),
2361 ('trunk/foo.xml', ['1.2']),
2362 ('trunk/foo.zip', ['1.2']),
2363 ('trunk/foo.bin', ['1.2']),
2364 ('trunk/foo.csv', ['1.2']),
2365 ('trunk/foo.dbf', ['1.2']),
2369 keywords
= Cvs2SvnPropertiesTestCase(
2371 doc
='test setting of svn:keywords property among others',
2372 args
=['--default-eol=native'],
2373 props_to_test
=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2375 ('trunk/foo.default', [KEYWORDS
, 'native', None]),
2376 ('trunk/foo.kkvl', [KEYWORDS
, 'native', None]),
2377 ('trunk/foo.kkv', [KEYWORDS
, 'native', None]),
2378 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2379 ('trunk/foo.kk', [None, 'native', None]),
2380 ('trunk/foo.ko', [None, 'native', None]),
2381 ('trunk/foo.kv', [None, 'native', None]),
2385 @Cvs2SvnTestFunction
2387 "test setting of svn:ignore property"
2388 conv
= ensure_conversion('cvsignore')
2389 wc_tree
= conv
.get_wc_tree()
2390 topdir_props
= props_for_path(wc_tree
, 'trunk/proj')
2391 subdir_props
= props_for_path(wc_tree
, '/trunk/proj/subdir')
2393 if topdir_props
['svn:ignore'] != \
2394 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2397 if subdir_props
['svn:ignore'] != \
2398 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2402 @Cvs2SvnTestFunction
2404 "test that CVS can still do what RCS can't"
2405 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2406 conv
= ensure_conversion(
2407 'requires-cvs', args
=['--use-cvs', '--default-eol=native'],
2410 atsign_contents
= file(conv
.get_wc("trunk", "atsign-add")).read()
2411 cl_contents
= file(conv
.get_wc("trunk", "client_lock.idl")).read()
2413 if atsign_contents
[-1:] == "@":
2415 if cl_contents
.find("gregh\n//\n//Integration for locks") < 0:
2418 if not (conv
.logs
[6].author
== "William Lyon Phelps III" and
2419 conv
.logs
[5].author
== "j random"):
2423 @Cvs2SvnTestFunction
2424 def questionable_branch_names():
2425 "test that we can handle weird branch names"
2426 conv
= ensure_conversion('questionable-symbols')
2427 # If the conversion succeeds, then we're okay. We could check the
2428 # actual branch paths, too, but the main thing is to know that the
2429 # conversion doesn't fail.
2432 @Cvs2SvnTestFunction
2433 def questionable_tag_names():
2434 "test that we can handle weird tag names"
2435 conv
= ensure_conversion('questionable-symbols')
2436 conv
.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2437 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2439 conv
.find_tag_log('TagWith/Backslash_E').check(
2440 sym_log_msg('TagWith/Backslash_E',1),
2442 ('/%(tags)s/TagWith', 'A'),
2443 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2446 conv
.find_tag_log('TagWith/Slash_Z').check(
2447 sym_log_msg('TagWith/Slash_Z',1),
2449 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2454 @Cvs2SvnTestFunction
2455 def revision_reorder_bug():
2456 "reveal a bug that reorders file revisions"
2457 conv
= ensure_conversion('revision-reorder-bug')
2458 # If the conversion succeeds, then we're okay. We could check the
2459 # actual revisions, too, but the main thing is to know that the
2460 # conversion doesn't fail.
2463 @Cvs2SvnTestFunction
2465 "test that exclude really excludes everything"
2466 conv
= ensure_conversion('main', args
=['--exclude=.*'])
2467 for log
in conv
.logs
.values():
2468 for item
in log
.changed_paths
.keys():
2469 if item
.startswith('/branches/') or item
.startswith('/tags/'):
2473 @Cvs2SvnTestFunction
2474 def vendor_branch_delete_add():
2475 "add trunk file that was deleted on vendor branch"
2476 # This will error if the bug is present
2477 conv
= ensure_conversion('vendor-branch-delete-add')
2480 @Cvs2SvnTestFunction
2481 def resync_pass2_pull_forward():
2482 "ensure pass2 doesn't pull rev too far forward"
2483 conv
= ensure_conversion('resync-pass2-pull-forward')
2484 # If the conversion succeeds, then we're okay. We could check the
2485 # actual revisions, too, but the main thing is to know that the
2486 # conversion doesn't fail.
2489 @Cvs2SvnTestFunction
2491 "only LFs for svn:eol-style=native files"
2492 conv
= ensure_conversion('native-eol', args
=['--default-eol=native'])
2493 lines
= run_program(svntest
.main
.svnadmin_binary
, None, 'dump', '-q',
2495 # Verify that all files in the dump have LF EOLs. We're actually
2496 # testing the whole dump file, but the dump file itself only uses
2497 # LF EOLs, so we're safe.
2499 if line
[-1] != '\n' or line
[:-1].find('\r') != -1:
2503 @Cvs2SvnTestFunction
2505 "reveal a bug that created a branch twice"
2506 conv
= ensure_conversion('double-fill')
2507 # If the conversion succeeds, then we're okay. We could check the
2508 # actual revisions, too, but the main thing is to know that the
2509 # conversion doesn't fail.
2512 @Cvs2SvnTestFunction
2514 "reveal a second bug that created a branch twice"
2515 conv
= ensure_conversion('double-fill2')
2516 conv
.logs
[6].check_msg(sym_log_msg('BRANCH1'))
2517 conv
.logs
[7].check_msg(sym_log_msg('BRANCH2'))
2519 # This check should fail:
2520 conv
.logs
[8].check_msg(sym_log_msg('BRANCH2'))
2524 raise Failure('Symbol filled twice in a row')
2527 @Cvs2SvnTestFunction
2528 def resync_pass2_push_backward():
2529 "ensure pass2 doesn't push rev too far backward"
2530 conv
= ensure_conversion('resync-pass2-push-backward')
2531 # If the conversion succeeds, then we're okay. We could check the
2532 # actual revisions, too, but the main thing is to know that the
2533 # conversion doesn't fail.
2536 @Cvs2SvnTestFunction
2538 "reveal a bug that added a branch file twice"
2539 conv
= ensure_conversion('double-add')
2540 # If the conversion succeeds, then we're okay. We could check the
2541 # actual revisions, too, but the main thing is to know that the
2542 # conversion doesn't fail.
2545 @Cvs2SvnTestFunction
2546 def bogus_branch_copy():
2547 "reveal a bug that copies a branch file wrongly"
2548 conv
= ensure_conversion('bogus-branch-copy')
2549 # If the conversion succeeds, then we're okay. We could check the
2550 # actual revisions, too, but the main thing is to know that the
2551 # conversion doesn't fail.
2554 @Cvs2SvnTestFunction
2555 def nested_ttb_directories():
2556 "require error if ttb directories are not disjoint"
2558 {'trunk' : 'a', 'branches' : 'a',},
2559 {'trunk' : 'a', 'tags' : 'a',},
2560 {'branches' : 'a', 'tags' : 'a',},
2561 # This option conflicts with the default trunk path:
2562 {'branches' : 'trunk',},
2563 # Try some nested directories:
2564 {'trunk' : 'a', 'branches' : 'a/b',},
2565 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2566 {'branches' : 'a', 'tags' : 'a/b',},
2569 for opts
in opts_list
:
2571 'main', error_re
=r
'The following paths are not disjoint\:', **opts
2575 class AutoProps(Cvs2SvnPropertiesTestCase
):
2578 The files are as follows:
2580 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2581 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2582 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2583 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2584 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2585 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2586 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2587 trunk/foo.UPCASE1: no -kb, no mime type.
2588 trunk/foo.UPCASE2: no -kb, no mime type.
2591 def __init__(self
, args
, **kw
):
2592 ### TODO: It's a bit klugey to construct this path here. See also
2593 ### the comment in eol_mime().
2594 auto_props_path
= os
.path
.join(
2595 test_data_dir
, 'eol-mime-cvsrepos', 'auto-props')
2597 Cvs2SvnPropertiesTestCase
.__init
__(
2607 '--auto-props=%s' % auto_props_path
,
2608 '--eol-from-mime-type'
2613 auto_props_ignore_case
= AutoProps(
2614 doc
="test auto-props",
2615 args
=['--default-eol=native'],
2617 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS
, None]),
2618 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS
, None]),
2619 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2620 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2622 ['bin', None, 'application/octet-stream', None, '']),
2623 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2625 ['dbf', None, 'application/what-is-dbf', None, None]),
2626 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS
, None]),
2627 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS
, None]),
2631 @Cvs2SvnTestFunction
2632 def ctrl_char_in_filename():
2633 "do not allow control characters in filenames"
2636 srcrepos_path
= os
.path
.join(test_data_dir
,'main-cvsrepos')
2637 dstrepos_path
= os
.path
.join(test_data_dir
,'ctrl-char-filename-cvsrepos')
2638 if os
.path
.exists(dstrepos_path
):
2639 safe_rmtree(dstrepos_path
)
2641 # create repos from existing main repos
2642 shutil
.copytree(srcrepos_path
, dstrepos_path
)
2643 base_path
= os
.path
.join(dstrepos_path
, 'single-files')
2645 shutil
.copyfile(os
.path
.join(base_path
, 'twoquick,v'),
2646 os
.path
.join(base_path
, 'two\rquick,v'))
2648 # Operating systems that don't allow control characters in
2649 # filenames will hopefully have thrown an exception; in that
2650 # case, just skip this test.
2651 raise svntest
.Skip()
2653 conv
= ensure_conversion(
2654 'ctrl-char-filename',
2655 error_re
=(r
'.*Subversion does not allow character .*.'),
2658 safe_rmtree(dstrepos_path
)
2661 @Cvs2SvnTestFunction
2662 def commit_dependencies():
2663 "interleaved and multi-branch commits to same files"
2664 conv
= ensure_conversion("commit-dependencies")
2665 conv
.logs
[2].check('adding', (
2666 ('/%(trunk)s/interleaved', 'A'),
2667 ('/%(trunk)s/interleaved/file1', 'A'),
2668 ('/%(trunk)s/interleaved/file2', 'A'),
2670 conv
.logs
[3].check('big commit', (
2671 ('/%(trunk)s/interleaved/file1', 'M'),
2672 ('/%(trunk)s/interleaved/file2', 'M'),
2674 conv
.logs
[4].check('dependant small commit', (
2675 ('/%(trunk)s/interleaved/file1', 'M'),
2677 conv
.logs
[5].check('adding', (
2678 ('/%(trunk)s/multi-branch', 'A'),
2679 ('/%(trunk)s/multi-branch/file1', 'A'),
2680 ('/%(trunk)s/multi-branch/file2', 'A'),
2682 conv
.logs
[6].check(sym_log_msg("branch"), (
2683 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2684 ('/%(branches)s/branch/interleaved', 'D'),
2686 conv
.logs
[7].check('multi-branch-commit', (
2687 ('/%(trunk)s/multi-branch/file1', 'M'),
2688 ('/%(trunk)s/multi-branch/file2', 'M'),
2689 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2690 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2694 @Cvs2SvnTestFunction
2695 def double_branch_delete():
2696 "fill branches before modifying files on them"
2697 conv
= ensure_conversion('double-branch-delete')
2699 # Test for issue #102. The file IMarshalledValue.java is branched,
2700 # deleted, readded on the branch, and then deleted again. If the
2701 # fill for the file on the branch is postponed until after the
2702 # modification, the file will end up live on the branch instead of
2703 # dead! Make sure it happens at the right time.
2705 conv
.logs
[6].check('JBAS-2436 - Adding LGPL Header2', (
2706 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2709 conv
.logs
[7].check('JBAS-3025 - Removing dependency', (
2710 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2714 @Cvs2SvnTestFunction
2715 def symbol_mismatches():
2716 "error for conflicting tag/branch"
2720 args
=['--symbol-default=strict'],
2721 error_re
=r
'.*Problems determining how symbols should be converted',
2725 @Cvs2SvnTestFunction
2726 def overlook_symbol_mismatches():
2727 "overlook conflicting tag/branch when --trunk-only"
2729 # This is a test for issue #85.
2731 ensure_conversion('symbol-mess', args
=['--trunk-only'])
2734 @Cvs2SvnTestFunction
2735 def force_symbols():
2736 "force symbols to be tags/branches"
2738 conv
= ensure_conversion(
2740 args
=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2741 if conv
.path_exists('tags', 'BRANCH') \
2742 or not conv
.path_exists('branches', 'BRANCH'):
2744 if not conv
.path_exists('tags', 'TAG') \
2745 or conv
.path_exists('branches', 'TAG'):
2747 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2748 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2750 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2751 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2755 @Cvs2SvnTestFunction
2756 def commit_blocks_tags():
2757 "commit prevents forced tag"
2759 basic_args
= ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2762 args
=(basic_args
+ ['--force-tag=BRANCH_WITH_COMMIT']),
2764 r
'.*The following branches cannot be forced to be tags '
2765 r
'because they have commits'
2770 @Cvs2SvnTestFunction
2771 def blocked_excludes():
2772 "error for blocked excludes"
2774 basic_args
= ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2775 for blocker
in ['BRANCH', 'COMMIT', 'UNNAMED']:
2779 args
=(basic_args
+ ['--exclude=BLOCKED_BY_%s' % blocker
]))
2780 raise MissingErrorException()
2785 @Cvs2SvnTestFunction
2786 def unblock_blocked_excludes():
2787 "excluding blocker removes blockage"
2789 basic_args
= ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2790 for blocker
in ['BRANCH', 'COMMIT']:
2793 args
=(basic_args
+ ['--exclude=BLOCKED_BY_%s' % blocker
,
2794 '--exclude=BLOCKING_%s' % blocker
]))
2797 @Cvs2SvnTestFunction
2798 def regexp_force_symbols():
2799 "force symbols via regular expressions"
2801 conv
= ensure_conversion(
2803 args
=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2804 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2805 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2807 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2808 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2812 @Cvs2SvnTestFunction
2813 def heuristic_symbol_default():
2814 "test 'heuristic' symbol default"
2816 conv
= ensure_conversion(
2817 'symbol-mess', args
=['--symbol-default=heuristic'])
2818 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2819 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2821 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2822 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2826 @Cvs2SvnTestFunction
2827 def branch_symbol_default():
2828 "test 'branch' symbol default"
2830 conv
= ensure_conversion(
2831 'symbol-mess', args
=['--symbol-default=branch'])
2832 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2833 or not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2835 if conv
.path_exists('tags', 'MOSTLY_TAG') \
2836 or not conv
.path_exists('branches', 'MOSTLY_TAG'):
2840 @Cvs2SvnTestFunction
2841 def tag_symbol_default():
2842 "test 'tag' symbol default"
2844 conv
= ensure_conversion(
2845 'symbol-mess', args
=['--symbol-default=tag'])
2846 if not conv
.path_exists('tags', 'MOSTLY_BRANCH') \
2847 or conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2849 if not conv
.path_exists('tags', 'MOSTLY_TAG') \
2850 or conv
.path_exists('branches', 'MOSTLY_TAG'):
2854 @Cvs2SvnTestFunction
2855 def symbol_transform():
2856 "test --symbol-transform"
2858 conv
= ensure_conversion(
2861 '--symbol-default=heuristic',
2862 '--symbol-transform=BRANCH:branch',
2863 '--symbol-transform=TAG:tag',
2864 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2866 if not conv
.path_exists('branches', 'branch'):
2868 if not conv
.path_exists('tags', 'tag'):
2870 if not conv
.path_exists('branches', 'MOSTLY.BRANCH'):
2872 if not conv
.path_exists('tags', 'MOSTLY.TAG'):
2876 @Cvs2SvnTestFunction
2877 def write_symbol_info():
2878 "test --write-symbol-info"
2882 'trunk', 'trunk', '.'],
2883 ['0', 'BLOCKED_BY_UNNAMED',
2884 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2885 ['0', 'BLOCKING_COMMIT',
2886 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2887 ['0', 'BLOCKED_BY_COMMIT',
2888 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2889 ['0', 'BLOCKING_BRANCH',
2890 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2891 ['0', 'BLOCKED_BY_BRANCH',
2892 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2893 ['0', 'MOSTLY_BRANCH',
2897 ['0', 'BRANCH_WITH_COMMIT',
2898 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2900 'branch', 'branches/BRANCH', '.trunk.'],
2902 'tag', 'tags/TAG', '.trunk.'],
2903 ['0', 'unlabeled-1.1.12.1.2',
2904 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2906 expected_lines
.sort()
2908 symbol_info_file
= os
.path
.join(tmp_dir
, 'symbol-mess-symbol-info.txt')
2913 '--symbol-default=strict',
2914 '--write-symbol-info=%s' % (symbol_info_file
,),
2915 '--passes=:CollateSymbolsPass',
2918 raise MissingErrorException()
2922 comment_re
= re
.compile(r
'^\s*\#')
2923 for l
in open(symbol_info_file
, 'r'):
2924 if comment_re
.match(l
):
2926 lines
.append(l
.strip().split())
2928 if lines
!= expected_lines
:
2929 s
= ['Symbol info incorrect\n']
2931 for diffline
in differ
.compare(
2932 [' '.join(line
) + '\n' for line
in expected_lines
],
2933 [' '.join(line
) + '\n' for line
in lines
],
2936 raise Failure(''.join(s
))
2939 @Cvs2SvnTestFunction
2941 "test --symbol-hints for setting branch/tag"
2943 conv
= ensure_conversion(
2944 'symbol-mess', symbol_hints_file
='symbol-mess-symbol-hints.txt',
2946 if not conv
.path_exists('branches', 'MOSTLY_BRANCH'):
2948 if not conv
.path_exists('tags', 'MOSTLY_TAG'):
2950 conv
.logs
[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2951 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2953 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2954 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
2956 conv
.logs
[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2957 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
2961 @Cvs2SvnTestFunction
2963 "test --symbol-hints for setting parent"
2965 conv
= ensure_conversion(
2966 'symbol-mess', symbol_hints_file
='symbol-mess-parent-hints.txt',
2968 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2969 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
2973 @Cvs2SvnTestFunction
2974 def parent_hints_invalid():
2975 "test --symbol-hints with an invalid parent"
2977 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2978 # this symbol hints file sets the preferred parent to BRANCH
2980 conv
= ensure_conversion(
2981 'symbol-mess', symbol_hints_file
='symbol-mess-parent-hints-invalid.txt',
2983 r
"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
2988 @Cvs2SvnTestFunction
2989 def parent_hints_wildcards():
2990 "test --symbol-hints wildcards"
2992 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2993 # this symbol hints file sets the preferred parent to BRANCH
2995 conv
= ensure_conversion(
2997 symbol_hints_file
='symbol-mess-parent-hints-wildcards.txt',
2999 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3000 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3004 @Cvs2SvnTestFunction
3006 "test --symbol-hints for setting svn paths"
3008 conv
= ensure_conversion(
3009 'symbol-mess', symbol_hints_file
='symbol-mess-path-hints.txt',
3011 conv
.logs
[1].check('Standard project directories initialized by cvs2svn.', (
3014 ('/a/strange', 'A'),
3015 ('/a/strange/trunk', 'A'),
3016 ('/a/strange/trunk/path', 'A'),
3020 conv
.logs
[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3022 ('/special/tag', 'A'),
3023 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
3025 conv
.logs
[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3026 ('/special/other', 'A'),
3027 ('/special/other/branch', 'A'),
3028 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
3030 conv
.logs
[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3031 ('/special/branch', 'A'),
3032 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
3036 @Cvs2SvnTestFunction
3038 "test problem from issue 99"
3040 conv
= ensure_conversion('issue-99')
3043 @Cvs2SvnTestFunction
3045 "test problem from issue 100"
3047 conv
= ensure_conversion('issue-100')
3048 file1
= conv
.get_wc('trunk', 'file1.txt')
3049 if file(file1
).read() != 'file1.txt<1.2>\n':
3053 @Cvs2SvnTestFunction
3055 "test problem from issue 106"
3057 conv
= ensure_conversion('issue-106')
3060 @Cvs2SvnTestFunction
3061 def options_option():
3062 "use of the --options option"
3064 conv
= ensure_conversion('main', options_file
='cvs2svn.options')
3067 @Cvs2SvnTestFunction
3069 "multiproject conversion"
3071 conv
= ensure_conversion(
3072 'main', options_file
='cvs2svn-multiproject.options'
3074 conv
.logs
[1].check('Standard project directories initialized by cvs2svn.', (
3075 ('/partial-prune', 'A'),
3076 ('/partial-prune/trunk', 'A'),
3077 ('/partial-prune/branches', 'A'),
3078 ('/partial-prune/tags', 'A'),
3079 ('/partial-prune/releases', 'A'),
3083 @Cvs2SvnTestFunction
3085 "multiproject conversion with cross-project commits"
3087 conv
= ensure_conversion(
3088 'main', options_file
='cvs2svn-crossproject.options'
3092 @Cvs2SvnTestFunction
3093 def tag_with_no_revision():
3094 "tag defined but revision is deleted"
3096 conv
= ensure_conversion('tag-with-no-revision')
3099 @Cvs2SvnTestFunction
3100 def delete_cvsignore():
3101 "svn:ignore should vanish when .cvsignore does"
3103 # This is issue #81.
3105 conv
= ensure_conversion('delete-cvsignore')
3107 wc_tree
= conv
.get_wc_tree()
3108 props
= props_for_path(wc_tree
, 'trunk/proj')
3110 if props
.has_key('svn:ignore'):
3114 @Cvs2SvnTestFunction
3115 def repeated_deltatext():
3116 "ignore repeated deltatext blocks with warning"
3118 conv
= ensure_conversion('repeated-deltatext')
3119 warning_re
= r
'.*Deltatext block for revision 1.1 appeared twice'
3120 if not conv
.output_found(warning_re
):
3124 @Cvs2SvnTestFunction
3126 "process some nasty dependency graphs"
3128 # It's not how well the bear can dance, but that the bear can dance
3130 conv
= ensure_conversion('nasty-graphs')
3133 @Cvs2SvnTestFunction
3134 def tagging_after_delete():
3135 "optimal tag after deleting files"
3137 conv
= ensure_conversion('tagging-after-delete')
3139 # tag should be 'clean', no deletes
3140 log
= conv
.find_tag_log('tag1')
3142 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
3144 log
.check_changes(expected
)
3147 @Cvs2SvnTestFunction
3148 def crossed_branches():
3149 "branches created in inconsistent orders"
3151 conv
= ensure_conversion('crossed-branches')
3154 @Cvs2SvnTestFunction
3155 def file_directory_conflict():
3156 "error when filename conflicts with directory name"
3158 conv
= ensure_conversion(
3159 'file-directory-conflict',
3160 error_re
=r
'.*Directory name conflicts with filename',
3164 @Cvs2SvnTestFunction
3165 def attic_directory_conflict():
3166 "error when attic filename conflicts with dirname"
3168 # This tests the problem reported in issue #105.
3170 conv
= ensure_conversion(
3171 'attic-directory-conflict',
3172 error_re
=r
'.*Directory name conflicts with filename',
3176 @Cvs2SvnTestFunction
3178 "verify that --use-rcs and --use-internal-co agree"
3180 rcs_conv
= ensure_conversion(
3181 'main', args
=['--use-rcs', '--default-eol=native'],
3183 conv
= ensure_conversion(
3184 'main', args
=['--default-eol=native'],
3186 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3188 rcs_lines
= run_program(
3189 svntest
.main
.svnadmin_binary
, None, 'dump', '-q', '-r', '1:HEAD',
3191 lines
= run_program(
3192 svntest
.main
.svnadmin_binary
, None, 'dump', '-q', '-r', '1:HEAD',
3194 # Compare all lines following the repository UUID:
3195 if lines
[3:] != rcs_lines
[3:]:
3199 @Cvs2SvnTestFunction
3200 def internal_co_exclude():
3201 "verify that --use-internal-co --exclude=... works"
3203 rcs_conv
= ensure_conversion(
3205 args
=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3207 conv
= ensure_conversion(
3209 args
=['--exclude=BRANCH', '--default-eol=native'],
3211 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3213 rcs_lines
= run_program(
3214 svntest
.main
.svnadmin_binary
, None, 'dump', '-q', '-r', '1:HEAD',
3216 lines
= run_program(
3217 svntest
.main
.svnadmin_binary
, None, 'dump', '-q', '-r', '1:HEAD',
3219 # Compare all lines following the repository UUID:
3220 if lines
[3:] != rcs_lines
[3:]:
3224 @Cvs2SvnTestFunction
3225 def internal_co_trunk_only():
3226 "verify that --use-internal-co --trunk-only works"
3228 conv
= ensure_conversion(
3230 args
=['--trunk-only', '--default-eol=native'],
3232 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3236 @Cvs2SvnTestFunction
3237 def leftover_revs():
3238 "check for leftover checked-out revisions"
3240 conv
= ensure_conversion(
3242 args
=['--exclude=BRANCH', '--default-eol=native'],
3244 if conv
.output_found(r
'WARNING\: internal problem\: leftover revisions'):
3248 @Cvs2SvnTestFunction
3249 def requires_internal_co():
3250 "test that internal co can do more than RCS"
3251 # See issues 4, 11 for the bugs whose regression we're testing for.
3252 # Unlike in requires_cvs above, issue 29 is not covered.
3253 conv
= ensure_conversion('requires-cvs')
3255 atsign_contents
= file(conv
.get_wc("trunk", "atsign-add")).read()
3257 if atsign_contents
[-1:] == "@":
3260 if not (conv
.logs
[6].author
== "William Lyon Phelps III" and
3261 conv
.logs
[5].author
== "j random"):
3265 @Cvs2SvnTestFunction
3266 def internal_co_keywords():
3267 "test that internal co handles keywords correctly"
3268 conv_ic
= ensure_conversion('internal-co-keywords',
3269 args
=["--keywords-off"])
3270 conv_cvs
= ensure_conversion('internal-co-keywords',
3271 args
=["--use-cvs", "--keywords-off"])
3273 ko_ic
= file(conv_ic
.get_wc('trunk', 'dir', 'ko.txt')).read()
3274 ko_cvs
= file(conv_cvs
.get_wc('trunk', 'dir', 'ko.txt')).read()
3275 kk_ic
= file(conv_ic
.get_wc('trunk', 'dir', 'kk.txt')).read()
3276 kk_cvs
= file(conv_cvs
.get_wc('trunk', 'dir', 'kk.txt')).read()
3277 kv_ic
= file(conv_ic
.get_wc('trunk', 'dir', 'kv.txt')).read()
3278 kv_cvs
= file(conv_cvs
.get_wc('trunk', 'dir', 'kv.txt')).read()
3285 # The date format changed between cvs and co ('/' instead of '-').
3286 # Accept either one:
3287 date_substitution_re
= re
.compile(r
' ([0-9]*)-([0-9]*)-([0-9]*) ')
3288 if kv_ic
!= kv_cvs \
3289 and date_substitution_re
.sub(r
' \1/\2/\3 ', kv_ic
) != kv_cvs
:
3293 @Cvs2SvnTestFunction
3294 def timestamp_chaos():
3295 "test timestamp adjustments"
3297 conv
= ensure_conversion('timestamp-chaos', args
=["-v"])
3299 # The times are expressed here in UTC:
3301 '2007-01-01 21:00:00', # Initial commit
3302 '2007-01-01 21:00:00', # revision 1.1 of both files
3303 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3304 '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards
3305 '2007-01-01 22:00:00', # revision 1.3 of both files
3308 # Convert the times to seconds since the epoch, in UTC:
3309 times
= [calendar
.timegm(svn_strptime(t
)) for t
in times
]
3311 for i
in range(len(times
)):
3312 if abs(conv
.logs
[i
+ 1].date
- times
[i
]) > 0.1:
3316 @Cvs2SvnTestFunction
3318 "convert a repository that contains symlinks"
3320 # This is a test for issue #97.
3322 proj
= os
.path
.join(test_data_dir
, 'symlinks-cvsrepos', 'proj')
3325 os
.path
.join('..', 'file.txt,v'),
3326 os
.path
.join(proj
, 'dir1', 'file.txt,v'),
3330 os
.path
.join(proj
, 'dir2'),
3336 except AttributeError:
3337 # Apparently this OS doesn't support symlinks, so skip test.
3338 raise svntest
.Skip()
3341 for (src
,dst
) in links
:
3342 os
.symlink(src
, dst
)
3344 conv
= ensure_conversion('symlinks')
3345 conv
.logs
[2].check('', (
3346 ('/%(trunk)s/proj', 'A'),
3347 ('/%(trunk)s/proj/file.txt', 'A'),
3348 ('/%(trunk)s/proj/dir1', 'A'),
3349 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3350 ('/%(trunk)s/proj/dir2', 'A'),
3351 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3354 for (src
,dst
) in links
:
3358 @Cvs2SvnTestFunction
3359 def empty_trunk_path():
3360 "allow --trunk to be empty if --trunk-only"
3362 # This is a test for issue #53.
3364 conv
= ensure_conversion(
3365 'main', args
=['--trunk-only', '--trunk='],
3369 @Cvs2SvnTestFunction
3370 def preferred_parent_cycle():
3371 "handle a cycle in branch parent preferences"
3373 conv
= ensure_conversion('preferred-parent-cycle')
3376 @Cvs2SvnTestFunction
3377 def branch_from_empty_dir():
3378 "branch from an empty directory"
3380 conv
= ensure_conversion('branch-from-empty-dir')
3383 @Cvs2SvnTestFunction
3385 "add a file on a branch then on trunk"
3387 conv
= ensure_conversion('trunk-readd')
3390 @Cvs2SvnTestFunction
3391 def branch_from_deleted_1_1():
3392 "branch from a 1.1 revision that will be deleted"
3394 conv
= ensure_conversion('branch-from-deleted-1-1')
3395 conv
.logs
[5].check('Adding b.txt:1.1.2.1', (
3396 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3398 conv
.logs
[6].check('Adding b.txt:1.1.4.1', (
3399 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3401 conv
.logs
[7].check('Adding b.txt:1.2', (
3402 ('/%(trunk)s/proj/b.txt', 'A'),
3405 conv
.logs
[8].check('Adding c.txt:1.1.2.1', (
3406 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3408 conv
.logs
[9].check('Adding c.txt:1.1.4.1', (
3409 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3413 @Cvs2SvnTestFunction
3414 def add_on_branch():
3415 "add a file on a branch using newer CVS"
3417 conv
= ensure_conversion('add-on-branch')
3418 conv
.logs
[6].check('Adding b.txt:1.1', (
3419 ('/%(trunk)s/proj/b.txt', 'A'),
3421 conv
.logs
[7].check('Adding b.txt:1.1.2.2', (
3422 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3424 conv
.logs
[8].check('Adding c.txt:1.1', (
3425 ('/%(trunk)s/proj/c.txt', 'A'),
3427 conv
.logs
[9].check('Removing c.txt:1.2', (
3428 ('/%(trunk)s/proj/c.txt', 'D'),
3430 conv
.logs
[10].check('Adding c.txt:1.2.2.2', (
3431 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3433 conv
.logs
[11].check('Adding d.txt:1.1', (
3434 ('/%(trunk)s/proj/d.txt', 'A'),
3436 conv
.logs
[12].check('Adding d.txt:1.1.2.2', (
3437 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3441 @Cvs2SvnTestFunction
3443 "test output in git-fast-import format"
3445 # Note: To test importing into git, do
3447 # ./run-tests <test-number>
3450 # cat cvs2svn-tmp/{blobfile,dumpfile}.out | git fast-import
3452 # Or, to load the dumpfiles separately:
3454 # cat cvs2svn-tmp/git-blob.dat \
3455 # | git fast-import --export-marks=cvs2svn-tmp/git-marks.dat
3456 # cat cvs2svn-tmp/git-dump.dat \
3457 # | git fast-import --import-marks=cvs2svn-tmp/git-marks.dat
3459 # Then use "gitk --all", "git log", etc. to test the contents of the
3462 # We don't have the infrastructure to check that the resulting git
3463 # repository is correct, so we just check that the conversion runs
3465 conv
= GitConversion('main', None, [
3466 '--blobfile=cvs2svn-tmp/blobfile.out',
3467 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3468 '--username=cvs2git',
3469 'test-data/main-cvsrepos',
3473 @Cvs2SvnTestFunction
3475 "test cvs2git --use-external-blob-generator option"
3477 # See comment in main_git() for more information.
3479 conv
= GitConversion('main', None, [
3480 '--use-external-blob-generator',
3481 '--blobfile=cvs2svn-tmp/blobfile.out',
3482 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3483 '--username=cvs2git',
3484 'test-data/main-cvsrepos',
3488 @Cvs2SvnTestFunction
3490 "test cvs2git using options file"
3492 conv
= GitConversion('main', None, [], options_file
='cvs2git.options')
3495 @Cvs2SvnTestFunction
3497 "output in git-fast-import format with inline data"
3499 # The output should be suitable for import by Mercurial.
3501 # We don't have the infrastructure to check that the resulting
3502 # Mercurial repository is correct, so we just check that the
3503 # conversion runs to completion:
3504 conv
= GitConversion('main', None, [], options_file
='cvs2hg.options')
3507 @Cvs2SvnTestFunction
3508 def invalid_symbol():
3509 "a symbol with the incorrect format"
3511 conv
= ensure_conversion('invalid-symbol')
3512 if not conv
.output_found(
3513 r
".*branch 'SYMBOL' references invalid revision 1$"
3518 @Cvs2SvnTestFunction
3519 def invalid_symbol_ignore():
3520 "ignore a symbol using a SymbolMapper"
3522 conv
= ensure_conversion(
3523 'invalid-symbol', options_file
='cvs2svn-ignore.options'
3527 @Cvs2SvnTestFunction
3528 def invalid_symbol_ignore2():
3529 "ignore a symbol using an IgnoreSymbolTransform"
3531 conv
= ensure_conversion(
3532 'invalid-symbol', options_file
='cvs2svn-ignore2.options'
3536 class EOLVariants(Cvs2SvnTestCase
):
3537 "handle various --eol-style options"
3539 eol_style_strings
= {
3546 def __init__(self
, eol_style
):
3547 self
.eol_style
= eol_style
3548 self
.dumpfile
= 'eol-variants-%s.dump' % (self
.eol_style
,)
3549 Cvs2SvnTestCase
.__init
__(
3550 self
, 'eol-variants', variant
=self
.eol_style
,
3551 dumpfile
=self
.dumpfile
,
3553 '--default-eol=%s' % (self
.eol_style
,),
3557 def run(self
, sbox
):
3558 conv
= self
.ensure_conversion()
3559 dump_contents
= open(conv
.dumpfile
, 'rb').read()
3560 expected_text
= self
.eol_style_strings
[self
.eol_style
].join(
3561 ['line 1', 'line 2', '\n\n']
3563 if not dump_contents
.endswith(expected_text
):
3567 @Cvs2SvnTestFunction
3569 "handle a file with no revisions (issue #80)"
3571 conv
= ensure_conversion('no-revs-file')
3574 @Cvs2SvnTestFunction
3575 def mirror_keyerror_test():
3576 "a case that gave KeyError in SVNRepositoryMirror"
3578 conv
= ensure_conversion('mirror-keyerror')
3581 @Cvs2SvnTestFunction
3582 def exclude_ntdb_test():
3583 "exclude a non-trunk default branch"
3585 symbol_info_file
= os
.path
.join(tmp_dir
, 'exclude-ntdb-symbol-info.txt')
3586 conv
= ensure_conversion(
3589 '--write-symbol-info=%s' % (symbol_info_file
,),
3590 '--exclude=branch3',
3592 '--exclude=vendortag3',
3593 '--exclude=vendorbranch',
3598 @Cvs2SvnTestFunction
3599 def mirror_keyerror2_test():
3600 "a case that gave KeyError in RepositoryMirror"
3602 conv
= ensure_conversion('mirror-keyerror2')
3605 @Cvs2SvnTestFunction
3606 def mirror_keyerror3_test():
3607 "a case that gave KeyError in RepositoryMirror"
3609 conv
= ensure_conversion('mirror-keyerror3')
3612 @Cvs2SvnTestFunction
3613 def add_cvsignore_to_branch_test():
3614 "check adding .cvsignore to an existing branch"
3616 # This a test for issue #122.
3618 conv
= ensure_conversion('add-cvsignore-to-branch')
3619 wc_tree
= conv
.get_wc_tree()
3620 trunk_props
= props_for_path(wc_tree
, 'trunk/dir')
3621 if trunk_props
['svn:ignore'] != '*.o\n\n':
3624 branch_props
= props_for_path(wc_tree
, 'branches/BRANCH/dir')
3625 if branch_props
['svn:ignore'] != '*.o\n\n':
3629 @Cvs2SvnTestFunction
3630 def missing_deltatext():
3631 "a revision's deltatext is missing"
3633 # This is a type of RCS file corruption that has been observed.
3634 conv
= ensure_conversion(
3635 'missing-deltatext',
3637 r
"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3642 @Cvs2SvnTestFunction
3643 def transform_unlabeled_branch_name():
3644 "transform name of unlabeled branch"
3646 conv
= ensure_conversion(
3649 '--symbol-transform=unlabeled-1.1.4:BRANCH2',
3652 if conv
.path_exists('branches', 'unlabeled-1.1.4'):
3653 raise Failure('Branch unlabeled-1.1.4 not excluded')
3654 if not conv
.path_exists('branches', 'BRANCH2'):
3655 raise Failure('Branch BRANCH2 not found')
3658 @Cvs2SvnTestFunction
3659 def ignore_unlabeled_branch():
3660 "ignoring an unlabeled branch is not allowed"
3662 conv
= ensure_conversion(
3664 options_file
='cvs2svn-ignore.options',
3666 r
"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' "
3667 r
"in \'.*\' contains commits"
3672 @Cvs2SvnTestFunction
3673 def exclude_unlabeled_branch():
3674 "exclude unlabeled branch"
3676 conv
= ensure_conversion(
3678 args
=['--exclude=unlabeled-.*'],
3680 if conv
.path_exists('branches', 'unlabeled-1.1.4'):
3681 raise Failure('Branch unlabeled-1.1.4 not excluded')
3684 @Cvs2SvnTestFunction
3685 def unlabeled_branch_name_collision():
3686 "transform unlabeled branch to same name as branch"
3688 conv
= ensure_conversion(
3691 '--symbol-transform=unlabeled-1.1.4:BRANCH',
3694 r
"ERROR\: Symbol name \'BRANCH\' is already used"
3699 @Cvs2SvnTestFunction
3700 def collision_with_unlabeled_branch_name():
3701 "transform branch to same name as unlabeled branch"
3703 conv
= ensure_conversion(
3706 '--symbol-transform=BRANCH:unlabeled-1.1.4',
3709 r
"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used"
3714 @Cvs2SvnTestFunction
3716 "a repo with many removable dead revisions"
3718 conv
= ensure_conversion('many-deletes')
3719 conv
.logs
[5].check('Add files on BRANCH', (
3720 ('/%(branches)s/BRANCH/proj/b.txt', 'A'),
3722 conv
.logs
[6].check('Add files on BRANCH2', (
3723 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3724 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3725 ('/%(branches)s/BRANCH2/proj/d.txt', 'A'),
3729 cvs_description
= Cvs2SvnPropertiesTestCase(
3731 doc
='test handling of CVS file descriptions',
3732 props_to_test
=['cvs:description'],
3734 ('trunk/proj/default', ['This is an example file description.']),
3735 ('trunk/proj/sub1/default', [None]),
3739 @Cvs2SvnTestFunction
3740 def include_empty_directories():
3741 "test --include-empty-directories option"
3743 conv
= ensure_conversion(
3744 'empty-directories', args
=['--include-empty-directories'],
3746 conv
.logs
[1].check('Standard project directories', (
3747 ('/%(trunk)s', 'A'),
3748 ('/%(branches)s', 'A'),
3750 ('/%(trunk)s/root-empty-directory', 'A'),
3751 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3753 conv
.logs
[3].check('Add b.txt.', (
3754 ('/%(trunk)s/direct', 'A'),
3755 ('/%(trunk)s/direct/b.txt', 'A'),
3756 ('/%(trunk)s/direct/empty-directory', 'A'),
3757 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3759 conv
.logs
[4].check('Add c.txt.', (
3760 ('/%(trunk)s/indirect', 'A'),
3761 ('/%(trunk)s/indirect/subdirectory', 'A'),
3762 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3763 ('/%(trunk)s/indirect/empty-directory', 'A'),
3764 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3766 conv
.logs
[5].check('Remove b.txt', (
3767 ('/%(trunk)s/direct', 'D'),
3769 conv
.logs
[6].check('Remove c.txt', (
3770 ('/%(trunk)s/indirect', 'D'),
3772 conv
.logs
[7].check('Re-add b.txt.', (
3773 ('/%(trunk)s/direct', 'A'),
3774 ('/%(trunk)s/direct/b.txt', 'A'),
3775 ('/%(trunk)s/direct/empty-directory', 'A'),
3776 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3778 conv
.logs
[8].check('Re-add c.txt.', (
3779 ('/%(trunk)s/indirect', 'A'),
3780 ('/%(trunk)s/indirect/subdirectory', 'A'),
3781 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3782 ('/%(trunk)s/indirect/empty-directory', 'A'),
3783 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3785 conv
.logs
[9].check('This commit was manufactured', (
3786 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3788 conv
.logs
[10].check('This commit was manufactured', (
3789 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3791 conv
.logs
[11].check('Import d.txt.', (
3792 ('/%(branches)s/VENDORBRANCH', 'A'),
3793 ('/%(branches)s/VENDORBRANCH/import', 'A'),
3794 ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'),
3795 ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'),
3796 ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory',
3798 ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'),
3799 ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory',
3802 conv
.logs
[12].check('This commit was generated', (
3803 ('/%(trunk)s/import', 'A'),
3804 ('/%(trunk)s/import/d.txt '
3805 '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'),
3806 ('/%(trunk)s/import/empty-directory', 'A'),
3807 ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'),
3811 @Cvs2SvnTestFunction
3812 def include_empty_directories_no_prune():
3813 "test --include-empty-directories with --no-prune"
3815 conv
= ensure_conversion(
3816 'empty-directories', args
=['--include-empty-directories', '--no-prune'],
3818 conv
.logs
[1].check('Standard project directories', (
3819 ('/%(trunk)s', 'A'),
3820 ('/%(branches)s', 'A'),
3822 ('/%(trunk)s/root-empty-directory', 'A'),
3823 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3825 conv
.logs
[3].check('Add b.txt.', (
3826 ('/%(trunk)s/direct', 'A'),
3827 ('/%(trunk)s/direct/b.txt', 'A'),
3828 ('/%(trunk)s/direct/empty-directory', 'A'),
3829 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3831 conv
.logs
[4].check('Add c.txt.', (
3832 ('/%(trunk)s/indirect', 'A'),
3833 ('/%(trunk)s/indirect/subdirectory', 'A'),
3834 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3835 ('/%(trunk)s/indirect/empty-directory', 'A'),
3836 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3838 conv
.logs
[5].check('Remove b.txt', (
3839 ('/%(trunk)s/direct/b.txt', 'D'),
3841 conv
.logs
[6].check('Remove c.txt', (
3842 ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'),
3844 conv
.logs
[7].check('Re-add b.txt.', (
3845 ('/%(trunk)s/direct/b.txt', 'A'),
3847 conv
.logs
[8].check('Re-add c.txt.', (
3848 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3850 conv
.logs
[9].check('This commit was manufactured', (
3851 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3853 conv
.logs
[10].check('This commit was manufactured', (
3854 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3858 @Cvs2SvnTestFunction
3859 def exclude_symbol_default():
3860 "test 'exclude' symbol default"
3862 conv
= ensure_conversion(
3863 'symbol-mess', args
=['--symbol-default=exclude'])
3864 if conv
.path_exists('tags', 'MOSTLY_BRANCH') \
3865 or conv
.path_exists('branches', 'MOSTLY_BRANCH'):
3867 if conv
.path_exists('tags', 'MOSTLY_TAG') \
3868 or conv
.path_exists('branches', 'MOSTLY_TAG'):
3872 @Cvs2SvnTestFunction
3873 def add_on_branch2():
3874 "another add-on-branch test case"
3876 conv
= ensure_conversion('add-on-branch2')
3877 if len(conv
.logs
) != 2:
3879 conv
.logs
[2].check('add file on branch', (
3880 ('/%(branches)s/BRANCH', 'A'),
3881 ('/%(branches)s/BRANCH/file1', 'A'),
3885 @Cvs2SvnTestFunction
3886 def branch_from_vendor_branch():
3887 "branch from vendor branch"
3890 'branch-from-vendor-branch',
3891 symbol_hints_file
='branch-from-vendor-branch-symbol-hints.txt',
3895 @Cvs2SvnTestFunction
3896 def strange_default_branch():
3897 "default branch too deep in the hierarchy"
3900 'strange-default-branch',
3902 r
'ERROR\: The default branch 1\.2\.4\.3\.2\.1\.2 '
3903 r
'in file .* is not a top-level branch'
3908 @Cvs2SvnTestFunction
3910 "graft onto preferred parent that was itself moved"
3912 conv
= ensure_conversion(
3915 conv
.logs
[2].check('first', (
3916 ('/%(trunk)s/file1', 'A'),
3917 ('/%(trunk)s/file2', 'A'),
3919 conv
.logs
[3].check('This commit was manufactured', (
3920 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
3922 conv
.logs
[4].check('second', (
3923 ('/%(branches)s/b2/file1', 'M'),
3925 conv
.logs
[5].check('This commit was manufactured', (
3926 ('/%(branches)s/b1 (from /%(branches)s/b2:4)', 'A'),
3929 # b2 and b1 are equally good parents for b3, so accept either one.
3930 # (Currently, cvs2svn chooses b1 as the preferred parent because it
3931 # comes earlier than b2 in alphabetical order.)
3933 conv
.logs
[6].check('This commit was manufactured', (
3934 ('/%(branches)s/b3 (from /%(branches)s/b1:5)', 'A'),
3937 conv
.logs
[6].check('This commit was manufactured', (
3938 ('/%(branches)s/b3 (from /%(branches)s/b2:4)', 'A'),
3942 ########################################################################
3945 # list all tests here, starting with None:
3952 XFail(cvs2hg_manpage
),
3957 PruneWithCare(variant
=1, trunk
='a', branches
='b', tags
='c'),
3959 PruneWithCare(variant
=2, trunk
='a/1', branches
='b/1', tags
='c/1'),
3960 PruneWithCare(variant
=3, trunk
='a/1', branches
='a/2', tags
='a/3'),
3961 interleaved_commits
,
3964 SimpleTags(variant
=1, trunk
='a', branches
='b', tags
='c'),
3965 SimpleTags(variant
=2, trunk
='a/1', branches
='b/1', tags
='c/1'),
3966 SimpleTags(variant
=3, trunk
='a/1', branches
='a/2', tags
='a/3'),
3967 simple_branch_commits
,
3970 mixed_time_branch_with_added_file
,
3976 PhoenixBranch(variant
=1, trunk
='a/1', branches
='b/1', tags
='c/1'),
3981 NoTrunkPrune(variant
=1, trunk
='a', branches
='b', tags
='c'),
3982 NoTrunkPrune(variant
=2, trunk
='a/1', branches
='b/1', tags
='c/1'),
3983 NoTrunkPrune(variant
=3, trunk
='a/1', branches
='a/2', tags
='a/3'),
3987 TaggedBranchAndTrunk(),
3988 TaggedBranchAndTrunk(variant
=1, trunk
='a/1', branches
='a/2', tags
='a/3'),
3992 BranchDeleteFirst(),
3993 BranchDeleteFirst(variant
=1, trunk
='a/1', branches
='a/2', tags
='a/3'),
3996 warning_expected
=1),
3999 variant
='encoding', args
=['--encoding=utf_8']),
4002 variant
='fallback-encoding', args
=['--fallback-encoding=utf_8']),
4004 warning_expected
=1),
4007 variant
='encoding', args
=['--encoding=utf_8']),
4010 variant
='fallback-encoding', args
=['--fallback-encoding=utf_8']),
4011 vendor_branch_sameness
,
4013 vendor_branch_trunk_only
,
4015 default_branches_trunk_only
,
4016 default_branch_and_1_2
,
4017 compose_tag_three_sources
,
4020 PeerPathPruning(variant
=1, trunk
='a/1', branches
='a/2', tags
='a/3'),
4022 EmptyTrunk(variant
=1, trunk
='a', branches
='b', tags
='c'),
4024 EmptyTrunk(variant
=2, trunk
='a/1', branches
='a/2', tags
='a/3'),
4025 no_spurious_svn_commits
,
4026 invalid_closings_on_trunk
,
4029 branch_from_default_branch
,
4031 retain_file_in_attic_too
,
4032 symbolic_name_filling_guide
,
4043 questionable_branch_names
,
4044 questionable_tag_names
,
4046 revision_reorder_bug
,
4048 vendor_branch_delete_add
,
4049 resync_pass2_pull_forward
,
4052 XFail(double_fill2
),
4053 resync_pass2_push_backward
,
4057 nested_ttb_directories
,
4058 auto_props_ignore_case
,
4059 ctrl_char_in_filename
,
4060 commit_dependencies
,
4063 multiply_defined_symbols
,
4064 multiply_defined_symbols_renamed
,
4065 multiply_defined_symbols_ignored
,
4066 repeatedly_defined_symbols
,
4068 double_branch_delete
,
4070 overlook_symbol_mismatches
,
4074 unblock_blocked_excludes
,
4075 regexp_force_symbols
,
4076 heuristic_symbol_default
,
4077 branch_symbol_default
,
4084 parent_hints_invalid
,
4085 parent_hints_wildcards
,
4094 tag_with_no_revision
,
4098 XFail(tagging_after_delete
),
4101 file_directory_conflict
,
4102 attic_directory_conflict
,
4104 internal_co_exclude
,
4105 internal_co_trunk_only
,
4106 internal_co_keywords
,
4108 requires_internal_co
,
4113 preferred_parent_cycle
,
4114 branch_from_empty_dir
,
4116 branch_from_deleted_1_1
,
4124 invalid_symbol_ignore
,
4125 invalid_symbol_ignore2
,
4128 EOLVariants('CRLF'),
4129 EOLVariants('native'),
4131 mirror_keyerror_test
,
4134 mirror_keyerror2_test
,
4135 mirror_keyerror3_test
,
4136 XFail(add_cvsignore_to_branch_test
),
4138 transform_unlabeled_branch_name
,
4139 ignore_unlabeled_branch
,
4140 exclude_unlabeled_branch
,
4141 unlabeled_branch_name_collision
,
4142 collision_with_unlabeled_branch_name
,
4146 include_empty_directories
,
4147 include_empty_directories_no_prune
,
4148 exclude_symbol_default
,
4150 branch_from_vendor_branch
,
4151 strange_default_branch
,
4155 if __name__
== '__main__':
4157 # Configure the environment for reproducable output from svn, etc.
4158 os
.environ
["LC_ALL"] = "C"
4160 # Unfortunately, there is no way under Windows to make Subversion
4161 # think that the local time zone is UTC, so we just work in the
4164 # The Subversion test suite code assumes it's being invoked from
4165 # within a working copy of the Subversion sources, and tries to use
4166 # the binaries in that tree. Since the cvs2svn tree never contains
4167 # a Subversion build, we just use the system's installed binaries.
4168 svntest
.main
.svn_binary
= svn_binary
4169 svntest
.main
.svnlook_binary
= svnlook_binary
4170 svntest
.main
.svnadmin_binary
= svnadmin_binary
4171 svntest
.main
.svnversion_binary
= svnversion_binary
4173 svntest
.main
.run_tests(test_list
)