run-tests.py: Only pass the --svnadmin option to cvs2svn when needed.
[cvs2svn.git] / run-tests.py
blobe42028c1eb66b119b748554ccad269b6a3926950
1 #!/usr/bin/env python
3 # run_tests.py: test suite for cvs2svn
5 # Usage: run_tests.py [-v | --verbose] [list | <num>]
7 # Options:
8 # -v, --verbose
9 # enable verbose output
11 # Arguments (at most one argument is allowed):
12 # list
13 # If the word "list" is passed as an argument, the list of
14 # available tests is printed (but no tests are run).
16 # <num>
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 ######################################################################
36 # General modules
37 import sys
38 import shutil
39 import stat
40 import re
41 import os
42 import time
43 import os.path
44 import locale
45 import textwrap
46 import calendar
47 import types
48 try:
49 from hashlib import md5
50 except ImportError:
51 from md5 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):
56 sys.stderr.write(
57 'error: Python 2, version 2.4 or higher required.\n'
59 sys.exit(1)
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")
65 sys.exit(1)
67 # Load the Subversion test framework.
68 import svntest
69 from svntest import Failure
70 from svntest.main import safe_rmtree
71 from svntest.testcase import TestCase
72 from svntest.testcase import XFail_deco
74 # Test if Mercurial >= 1.1 is available.
75 try:
76 from mercurial import context
77 context.memctx
78 have_hg = True
79 except (ImportError, AttributeError):
80 have_hg = False
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.
90 svn_binary = 'svn'
91 svnlook_binary = 'svnlook'
92 svnadmin_binary = 'svnadmin'
93 svnversion_binary = 'svnversion'
95 test_data_dir = 'test-data'
96 tmp_dir = 'cvs2svn-tmp'
99 #----------------------------------------------------------------------
100 # Helpers.
101 #----------------------------------------------------------------------
104 # The value to expect for svn:keywords if it is set:
105 KEYWORDS = 'Author Date Id Revision'
108 class RunProgramException(Failure):
109 pass
112 class MissingErrorException(Failure):
113 def __init__(self, error_re):
114 Failure.__init__(
115 self, "Test failed because no error matched '%s'" % (error_re,)
119 def run_program(program, error_re, *varargs):
120 """Run PROGRAM with VARARGS, return stdout as a list of lines.
122 If there is any stderr and ERROR_RE is None, raise
123 RunProgramException, and log the stderr lines via
124 svntest.main.logger.info().
126 If ERROR_RE is not None, it is a string regular expression that must
127 match some line of stderr. If it fails to match, raise
128 MissingErrorExpection."""
130 # FIXME: exit_code is currently ignored.
131 exit_code, out, err = svntest.main.run_command(program, 1, 0, *varargs)
133 if error_re:
134 # Specified error expected on stderr.
135 if not err:
136 raise MissingErrorException(error_re)
137 else:
138 for line in err:
139 if re.match(error_re, line):
140 return out
141 raise MissingErrorException(error_re)
142 else:
143 # No stderr allowed.
144 if err:
145 log = svntest.main.logger.info
146 log('%s said:' % program)
147 for line in err:
148 log(' ' + line.rstrip())
149 raise RunProgramException()
151 return out
154 def run_script(script, error_re, *varargs):
155 """Run Python script SCRIPT with VARARGS, returning stdout as a list
156 of lines.
158 If there is any stderr and ERROR_RE is None, raise
159 RunProgramException, and log the stderr lines via
160 svntest.main.logger.info().
162 If ERROR_RE is not None, it is a string regular expression that must
163 match some line of stderr. If it fails to match, raise
164 MissingErrorException."""
166 # Use the same python that is running this script
167 return run_program(sys.executable, error_re, script, *varargs)
168 # On Windows, for an unknown reason, the cmd.exe process invoked by
169 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
170 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
171 # this. Therefore, the redirection of the output to the .s-revs file fails.
172 # We no longer use the problematic invocation on any system, but this
173 # comment remains to warn about this problem.
176 def run_svn(*varargs):
177 """Run svn with VARARGS; return stdout as a list of lines.
178 If there is any stderr, raise RunProgramException, and log the
179 stderr lines via svntest.main.logger.info()."""
180 return run_program(svn_binary, None, *varargs)
183 def repos_to_url(path_to_svn_repos):
184 """This does what you think it does."""
185 rpath = os.path.abspath(path_to_svn_repos)
186 if rpath[0] != '/':
187 rpath = '/' + rpath
188 return 'file://%s' % rpath.replace(os.sep, '/')
191 def svn_strptime(timestr):
192 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
195 class Log:
196 def __init__(self, revision, author, date, symbols):
197 self.revision = revision
198 self.author = author
200 # Internally, we represent the date as seconds since epoch (UTC).
201 # Since standard subversion log output shows dates in localtime
203 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
205 # and time.mktime() converts from localtime, it all works out very
206 # happily.
207 self.date = time.mktime(svn_strptime(date[0:19]))
209 # The following symbols are used for string interpolation when
210 # checking paths:
211 self.symbols = symbols
213 # The changed paths will be accumulated later, as log data is read.
214 # Keys here are paths such as '/trunk/foo/bar', values are letter
215 # codes such as 'M', 'A', and 'D'.
216 self.changed_paths = { }
218 # The msg will be accumulated later, as log data is read.
219 self.msg = ''
221 def absorb_changed_paths(self, out):
222 'Read changed paths from OUT into self, until no more.'
223 while 1:
224 line = out.readline()
225 if len(line) == 1: return
226 line = line[:-1]
227 op_portion = line[3:4]
228 path_portion = line[5:]
229 # If we're running on Windows we get backslashes instead of
230 # forward slashes.
231 path_portion = path_portion.replace('\\', '/')
232 # # We could parse out history information, but currently we
233 # # just leave it in the path portion because that's how some
234 # # tests expect it.
236 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
237 # if m:
238 # path_portion = m.group(1)
239 self.changed_paths[path_portion] = op_portion
241 def __cmp__(self, other):
242 return cmp(self.revision, other.revision) or \
243 cmp(self.author, other.author) or cmp(self.date, other.date) or \
244 cmp(self.changed_paths, other.changed_paths) or \
245 cmp(self.msg, other.msg)
247 def get_path_op(self, path):
248 """Return the operator for the change involving PATH.
250 PATH is allowed to include string interpolation directives (e.g.,
251 '%(trunk)s'), which are interpolated against self.symbols. Return
252 None if there is no record for PATH."""
253 return self.changed_paths.get(path % self.symbols)
255 def check_msg(self, msg):
256 """Verify that this Log's message starts with the specified MSG."""
257 if self.msg.find(msg) != 0:
258 raise Failure(
259 "Revision %d log message was:\n%s\n\n"
260 "It should have begun with:\n%s\n\n"
261 % (self.revision, self.msg, msg,)
264 def check_change(self, path, op):
265 """Verify that this Log includes a change for PATH with operator OP.
267 PATH is allowed to include string interpolation directives (e.g.,
268 '%(trunk)s'), which are interpolated against self.symbols."""
270 path = path % self.symbols
271 found_op = self.changed_paths.get(path, None)
272 if found_op is None:
273 raise Failure(
274 "Revision %d does not include change for path %s "
275 "(it should have been %s).\n"
276 % (self.revision, path, op,)
278 if found_op != op:
279 raise Failure(
280 "Revision %d path %s had op %s (it should have been %s)\n"
281 % (self.revision, path, found_op, op,)
284 def check_changes(self, changed_paths):
285 """Verify that this Log has precisely the CHANGED_PATHS specified.
287 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
288 strings are allowed to include string interpolation directives
289 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
291 cp = {}
292 for (path, op) in changed_paths:
293 cp[path % self.symbols] = op
295 if self.changed_paths != cp:
296 raise Failure(
297 "Revision %d changed paths list was:\n%s\n\n"
298 "It should have been:\n%s\n\n"
299 % (self.revision, self.changed_paths, cp,)
302 def check(self, msg, changed_paths):
303 """Verify that this Log has the MSG and CHANGED_PATHS specified.
305 Convenience function to check two things at once. MSG is passed
306 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
308 self.check_msg(msg)
309 self.check_changes(changed_paths)
312 def parse_log(svn_repos, symbols):
313 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
315 Initialize the Logs' symbols with SYMBOLS."""
317 class LineFeeder:
318 'Make a list of lines behave like an open file handle.'
319 def __init__(self, lines):
320 self.lines = lines
321 def readline(self):
322 if len(self.lines) > 0:
323 return self.lines.pop(0)
324 else:
325 return None
327 def absorb_message_body(out, num_lines, log):
328 """Read NUM_LINES of log message body from OUT into Log item LOG."""
330 for i in range(num_lines):
331 log.msg += out.readline()
333 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
334 '(?P<author>[^\|]+) \| '
335 '(?P<date>[^\|]+) '
336 '\| (?P<lines>[0-9]+) (line|lines)$')
338 log_separator = '-' * 72
340 logs = { }
342 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
344 while 1:
345 this_log = None
346 line = out.readline()
347 if not line: break
348 line = line[:-1]
350 if line.find(log_separator) == 0:
351 line = out.readline()
352 if not line: break
353 line = line[:-1]
354 m = log_start_re.match(line)
355 if m:
356 this_log = Log(
357 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
358 line = out.readline()
359 if not line.find('Changed paths:') == 0:
360 print 'unexpected log output (missing changed paths)'
361 print "Line: '%s'" % line
362 sys.exit(1)
363 this_log.absorb_changed_paths(out)
364 absorb_message_body(out, int(m.group('lines')), this_log)
365 logs[this_log.revision] = this_log
366 elif len(line) == 0:
367 break # We've reached the end of the log output.
368 else:
369 print 'unexpected log output (missing revision line)'
370 print "Line: '%s'" % line
371 sys.exit(1)
372 else:
373 print 'unexpected log output (missing log separator)'
374 print "Line: '%s'" % line
375 sys.exit(1)
377 return logs
380 def erase(path):
381 """Unconditionally remove PATH and its subtree, if any. PATH may be
382 non-existent, a file or symlink, or a directory."""
383 if os.path.isdir(path):
384 safe_rmtree(path)
385 elif os.path.exists(path):
386 os.remove(path)
389 log_msg_text_wrapper = textwrap.TextWrapper(width=76, break_long_words=False)
391 def sym_log_msg(symbolic_name, is_tag=None):
392 """Return the expected log message for a cvs2svn-synthesized revision
393 creating branch or tag SYMBOLIC_NAME."""
395 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
396 if is_tag:
397 type = 'tag'
398 else:
399 type = 'branch'
401 return log_msg_text_wrapper.fill(
402 "This commit was manufactured by cvs2svn to create %s '%s'."
403 % (type, symbolic_name)
407 def make_conversion_id(
408 name, args, passbypass, options_file=None, symbol_hints_file=None
410 """Create an identifying tag for a conversion.
412 The return value can also be used as part of a filesystem path.
414 NAME is the name of the CVS repository.
416 ARGS are the extra arguments to be passed to cvs2svn.
418 PASSBYPASS is a boolean indicating whether the conversion is to be
419 run one pass at a time.
421 If OPTIONS_FILE is specified, it is an options file that will be
422 used for the conversion.
424 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
425 will be used for the conversion.
427 The 1-to-1 mapping between cvs2svn command parameters and
428 conversion_ids allows us to avoid running the same conversion more
429 than once, when multiple tests use exactly the same conversion."""
431 conv_id = name
433 args = args[:]
435 if passbypass:
436 args.append('--passbypass')
438 if symbol_hints_file is not None:
439 args.append('--symbol-hints=%s' % (symbol_hints_file,))
441 # There are some characters that are forbidden in filenames, and
442 # there is a limit on the total length of a path to a file. So use
443 # a hash of the parameters rather than concatenating the parameters
444 # into a string.
445 if args:
446 conv_id += "-" + md5('\0'.join(args)).hexdigest()
448 # Some options-file based tests rely on knowing the paths to which
449 # the repository should be written, so we handle that option as a
450 # predictable string:
451 if options_file is not None:
452 conv_id += '--options=%s' % (options_file,)
454 return conv_id
457 class Conversion:
458 """A record of a cvs2svn conversion.
460 Fields:
462 conv_id -- the conversion id for this Conversion.
464 name -- a one-word name indicating the involved repositories.
466 dumpfile -- the name of the SVN dumpfile created by the conversion
467 (if the DUMPFILE constructor argument was used); otherwise,
468 None.
470 repos -- the path to the svn repository. Unset if DUMPFILE was
471 specified.
473 logs -- a dictionary of Log instances, as returned by parse_log().
474 Unset if DUMPFILE was specified.
476 symbols -- a dictionary of symbols used for string interpolation
477 in path names.
479 stdout -- a list of lines written by cvs2svn to stdout
481 _wc -- the basename of the svn working copy (within tmp_dir).
482 Unset if DUMPFILE was specified.
484 _wc_path -- the path to the svn working copy, if it has already
485 been created; otherwise, None. (The working copy is created
486 lazily when get_wc() is called.) Unset if DUMPFILE was
487 specified.
489 _wc_tree -- the tree built from the svn working copy, if it has
490 already been created; otherwise, None. The tree is created
491 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
492 specified.
494 _svnrepos -- the basename of the svn repository (within tmp_dir).
495 Unset if DUMPFILE was specified."""
497 # The number of the last cvs2svn pass (determined lazily by
498 # get_last_pass()).
499 last_pass = None
501 @classmethod
502 def get_last_pass(cls):
503 """Return the number of cvs2svn's last pass."""
505 if cls.last_pass is None:
506 out = run_script(cvs2svn, None, '--help-passes')
507 cls.last_pass = int(out[-1].split()[0])
508 return cls.last_pass
510 def __init__(
511 self, conv_id, name, error_re, passbypass, symbols, args,
512 options_file=None, symbol_hints_file=None, dumpfile=None,
514 self.conv_id = conv_id
515 self.name = name
516 self.symbols = symbols
517 if not os.path.isdir(tmp_dir):
518 os.mkdir(tmp_dir)
520 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
522 if dumpfile:
523 self.dumpfile = os.path.join(tmp_dir, dumpfile)
524 # Clean up from any previous invocations of this script.
525 erase(self.dumpfile)
526 else:
527 self.dumpfile = None
528 self.repos = os.path.join(tmp_dir, '%s-svnrepos' % self.conv_id)
529 self._wc = os.path.join(tmp_dir, '%s-wc' % self.conv_id)
530 self._wc_path = None
531 self._wc_tree = None
533 # Clean up from any previous invocations of this script.
534 erase(self.repos)
535 erase(self._wc)
537 args = list(args)
538 if svntest.main.svnadmin_binary != 'svnadmin':
539 args.extend([
540 '--svnadmin=%s' % (svntest.main.svnadmin_binary,),
542 if options_file:
543 self.options_file = os.path.join(cvsrepos, options_file)
544 args.extend([
545 '--options=%s' % self.options_file,
547 assert not symbol_hints_file
548 else:
549 self.options_file = None
550 args.extend([
551 '--tmpdir=%s' % tmp_dir,
554 if symbol_hints_file:
555 self.symbol_hints_file = os.path.join(cvsrepos, symbol_hints_file)
556 args.extend([
557 '--symbol-hints=%s' % self.symbol_hints_file,
560 if self.dumpfile:
561 args.extend(['--dumpfile=%s' % (self.dumpfile,)])
562 else:
563 args.extend(['-s', self.repos])
564 args.extend([cvsrepos])
566 if passbypass:
567 self.stdout = []
568 for p in range(1, self.get_last_pass() + 1):
569 self.stdout += run_script(cvs2svn, error_re, '-p', str(p), *args)
570 else:
571 self.stdout = run_script(cvs2svn, error_re, *args)
573 if self.dumpfile:
574 if not os.path.isfile(self.dumpfile):
575 raise Failure(
576 "Dumpfile not created: '%s'"
577 % os.path.join(os.getcwd(), self.dumpfile)
579 else:
580 if os.path.isdir(self.repos):
581 self.logs = parse_log(self.repos, self.symbols)
582 elif error_re is None:
583 raise Failure(
584 "Repository not created: '%s'"
585 % os.path.join(os.getcwd(), self.repos)
588 def output_found(self, pattern):
589 """Return True if PATTERN matches any line in self.stdout.
591 PATTERN is a regular expression pattern as a string.
594 pattern_re = re.compile(pattern)
596 for line in self.stdout:
597 if pattern_re.match(line):
598 # We found the pattern that we were looking for.
599 return 1
600 else:
601 return 0
603 def find_tag_log(self, tagname):
604 """Search LOGS for a log message containing 'TAGNAME' and return the
605 log in which it was found."""
606 for i in xrange(len(self.logs), 0, -1):
607 if self.logs[i].msg.find("'"+tagname+"'") != -1:
608 return self.logs[i]
609 raise ValueError("Tag %s not found in logs" % tagname)
611 def get_wc(self, *args):
612 """Return the path to the svn working copy, or a path within the WC.
614 If a working copy has not been created yet, create it now.
616 If ARGS are specified, then they should be strings that form
617 fragments of a path within the WC. They are joined using
618 os.path.join() and appended to the WC path."""
620 if self._wc_path is None:
621 run_svn('co', repos_to_url(self.repos), self._wc)
622 self._wc_path = self._wc
623 return os.path.join(self._wc_path, *args)
625 def get_wc_tree(self):
626 if self._wc_tree is None:
627 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
628 return self._wc_tree
630 def path_exists(self, *args):
631 """Return True if the specified path exists within the repository.
633 (The strings in ARGS are first joined into a path using
634 os.path.join().)"""
636 return os.path.exists(self.get_wc(*args))
638 def check_props(self, keys, checks):
639 """Helper function for checking lots of properties. For a list of
640 files in the conversion, check that the values of the properties
641 listed in KEYS agree with those listed in CHECKS. CHECKS is a
642 list of tuples: [ (filename, [value, value, ...]), ...], where the
643 values are listed in the same order as the key names are listed in
644 KEYS."""
646 for (file, values) in checks:
647 assert len(values) == len(keys)
648 props = props_for_path(self.get_wc_tree(), file)
649 for i in range(len(keys)):
650 if props.get(keys[i]) != values[i]:
651 raise Failure(
652 "File %s has property %s set to \"%s\" "
653 "(it should have been \"%s\").\n"
654 % (file, keys[i], props.get(keys[i]), values[i],)
658 class GitConversion:
659 """A record of a cvs2svn conversion.
661 Fields:
663 name -- a one-word name indicating the CVS repository to be converted.
665 stdout -- a list of lines written by cvs2svn to stdout."""
667 def __init__(self, name, error_re, args, options_file=None):
668 self.name = name
669 if not os.path.isdir(tmp_dir):
670 os.mkdir(tmp_dir)
672 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
674 args = list(args)
675 if options_file:
676 self.options_file = os.path.join(cvsrepos, options_file)
677 args.extend([
678 '--options=%s' % self.options_file,
680 else:
681 self.options_file = None
683 self.stdout = run_script(cvs2git, error_re, *args)
686 # Cache of conversions that have already been done. Keys are conv_id;
687 # values are Conversion instances.
688 already_converted = { }
690 def ensure_conversion(
691 name, error_re=None, passbypass=None,
692 trunk=None, branches=None, tags=None,
693 args=None, options_file=None, symbol_hints_file=None, dumpfile=None,
695 """Convert CVS repository NAME to Subversion, but only if it has not
696 been converted before by this invocation of this script. If it has
697 been converted before, return the Conversion object from the
698 previous invocation.
700 If no error, return a Conversion instance.
702 If ERROR_RE is a string, it is a regular expression expected to
703 match some line of stderr printed by the conversion. If there is an
704 error and ERROR_RE is not set, then raise Failure.
706 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
707 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
709 NAME is just one word. For example, 'main' would mean to convert
710 './test-data/main-cvsrepos', and after the conversion, the resulting
711 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
712 a checked out head working copy in './cvs2svn-tmp/main-wc'.
714 Any other options to pass to cvs2svn should be in ARGS, each element
715 being one option, e.g., '--trunk-only'. If the option takes an
716 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
717 are passed to cvs2svn in the order that they appear in ARGS.
719 If OPTIONS_FILE is specified, then it should be the name of a file
720 within the main directory of the cvs repository associated with this
721 test. It is passed to cvs2svn using the --options option (which
722 suppresses some other options that are incompatible with --options).
724 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
725 file within the main directory of the cvs repository associated with
726 this test. It is passed to cvs2svn using the --symbol-hints option.
728 If DUMPFILE is specified, then it is the name of a dumpfile within
729 the temporary directory to which the conversion output should be
730 written."""
732 if args is None:
733 args = []
734 else:
735 args = list(args)
737 if trunk is None:
738 trunk = 'trunk'
739 else:
740 args.append('--trunk=%s' % (trunk,))
742 if branches is None:
743 branches = 'branches'
744 else:
745 args.append('--branches=%s' % (branches,))
747 if tags is None:
748 tags = 'tags'
749 else:
750 args.append('--tags=%s' % (tags,))
752 conv_id = make_conversion_id(
753 name, args, passbypass, options_file, symbol_hints_file
756 if conv_id not in already_converted:
757 try:
758 # Run the conversion and store the result for the rest of this
759 # session:
760 already_converted[conv_id] = Conversion(
761 conv_id, name, error_re, passbypass,
762 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
763 args, options_file, symbol_hints_file, dumpfile,
765 except Failure:
766 # Remember the failure so that a future attempt to run this conversion
767 # does not bother to retry, but fails immediately.
768 already_converted[conv_id] = None
769 raise
771 conv = already_converted[conv_id]
772 if conv is None:
773 raise Failure()
774 return conv
777 class Cvs2SvnTestFunction(TestCase):
778 """A TestCase based on a naked Python function object.
780 FUNC should be a function that returns None on success and throws an
781 svntest.Failure exception on failure. It should have a brief
782 docstring describing what it does (and fulfilling certain
783 conditions). FUNC must take no arguments.
785 This class is almost identical to svntest.testcase.FunctionTestCase,
786 except that the test function does not require a sandbox and does
787 not accept any parameter (not even sandbox=None).
789 This class can be used as an annotation on a Python function.
793 def __init__(self, func):
794 # it better be a function that accepts no parameters and has a
795 # docstring on it.
796 assert isinstance(func, types.FunctionType)
798 name = func.func_name
800 assert func.func_code.co_argcount == 0, \
801 '%s must not take any arguments' % name
803 doc = func.__doc__.strip()
804 assert doc, '%s must have a docstring' % name
806 # enforce stylistic guidelines for the function docstrings:
807 # - no longer than 50 characters
808 # - should not end in a period
809 # - should not be capitalized
810 assert len(doc) <= 50, \
811 "%s's docstring must be 50 characters or less" % name
812 assert doc[-1] != '.', \
813 "%s's docstring should not end in a period" % name
814 assert doc[0].lower() == doc[0], \
815 "%s's docstring should not be capitalized" % name
817 TestCase.__init__(self, doc=doc)
818 self.func = func
820 def get_function_name(self):
821 return self.func.func_name
823 def get_sandbox_name(self):
824 return None
826 def run(self, sandbox):
827 return self.func()
830 class Cvs2HgTestFunction(Cvs2SvnTestFunction):
831 """Same as Cvs2SvnTestFunction, but for test cases that should be
832 skipped if Mercurial is not available.
834 def run(self, sandbox):
835 if not have_hg:
836 raise svntest.Skip()
837 else:
838 return self.func()
841 class Cvs2SvnTestCase(TestCase):
842 def __init__(
843 self, name, doc=None, variant=None,
844 error_re=None, passbypass=None,
845 trunk=None, branches=None, tags=None,
846 args=None,
847 options_file=None, symbol_hints_file=None, dumpfile=None,
849 self.name = name
851 if doc is None:
852 # By default, use the first line of the class docstring as the
853 # doc:
854 doc = self.__doc__.splitlines()[0]
856 if variant is not None:
857 # Modify doc to show the variant. Trim doc first if necessary
858 # to stay within the 50-character limit.
859 suffix = '...variant %s' % (variant,)
860 doc = doc[:50 - len(suffix)] + suffix
862 TestCase.__init__(self, doc=doc)
864 self.error_re = error_re
865 self.passbypass = passbypass
866 self.trunk = trunk
867 self.branches = branches
868 self.tags = tags
869 self.args = args
870 self.options_file = options_file
871 self.symbol_hints_file = symbol_hints_file
872 self.dumpfile = dumpfile
874 def ensure_conversion(self):
875 return ensure_conversion(
876 self.name,
877 error_re=self.error_re, passbypass=self.passbypass,
878 trunk=self.trunk, branches=self.branches, tags=self.tags,
879 args=self.args,
880 options_file=self.options_file,
881 symbol_hints_file=self.symbol_hints_file,
882 dumpfile=self.dumpfile,
885 def get_sandbox_name(self):
886 return None
889 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase):
890 """Test properties resulting from a conversion."""
892 def __init__(self, name, props_to_test, expected_props, **kw):
893 """Initialize an instance of Cvs2SvnPropertiesTestCase.
895 NAME is the name of the test, passed to Cvs2SvnTestCase.
896 PROPS_TO_TEST is a list of the names of svn properties that should
897 be tested. EXPECTED_PROPS is a list of tuples [(filename,
898 [value,...])], where the second item in each tuple is a list of
899 values expected for the properties listed in PROPS_TO_TEST for the
900 specified filename. If a property must *not* be set, then its
901 value should be listed as None."""
903 Cvs2SvnTestCase.__init__(self, name, **kw)
904 self.props_to_test = props_to_test
905 self.expected_props = expected_props
907 def run(self, sbox):
908 conv = self.ensure_conversion()
909 conv.check_props(self.props_to_test, self.expected_props)
912 #----------------------------------------------------------------------
913 # Tests.
914 #----------------------------------------------------------------------
917 @Cvs2SvnTestFunction
918 def show_usage():
919 "cvs2svn with no arguments shows usage"
920 out = run_script(cvs2svn, None)
921 if (len(out) > 2 and out[0].find('ERROR:') == 0
922 and out[1].find('DBM module')):
923 print 'cvs2svn cannot execute due to lack of proper DBM module.'
924 print 'Exiting without running any further tests.'
925 sys.exit(1)
926 if out[0].find('Usage:') < 0:
927 raise Failure('Basic cvs2svn invocation failed.')
930 @Cvs2SvnTestFunction
931 def cvs2svn_manpage():
932 "generate a manpage for cvs2svn"
933 out = run_script(cvs2svn, None, '--man')
936 @Cvs2SvnTestFunction
937 def cvs2git_manpage():
938 "generate a manpage for cvs2git"
939 out = run_script(cvs2git, None, '--man')
942 @XFail_deco()
943 @Cvs2HgTestFunction
944 def cvs2hg_manpage():
945 "generate a manpage for cvs2hg"
946 out = run_script(cvs2hg, None, '--man')
949 @Cvs2SvnTestFunction
950 def show_help_passes():
951 "cvs2svn --help-passes shows pass information"
952 out = run_script(cvs2svn, None, '--help-passes')
953 if out[0].find('PASSES') < 0:
954 raise Failure('cvs2svn --help-passes failed.')
957 @Cvs2SvnTestFunction
958 def attr_exec():
959 "detection of the executable flag"
960 if sys.platform == 'win32':
961 raise svntest.Skip()
962 conv = ensure_conversion('main')
963 st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec'))
964 if not st.st_mode & stat.S_IXUSR:
965 raise Failure()
968 @Cvs2SvnTestFunction
969 def space_fname():
970 "conversion of filename with a space"
971 conv = ensure_conversion('main')
972 if not conv.path_exists('trunk', 'single-files', 'space fname'):
973 raise Failure()
976 @Cvs2SvnTestFunction
977 def two_quick():
978 "two commits in quick succession"
979 conv = ensure_conversion('main')
980 logs = parse_log(
981 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
982 if len(logs) != 2:
983 raise Failure()
986 class PruneWithCare(Cvs2SvnTestCase):
987 "prune, but never too much"
989 def __init__(self, **kw):
990 Cvs2SvnTestCase.__init__(self, 'main', **kw)
992 def run(self, sbox):
993 # Robert Pluim encountered this lovely one while converting the
994 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
995 # repository (see issue #1302). Step 4 is the doozy:
997 # revision 1: adds trunk/blah/, adds trunk/blah/first
998 # revision 2: adds trunk/blah/second
999 # revision 3: deletes trunk/blah/first
1000 # revision 4: deletes blah [re-deleting trunk/blah/first pruned blah!]
1001 # revision 5: does nothing
1003 # After fixing cvs2svn, the sequence (correctly) looks like this:
1005 # revision 1: adds trunk/blah/, adds trunk/blah/first
1006 # revision 2: adds trunk/blah/second
1007 # revision 3: deletes trunk/blah/first
1008 # revision 4: does nothing [because trunk/blah/first already deleted]
1009 # revision 5: deletes blah
1011 # The difference is in 4 and 5. In revision 4, it's not correct
1012 # to prune blah/, because second is still in there, so revision 4
1013 # does nothing now. But when we delete second in 5, that should
1014 # bubble up and prune blah/ instead.
1016 # ### Note that empty revisions like 4 are probably going to become
1017 # ### at least optional, if not banished entirely from cvs2svn's
1018 # ### output. Hmmm, or they may stick around, with an extra
1019 # ### revision property explaining what happened. Need to think
1020 # ### about that. In some sense, it's a bug in Subversion itself,
1021 # ### that such revisions don't show up in 'svn log' output.
1023 conv = self.ensure_conversion()
1025 # Confirm that revision 4 removes '/trunk/full-prune/first',
1026 # and that revision 6 removes '/trunk/full-prune'.
1028 # Also confirm similar things about '/full-prune-reappear/...',
1029 # which is similar, except that later on it reappears, restored
1030 # from pruneland, because a file gets added to it.
1032 # And finally, a similar thing for '/partial-prune/...', except that
1033 # in its case, a permanent file on the top level prevents the
1034 # pruning from going farther than the subdirectory containing first
1035 # and second.
1037 for path in ('full-prune/first',
1038 'full-prune-reappear/sub/first',
1039 'partial-prune/sub/first'):
1040 conv.logs[5].check_change('/%(trunk)s/' + path, 'D')
1042 for path in ('full-prune',
1043 'full-prune-reappear',
1044 'partial-prune/sub'):
1045 conv.logs[7].check_change('/%(trunk)s/' + path, 'D')
1047 for path in ('full-prune-reappear',
1048 'full-prune-reappear/appears-later'):
1049 conv.logs[33].check_change('/%(trunk)s/' + path, 'A')
1052 @Cvs2SvnTestFunction
1053 def interleaved_commits():
1054 "two interleaved trunk commits, different log msgs"
1055 # See test-data/main-cvsrepos/proj/README.
1056 conv = ensure_conversion('main')
1058 # The initial import.
1059 rev = 26
1060 conv.logs[rev].check('Initial import.', (
1061 ('/%(trunk)s/interleaved', 'A'),
1062 ('/%(trunk)s/interleaved/1', 'A'),
1063 ('/%(trunk)s/interleaved/2', 'A'),
1064 ('/%(trunk)s/interleaved/3', 'A'),
1065 ('/%(trunk)s/interleaved/4', 'A'),
1066 ('/%(trunk)s/interleaved/5', 'A'),
1067 ('/%(trunk)s/interleaved/a', 'A'),
1068 ('/%(trunk)s/interleaved/b', 'A'),
1069 ('/%(trunk)s/interleaved/c', 'A'),
1070 ('/%(trunk)s/interleaved/d', 'A'),
1071 ('/%(trunk)s/interleaved/e', 'A'),
1074 def check_letters(rev):
1075 """Check if REV is the rev where only letters were committed."""
1077 conv.logs[rev].check('Committing letters only.', (
1078 ('/%(trunk)s/interleaved/a', 'M'),
1079 ('/%(trunk)s/interleaved/b', 'M'),
1080 ('/%(trunk)s/interleaved/c', 'M'),
1081 ('/%(trunk)s/interleaved/d', 'M'),
1082 ('/%(trunk)s/interleaved/e', 'M'),
1085 def check_numbers(rev):
1086 """Check if REV is the rev where only numbers were committed."""
1088 conv.logs[rev].check('Committing numbers only.', (
1089 ('/%(trunk)s/interleaved/1', 'M'),
1090 ('/%(trunk)s/interleaved/2', 'M'),
1091 ('/%(trunk)s/interleaved/3', 'M'),
1092 ('/%(trunk)s/interleaved/4', 'M'),
1093 ('/%(trunk)s/interleaved/5', 'M'),
1096 # One of the commits was letters only, the other was numbers only.
1097 # But they happened "simultaneously", so we don't assume anything
1098 # about which commit appeared first, so we just try both ways.
1099 rev += 1
1100 try:
1101 check_letters(rev)
1102 check_numbers(rev + 1)
1103 except Failure:
1104 check_numbers(rev)
1105 check_letters(rev + 1)
1108 @Cvs2SvnTestFunction
1109 def simple_commits():
1110 "simple trunk commits"
1111 # See test-data/main-cvsrepos/proj/README.
1112 conv = ensure_conversion('main')
1114 # The initial import.
1115 conv.logs[13].check('Initial import.', (
1116 ('/%(trunk)s/proj', 'A'),
1117 ('/%(trunk)s/proj/default', 'A'),
1118 ('/%(trunk)s/proj/sub1', 'A'),
1119 ('/%(trunk)s/proj/sub1/default', 'A'),
1120 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1121 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1122 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1123 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1124 ('/%(trunk)s/proj/sub2', 'A'),
1125 ('/%(trunk)s/proj/sub2/default', 'A'),
1126 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1127 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1128 ('/%(trunk)s/proj/sub3', 'A'),
1129 ('/%(trunk)s/proj/sub3/default', 'A'),
1132 # The first commit.
1133 conv.logs[18].check('First commit to proj, affecting two files.', (
1134 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1135 ('/%(trunk)s/proj/sub3/default', 'M'),
1138 # The second commit.
1139 conv.logs[19].check('Second commit to proj, affecting all 7 files.', (
1140 ('/%(trunk)s/proj/default', 'M'),
1141 ('/%(trunk)s/proj/sub1/default', 'M'),
1142 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1143 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1144 ('/%(trunk)s/proj/sub2/default', 'M'),
1145 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1146 ('/%(trunk)s/proj/sub3/default', 'M')
1150 class SimpleTags(Cvs2SvnTestCase):
1151 "simple tags and branches, no commits"
1153 def __init__(self, **kw):
1154 # See test-data/main-cvsrepos/proj/README.
1155 Cvs2SvnTestCase.__init__(self, 'main', **kw)
1157 def run(self, sbox):
1158 conv = self.ensure_conversion()
1160 # Verify the copy source for the tags we are about to check
1161 # No need to verify the copyfrom revision, as simple_commits did that
1162 conv.logs[13].check('Initial import.', (
1163 ('/%(trunk)s/proj', 'A'),
1164 ('/%(trunk)s/proj/default', 'A'),
1165 ('/%(trunk)s/proj/sub1', 'A'),
1166 ('/%(trunk)s/proj/sub1/default', 'A'),
1167 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1168 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1169 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1170 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1171 ('/%(trunk)s/proj/sub2', 'A'),
1172 ('/%(trunk)s/proj/sub2/default', 'A'),
1173 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1174 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1175 ('/%(trunk)s/proj/sub3', 'A'),
1176 ('/%(trunk)s/proj/sub3/default', 'A'),
1179 # Tag on rev 1.1.1.1 of all files in proj
1180 conv.logs[16].check(sym_log_msg('B_FROM_INITIALS'), (
1181 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1182 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1183 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1186 # The same, as a tag
1187 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
1188 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1189 ('/%(tags)s/T_ALL_INITIAL_FILES (from /%(trunk)s:13)', 'A'),
1190 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
1191 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
1194 # Tag on rev 1.1.1.1 of all files in proj, except one
1195 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1196 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1197 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE (from /%(trunk)s:13)', 'A'),
1198 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
1199 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
1200 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1203 # The same, as a branch
1204 conv.logs[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1205 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE (from /%(trunk)s:13)', 'A'),
1206 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1207 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
1208 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
1212 @Cvs2SvnTestFunction
1213 def simple_branch_commits():
1214 "simple branch commits"
1215 # See test-data/main-cvsrepos/proj/README.
1216 conv = ensure_conversion('main')
1218 conv.logs[23].check('Modify three files, on branch B_MIXED.', (
1219 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1220 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1221 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1225 @Cvs2SvnTestFunction
1226 def mixed_time_tag():
1227 "mixed-time tag"
1228 # See test-data/main-cvsrepos/proj/README.
1229 conv = ensure_conversion('main')
1231 log = conv.find_tag_log('T_MIXED')
1232 log.check_changes((
1233 ('/%(tags)s/T_MIXED (from /%(trunk)s:19)', 'A'),
1234 ('/%(tags)s/T_MIXED/single-files', 'D'),
1235 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
1236 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
1237 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1238 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1242 @Cvs2SvnTestFunction
1243 def mixed_time_branch_with_added_file():
1244 "mixed-time branch, and a file added to the branch"
1245 # See test-data/main-cvsrepos/proj/README.
1246 conv = ensure_conversion('main')
1248 # A branch from the same place as T_MIXED in the previous test,
1249 # plus a file added directly to the branch
1250 conv.logs[21].check(sym_log_msg('B_MIXED'), (
1251 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1252 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1253 ('/%(branches)s/B_MIXED/single-files', 'D'),
1254 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1255 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1256 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1259 conv.logs[22].check('Add a file on branch B_MIXED.', (
1260 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1264 @Cvs2SvnTestFunction
1265 def mixed_commit():
1266 "a commit affecting both trunk and a branch"
1267 # See test-data/main-cvsrepos/proj/README.
1268 conv = ensure_conversion('main')
1270 conv.logs[24].check(
1271 'A single commit affecting one file on branch B_MIXED '
1272 'and one on trunk.', (
1273 ('/%(trunk)s/proj/sub2/default', 'M'),
1274 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1278 @Cvs2SvnTestFunction
1279 def split_time_branch():
1280 "branch some trunk files, and later branch the rest"
1281 # See test-data/main-cvsrepos/proj/README.
1282 conv = ensure_conversion('main')
1284 # First change on the branch, creating it
1285 conv.logs[25].check(sym_log_msg('B_SPLIT'), (
1286 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1287 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1288 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1289 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1292 conv.logs[29].check('First change on branch B_SPLIT.', (
1293 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1294 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1295 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1296 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1297 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1300 # A trunk commit for the file which was not branched
1301 conv.logs[30].check('A trunk change to sub1/subsubB/default. '
1302 'This was committed about an', (
1303 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1306 # Add the file not already branched to the branch, with modification:w
1307 conv.logs[31].check(sym_log_msg('B_SPLIT'), (
1308 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1309 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1312 conv.logs[32].check('This change affects sub3/default and '
1313 'sub1/subsubB/default, on branch', (
1314 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1315 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1319 @Cvs2SvnTestFunction
1320 def multiple_tags():
1321 "multiple tags referring to same revision"
1322 conv = ensure_conversion('main')
1323 if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1324 raise Failure()
1325 if not conv.path_exists(
1326 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1327 raise Failure()
1330 @Cvs2SvnTestFunction
1331 def multiply_defined_symbols():
1332 "multiple definitions of symbol names"
1334 # We can only check one line of the error output at a time, so test
1335 # twice. (The conversion only have to be done once because the
1336 # results are cached.)
1337 conv = ensure_conversion(
1338 'multiply-defined-symbols',
1339 error_re=(
1340 r"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1341 r"1\.2\.4 1\.2\.2"
1344 conv = ensure_conversion(
1345 'multiply-defined-symbols',
1346 error_re=(
1347 r"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1348 r"1\.2 1\.1"
1353 @Cvs2SvnTestFunction
1354 def multiply_defined_symbols_renamed():
1355 "rename multiply defined symbols"
1357 conv = ensure_conversion(
1358 'multiply-defined-symbols',
1359 options_file='cvs2svn-rename.options',
1363 @Cvs2SvnTestFunction
1364 def multiply_defined_symbols_ignored():
1365 "ignore multiply defined symbols"
1367 conv = ensure_conversion(
1368 'multiply-defined-symbols',
1369 options_file='cvs2svn-ignore.options',
1373 @Cvs2SvnTestFunction
1374 def repeatedly_defined_symbols():
1375 "multiple identical definitions of symbol names"
1377 # If a symbol is defined multiple times but has the same value each
1378 # time, that should not be an error.
1380 conv = ensure_conversion('repeatedly-defined-symbols')
1383 @Cvs2SvnTestFunction
1384 def bogus_tag():
1385 "conversion of invalid symbolic names"
1386 conv = ensure_conversion('bogus-tag')
1389 @Cvs2SvnTestFunction
1390 def overlapping_branch():
1391 "ignore a file with a branch with two names"
1392 conv = ensure_conversion('overlapping-branch')
1394 if not conv.output_found('.*cannot also have name \'vendorB\''):
1395 raise Failure()
1397 conv.logs[2].check('imported', (
1398 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1399 ('/%(trunk)s/overlapping-branch', 'A'),
1402 if len(conv.logs) != 2:
1403 raise Failure()
1406 class PhoenixBranch(Cvs2SvnTestCase):
1407 "convert a branch file rooted in a 'dead' revision"
1409 def __init__(self, **kw):
1410 Cvs2SvnTestCase.__init__(self, 'phoenix', **kw)
1412 def run(self, sbox):
1413 conv = self.ensure_conversion()
1414 conv.logs[8].check('This file was supplied by Jack Moffitt', (
1415 ('/%(branches)s/volsung_20010721', 'A'),
1416 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1418 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1419 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1423 ###TODO: We check for 4 changed paths here to accomodate creating tags
1424 ###and branches in rev 1, but that will change, so this will
1425 ###eventually change back.
1426 @Cvs2SvnTestFunction
1427 def ctrl_char_in_log():
1428 "handle a control char in a log message"
1429 # This was issue #1106.
1430 rev = 2
1431 conv = ensure_conversion('ctrl-char-in-log')
1432 conv.logs[rev].check_changes((
1433 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1435 if conv.logs[rev].msg.find('\x04') < 0:
1436 raise Failure(
1437 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1440 @Cvs2SvnTestFunction
1441 def overdead():
1442 "handle tags rooted in a redeleted revision"
1443 conv = ensure_conversion('overdead')
1446 class NoTrunkPrune(Cvs2SvnTestCase):
1447 "ensure that trunk doesn't get pruned"
1449 def __init__(self, **kw):
1450 Cvs2SvnTestCase.__init__(self, 'overdead', **kw)
1452 def run(self, sbox):
1453 conv = self.ensure_conversion()
1454 for rev in conv.logs.keys():
1455 rev_logs = conv.logs[rev]
1456 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1457 raise Failure()
1460 @Cvs2SvnTestFunction
1461 def double_delete():
1462 "file deleted twice, in the root of the repository"
1463 # This really tests several things: how we handle a file that's
1464 # removed (state 'dead') in two successive revisions; how we
1465 # handle a file in the root of the repository (there were some
1466 # bugs in cvs2svn's svn path construction for top-level files); and
1467 # the --no-prune option.
1468 conv = ensure_conversion(
1469 'double-delete', args=['--trunk-only', '--no-prune'])
1471 path = '/%(trunk)s/twice-removed'
1472 rev = 2
1473 conv.logs[rev].check('Updated CVS', (
1474 (path, 'A'),
1476 conv.logs[rev + 1].check('Remove this file for the first time.', (
1477 (path, 'D'),
1479 conv.logs[rev + 2].check('Remove this file for the second time,', (
1483 @Cvs2SvnTestFunction
1484 def split_branch():
1485 "branch created from both trunk and another branch"
1486 # See test-data/split-branch-cvsrepos/README.
1488 # The conversion will fail if the bug is present, and
1489 # ensure_conversion will raise Failure.
1490 conv = ensure_conversion('split-branch')
1493 @Cvs2SvnTestFunction
1494 def resync_misgroups():
1495 "resyncing should not misorder commit groups"
1496 # See test-data/resync-misgroups-cvsrepos/README.
1498 # The conversion will fail if the bug is present, and
1499 # ensure_conversion will raise Failure.
1500 conv = ensure_conversion('resync-misgroups')
1503 class TaggedBranchAndTrunk(Cvs2SvnTestCase):
1504 "allow tags with mixed trunk and branch sources"
1506 def __init__(self, **kw):
1507 Cvs2SvnTestCase.__init__(self, 'tagged-branch-n-trunk', **kw)
1509 def run(self, sbox):
1510 conv = self.ensure_conversion()
1512 tags = conv.symbols.get('tags', 'tags')
1514 a_path = conv.get_wc(tags, 'some-tag', 'a.txt')
1515 b_path = conv.get_wc(tags, 'some-tag', 'b.txt')
1516 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1517 raise Failure()
1518 if (open(a_path, 'r').read().find('1.24') == -1) \
1519 or (open(b_path, 'r').read().find('1.5') == -1):
1520 raise Failure()
1523 @Cvs2SvnTestFunction
1524 def enroot_race():
1525 "never use the rev-in-progress as a copy source"
1527 # See issue #1427 and r8544.
1528 conv = ensure_conversion('enroot-race')
1529 rev = 6
1530 conv.logs[rev].check_changes((
1531 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1532 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1533 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1535 conv.logs[rev + 1].check_changes((
1536 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1537 ('/%(trunk)s/proj/a.txt', 'M'),
1538 ('/%(trunk)s/proj/b.txt', 'M'),
1542 @Cvs2SvnTestFunction
1543 def enroot_race_obo():
1544 "do use the last completed rev as a copy source"
1545 conv = ensure_conversion('enroot-race-obo')
1546 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1547 if not len(conv.logs) == 3:
1548 raise Failure()
1551 class BranchDeleteFirst(Cvs2SvnTestCase):
1552 "correctly handle deletion as initial branch action"
1554 def __init__(self, **kw):
1555 Cvs2SvnTestCase.__init__(self, 'branch-delete-first', **kw)
1557 def run(self, sbox):
1558 # See test-data/branch-delete-first-cvsrepos/README.
1560 # The conversion will fail if the bug is present, and
1561 # ensure_conversion would raise Failure.
1562 conv = self.ensure_conversion()
1564 branches = conv.symbols.get('branches', 'branches')
1566 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1567 if conv.path_exists(branches, 'branch-1', 'file'):
1568 raise Failure()
1569 if conv.path_exists(branches, 'branch-2', 'file'):
1570 raise Failure()
1571 if not conv.path_exists(branches, 'branch-3', 'file'):
1572 raise Failure()
1575 @Cvs2SvnTestFunction
1576 def nonascii_cvsignore():
1577 "non ascii files in .cvsignore"
1579 # The output seems to be in the C locale, where it looks like this
1580 # (at least on one test system):
1581 expected = (
1582 'Sp?\\195?\\164tzle\n'
1583 'Cr?\\195?\\168meBr?\\195?\\187l?\\195?\\169e\n'
1584 'Jam?\\195?\\179nIb?\\195?\\169rico\n'
1585 'Am?\\195?\\170ijoas?\\195?\\128Bulh?\\195?\\163oPato\n'
1588 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1589 props = props_for_path(conv.get_wc_tree(), 'trunk/single-files')
1591 if props['svn:ignore'] != expected:
1592 raise Failure()
1595 @Cvs2SvnTestFunction
1596 def nonascii_filenames():
1597 "non ascii files converted incorrectly"
1598 # see issue #1255
1600 # on a en_US.iso-8859-1 machine this test fails with
1601 # svn: Can't recode ...
1603 # as described in the issue
1605 # on a en_US.UTF-8 machine this test fails with
1606 # svn: Malformed XML ...
1608 # which means at least it fails. Unfortunately it won't fail
1609 # with the same error...
1611 # mangle current locale settings so we know we're not running
1612 # a UTF-8 locale (which does not exhibit this problem)
1613 current_locale = locale.getlocale()
1614 new_locale = 'en_US.ISO8859-1'
1615 locale_changed = None
1617 # From http://docs.python.org/lib/module-sys.html
1619 # getfilesystemencoding():
1621 # Return the name of the encoding used to convert Unicode filenames
1622 # into system file names, or None if the system default encoding is
1623 # used. The result value depends on the operating system:
1625 # - On Windows 9x, the encoding is ``mbcs''.
1626 # - On Mac OS X, the encoding is ``utf-8''.
1627 # - On Unix, the encoding is the user's preference according to the
1628 # result of nl_langinfo(CODESET), or None if the
1629 # nl_langinfo(CODESET) failed.
1630 # - On Windows NT+, file names are Unicode natively, so no conversion is
1631 # performed.
1633 # So we're going to skip this test on Mac OS X for now.
1634 if sys.platform == "darwin":
1635 raise svntest.Skip()
1637 try:
1638 # change locale to non-UTF-8 locale to generate latin1 names
1639 locale.setlocale(locale.LC_ALL, # this might be too broad?
1640 new_locale)
1641 locale_changed = 1
1642 except locale.Error:
1643 raise svntest.Skip()
1645 try:
1646 srcrepos_path = os.path.join(test_data_dir, 'non-ascii-cvsrepos')
1647 dstrepos_path = os.path.join(test_data_dir, 'non-ascii-copy-cvsrepos')
1648 if not os.path.exists(dstrepos_path):
1649 # create repos from existing main repos
1650 shutil.copytree(srcrepos_path, dstrepos_path)
1651 base_path = os.path.join(dstrepos_path, 'single-files')
1652 os.remove(os.path.join(base_path, '.cvsignore,v'))
1653 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1654 os.path.join(base_path, 'two\366uick,v'))
1655 new_path = os.path.join(dstrepos_path, 'single\366files')
1656 os.rename(base_path, new_path)
1658 conv = ensure_conversion('non-ascii-copy', args=['--encoding=latin1'])
1659 finally:
1660 if locale_changed:
1661 locale.setlocale(locale.LC_ALL, current_locale)
1662 safe_rmtree(dstrepos_path)
1665 class UnicodeTest(Cvs2SvnTestCase):
1666 "metadata contains Unicode"
1668 warning_pattern = r'ERROR\: There were warnings converting .* messages'
1670 def __init__(self, name, warning_expected, **kw):
1671 if warning_expected:
1672 error_re = self.warning_pattern
1673 else:
1674 error_re = None
1676 Cvs2SvnTestCase.__init__(self, name, error_re=error_re, **kw)
1677 self.warning_expected = warning_expected
1679 def run(self, sbox):
1680 try:
1681 # ensure the availability of the "utf_8" encoding:
1682 u'a'.encode('utf_8').decode('utf_8')
1683 except LookupError:
1684 raise svntest.Skip()
1686 self.ensure_conversion()
1689 class UnicodeAuthor(UnicodeTest):
1690 "author name contains Unicode"
1692 def __init__(self, warning_expected, **kw):
1693 UnicodeTest.__init__(self, 'unicode-author', warning_expected, **kw)
1696 class UnicodeLog(UnicodeTest):
1697 "log message contains Unicode"
1699 def __init__(self, warning_expected, **kw):
1700 UnicodeTest.__init__(self, 'unicode-log', warning_expected, **kw)
1703 @Cvs2SvnTestFunction
1704 def vendor_branch_sameness():
1705 "avoid spurious changes for initial revs"
1706 conv = ensure_conversion(
1707 'vendor-branch-sameness', args=['--keep-trivial-imports']
1710 # The following files are in this repository:
1712 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1713 # the same contents, the file's default branch is 1.1.1,
1714 # and both revisions are in state 'Exp'.
1716 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1717 # 1.1 (the addition of a line of text).
1719 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1721 # d.txt: This file was created by 'cvs add' instead of import, so
1722 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1723 # The timestamp on the add is exactly the same as for the
1724 # imports of the other files.
1726 # e.txt: Like a.txt, except that the log message for revision 1.1
1727 # is not the standard import log message.
1729 # (Aside from e.txt, the log messages for the same revisions are the
1730 # same in all files.)
1732 # We expect that only a.txt is recognized as an import whose 1.1
1733 # revision can be omitted. The other files should be added on trunk
1734 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1735 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1736 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1737 # and c.txt should be 'D'eleted.
1739 rev = 2
1740 conv.logs[rev].check('Initial revision', (
1741 ('/%(trunk)s/proj', 'A'),
1742 ('/%(trunk)s/proj/b.txt', 'A'),
1743 ('/%(trunk)s/proj/c.txt', 'A'),
1744 ('/%(trunk)s/proj/d.txt', 'A'),
1747 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1748 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1749 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1752 conv.logs[rev + 2].check('First vendor branch revision.', (
1753 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1754 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1755 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1758 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1759 'to compensate for changes in r4,', (
1760 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1761 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1762 ('/%(trunk)s/proj/c.txt', 'D'),
1765 rev = 7
1766 conv.logs[rev].check('This log message is not the standard', (
1767 ('/%(trunk)s/proj/e.txt', 'A'),
1770 conv.logs[rev + 2].check('First vendor branch revision', (
1771 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1774 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1775 'to compensate for changes in r9,', (
1776 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1780 @Cvs2SvnTestFunction
1781 def vendor_branch_trunk_only():
1782 "handle vendor branches with --trunk-only"
1783 conv = ensure_conversion('vendor-branch-sameness', args=['--trunk-only'])
1785 rev = 2
1786 conv.logs[rev].check('Initial revision', (
1787 ('/%(trunk)s/proj', 'A'),
1788 ('/%(trunk)s/proj/b.txt', 'A'),
1789 ('/%(trunk)s/proj/c.txt', 'A'),
1790 ('/%(trunk)s/proj/d.txt', 'A'),
1793 conv.logs[rev + 1].check('First vendor branch revision', (
1794 ('/%(trunk)s/proj/a.txt', 'A'),
1795 ('/%(trunk)s/proj/b.txt', 'M'),
1796 ('/%(trunk)s/proj/c.txt', 'D'),
1799 conv.logs[rev + 2].check('This log message is not the standard', (
1800 ('/%(trunk)s/proj/e.txt', 'A'),
1803 conv.logs[rev + 3].check('First vendor branch revision', (
1804 ('/%(trunk)s/proj/e.txt', 'M'),
1808 @Cvs2SvnTestFunction
1809 def default_branches():
1810 "handle default branches correctly"
1811 conv = ensure_conversion('default-branches')
1813 # There are seven files in the repository:
1815 # a.txt:
1816 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1817 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1818 # committed (thus losing the default branch "1.1.1"), then
1819 # 1.1.1.4 was imported. All vendor import release tags are
1820 # still present.
1822 # b.txt:
1823 # Like a.txt, but without rev 1.2.
1825 # c.txt:
1826 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1828 # d.txt:
1829 # Same as the previous two, but 1.1.1 branch is unlabeled.
1831 # e.txt:
1832 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1834 # deleted-on-vendor-branch.txt,v:
1835 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1837 # added-then-imported.txt,v:
1838 # Added with 'cvs add' to create 1.1, then imported with
1839 # completely different contents to create 1.1.1.1, therefore
1840 # never had a default branch.
1843 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1844 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1845 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1846 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1847 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1848 ('/%(branches)s/vbranchA', 'A'),
1849 ('/%(branches)s/vbranchA/proj', 'A'),
1850 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1851 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1852 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1853 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1856 conv.logs[3].check("This commit was generated by cvs2svn "
1857 "to compensate for changes in r2,", (
1858 ('/%(trunk)s/proj', 'A'),
1859 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1860 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1861 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1862 ('/%(trunk)s/proj/d.txt '
1863 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1864 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1865 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1866 ('/%(trunk)s/proj/e.txt '
1867 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1870 conv.logs[4].check(sym_log_msg('vtag-1',1), (
1871 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1872 ('/%(tags)s/vtag-1/proj/d.txt '
1873 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1876 conv.logs[5].check("Import (vbranchA, vtag-2).", (
1877 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1878 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1879 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1880 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1881 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1882 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1885 conv.logs[6].check("This commit was generated by cvs2svn "
1886 "to compensate for changes in r5,", (
1887 ('/%(trunk)s/proj/a.txt '
1888 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1889 ('/%(trunk)s/proj/b.txt '
1890 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1891 ('/%(trunk)s/proj/c.txt '
1892 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1893 ('/%(trunk)s/proj/d.txt '
1894 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1895 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1896 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1897 'R'),
1898 ('/%(trunk)s/proj/e.txt '
1899 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1902 conv.logs[7].check(sym_log_msg('vtag-2',1), (
1903 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1904 ('/%(tags)s/vtag-2/proj/d.txt '
1905 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1908 conv.logs[8].check("Import (vbranchA, vtag-3).", (
1909 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1910 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1911 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1912 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1913 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1914 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1917 conv.logs[9].check("This commit was generated by cvs2svn "
1918 "to compensate for changes in r8,", (
1919 ('/%(trunk)s/proj/a.txt '
1920 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1921 ('/%(trunk)s/proj/b.txt '
1922 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1923 ('/%(trunk)s/proj/c.txt '
1924 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1925 ('/%(trunk)s/proj/d.txt '
1926 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1927 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1928 ('/%(trunk)s/proj/e.txt '
1929 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1932 conv.logs[10].check(sym_log_msg('vtag-3',1), (
1933 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1934 ('/%(tags)s/vtag-3/proj/d.txt '
1935 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1936 ('/%(tags)s/vtag-3/proj/e.txt '
1937 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1940 conv.logs[11].check("First regular commit, to a.txt, on vtag-3.", (
1941 ('/%(trunk)s/proj/a.txt', 'M'),
1944 conv.logs[12].check("Add a file to the working copy.", (
1945 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1948 conv.logs[13].check(sym_log_msg('vbranchA'), (
1949 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1950 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1953 conv.logs[14].check("Import (vbranchA, vtag-4).", (
1954 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1955 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1956 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1957 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1958 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1959 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1960 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1963 conv.logs[15].check("This commit was generated by cvs2svn "
1964 "to compensate for changes in r14,", (
1965 ('/%(trunk)s/proj/b.txt '
1966 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1967 ('/%(trunk)s/proj/c.txt '
1968 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1969 ('/%(trunk)s/proj/d.txt '
1970 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1971 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1972 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1973 'A'),
1974 ('/%(trunk)s/proj/e.txt '
1975 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1978 conv.logs[16].check(sym_log_msg('vtag-4',1), (
1979 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1980 ('/%(tags)s/vtag-4/proj/d.txt '
1981 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
1985 @Cvs2SvnTestFunction
1986 def default_branches_trunk_only():
1987 "handle default branches with --trunk-only"
1989 conv = ensure_conversion('default-branches', args=['--trunk-only'])
1991 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1992 ('/%(trunk)s/proj', 'A'),
1993 ('/%(trunk)s/proj/a.txt', 'A'),
1994 ('/%(trunk)s/proj/b.txt', 'A'),
1995 ('/%(trunk)s/proj/c.txt', 'A'),
1996 ('/%(trunk)s/proj/d.txt', 'A'),
1997 ('/%(trunk)s/proj/e.txt', 'A'),
1998 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2001 conv.logs[3].check("Import (vbranchA, vtag-2).", (
2002 ('/%(trunk)s/proj/a.txt', 'M'),
2003 ('/%(trunk)s/proj/b.txt', 'M'),
2004 ('/%(trunk)s/proj/c.txt', 'M'),
2005 ('/%(trunk)s/proj/d.txt', 'M'),
2006 ('/%(trunk)s/proj/e.txt', 'M'),
2007 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
2010 conv.logs[4].check("Import (vbranchA, vtag-3).", (
2011 ('/%(trunk)s/proj/a.txt', 'M'),
2012 ('/%(trunk)s/proj/b.txt', 'M'),
2013 ('/%(trunk)s/proj/c.txt', 'M'),
2014 ('/%(trunk)s/proj/d.txt', 'M'),
2015 ('/%(trunk)s/proj/e.txt', 'M'),
2016 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
2019 conv.logs[5].check("First regular commit, to a.txt, on vtag-3.", (
2020 ('/%(trunk)s/proj/a.txt', 'M'),
2023 conv.logs[6].check("Add a file to the working copy.", (
2024 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
2027 conv.logs[7].check("Import (vbranchA, vtag-4).", (
2028 ('/%(trunk)s/proj/b.txt', 'M'),
2029 ('/%(trunk)s/proj/c.txt', 'M'),
2030 ('/%(trunk)s/proj/d.txt', 'M'),
2031 ('/%(trunk)s/proj/e.txt', 'M'),
2032 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2036 @Cvs2SvnTestFunction
2037 def default_branch_and_1_2():
2038 "do not allow 1.2 revision with default branch"
2040 conv = ensure_conversion(
2041 'default-branch-and-1-2',
2042 error_re=(
2043 r'.*File \'.*\' has default branch=1\.1\.1 but also a revision 1\.2'
2048 @Cvs2SvnTestFunction
2049 def compose_tag_three_sources():
2050 "compose a tag from three sources"
2051 conv = ensure_conversion('compose-tag-three-sources')
2053 conv.logs[2].check("Add on trunk", (
2054 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
2055 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
2056 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
2057 ('/%(trunk)s/tagged-on-b1', 'A'),
2058 ('/%(trunk)s/tagged-on-b2', 'A'),
2061 conv.logs[3].check(sym_log_msg('b1'), (
2062 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
2065 conv.logs[4].check(sym_log_msg('b2'), (
2066 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
2069 conv.logs[5].check("Commit on branch b1", (
2070 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
2071 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
2072 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
2073 ('/%(branches)s/b1/tagged-on-b1', 'M'),
2074 ('/%(branches)s/b1/tagged-on-b2', 'M'),
2077 conv.logs[6].check("Commit on branch b2", (
2078 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
2079 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
2080 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
2081 ('/%(branches)s/b2/tagged-on-b1', 'M'),
2082 ('/%(branches)s/b2/tagged-on-b2', 'M'),
2085 conv.logs[7].check("Commit again on trunk", (
2086 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
2087 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
2088 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
2089 ('/%(trunk)s/tagged-on-b1', 'M'),
2090 ('/%(trunk)s/tagged-on-b2', 'M'),
2093 conv.logs[8].check(sym_log_msg('T',1), (
2094 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
2095 ('/%(tags)s/T/tagged-on-trunk-1.1 '
2096 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
2097 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:5)', 'R'),
2098 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:6)', 'R'),
2102 @Cvs2SvnTestFunction
2103 def pass5_when_to_fill():
2104 "reserve a svn revnum for a fill only when required"
2105 # The conversion will fail if the bug is present, and
2106 # ensure_conversion would raise Failure.
2107 conv = ensure_conversion('pass5-when-to-fill')
2110 class EmptyTrunk(Cvs2SvnTestCase):
2111 "don't break when the trunk is empty"
2113 def __init__(self, **kw):
2114 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
2116 def run(self, sbox):
2117 # The conversion will fail if the bug is present, and
2118 # ensure_conversion would raise Failure.
2119 conv = self.ensure_conversion()
2122 @Cvs2SvnTestFunction
2123 def no_spurious_svn_commits():
2124 "ensure that we don't create any spurious commits"
2125 conv = ensure_conversion('phoenix')
2127 # Check spurious commit that could be created in
2128 # SVNCommitCreator._pre_commit()
2130 # (When you add a file on a branch, CVS creates a trunk revision
2131 # in state 'dead'. If the log message of that commit is equal to
2132 # the one that CVS generates, we do not ever create a 'fill'
2133 # SVNCommit for it.)
2135 # and spurious commit that could be created in
2136 # SVNCommitCreator._commit()
2138 # (When you add a file on a branch, CVS creates a trunk revision
2139 # in state 'dead'. If the log message of that commit is equal to
2140 # the one that CVS generates, we do not create a primary SVNCommit
2141 # for it.)
2142 conv.logs[17].check('File added on branch xiphophorus', (
2143 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
2146 # Check to make sure that a commit *is* generated:
2147 # (When you add a file on a branch, CVS creates a trunk revision
2148 # in state 'dead'. If the log message of that commit is NOT equal
2149 # to the one that CVS generates, we create a primary SVNCommit to
2150 # serve as a home for the log message in question.
2151 conv.logs[18].check('file added-on-branch2.txt was initially added on '
2152 + 'branch xiphophorus,\nand this log message was tweaked', ())
2154 # Check spurious commit that could be created in
2155 # SVNCommitCreator._commit_symbols().
2156 conv.logs[19].check('This file was also added on branch xiphophorus,', (
2157 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
2161 class PeerPathPruning(Cvs2SvnTestCase):
2162 "make sure that filling prunes paths correctly"
2164 def __init__(self, **kw):
2165 Cvs2SvnTestCase.__init__(self, 'peer-path-pruning', **kw)
2167 def run(self, sbox):
2168 conv = self.ensure_conversion()
2169 conv.logs[6].check(sym_log_msg('BRANCH'), (
2170 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A'),
2171 ('/%(branches)s/BRANCH/bar', 'D'),
2172 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:5)', 'R'),
2176 @Cvs2SvnTestFunction
2177 def invalid_closings_on_trunk():
2178 "verify correct revs are copied to default branches"
2179 # The conversion will fail if the bug is present, and
2180 # ensure_conversion would raise Failure.
2181 conv = ensure_conversion('invalid-closings-on-trunk')
2184 @Cvs2SvnTestFunction
2185 def individual_passes():
2186 "run each pass individually"
2187 conv = ensure_conversion('main')
2188 conv2 = ensure_conversion('main', passbypass=1)
2190 if conv.logs != conv2.logs:
2191 raise Failure()
2194 @Cvs2SvnTestFunction
2195 def resync_bug():
2196 "reveal a big bug in our resync algorithm"
2197 # This will fail if the bug is present
2198 conv = ensure_conversion('resync-bug')
2201 @Cvs2SvnTestFunction
2202 def branch_from_default_branch():
2203 "reveal a bug in our default branch detection code"
2204 conv = ensure_conversion('branch-from-default-branch')
2206 # This revision will be a default branch synchronization only
2207 # if cvs2svn is correctly determining default branch revisions.
2209 # The bug was that cvs2svn was treating revisions on branches off of
2210 # default branches as default branch revisions, resulting in
2211 # incorrectly regarding the branch off of the default branch as a
2212 # non-trunk default branch. Crystal clear? I thought so. See
2213 # issue #42 for more incoherent blathering.
2214 conv.logs[5].check("This commit was generated by cvs2svn", (
2215 ('/%(trunk)s/proj/file.txt '
2216 '(from /%(branches)s/upstream/proj/file.txt:4)', 'R'),
2220 @Cvs2SvnTestFunction
2221 def file_in_attic_too():
2222 "die if a file exists in and out of the attic"
2223 ensure_conversion(
2224 'file-in-attic-too',
2225 error_re=(
2226 r'.*A CVS repository cannot contain both '
2227 r'(.*)' + re.escape(os.sep) + r'(.*) '
2228 + r'and '
2229 r'\1' + re.escape(os.sep) + r'Attic' + re.escape(os.sep) + r'\2'
2234 @Cvs2SvnTestFunction
2235 def retain_file_in_attic_too():
2236 "test --retain-conflicting-attic-files option"
2237 conv = ensure_conversion(
2238 'file-in-attic-too', args=['--retain-conflicting-attic-files'])
2239 if not conv.path_exists('trunk', 'file.txt'):
2240 raise Failure()
2241 if not conv.path_exists('trunk', 'Attic', 'file.txt'):
2242 raise Failure()
2245 @Cvs2SvnTestFunction
2246 def symbolic_name_filling_guide():
2247 "reveal a big bug in our SymbolFillingGuide"
2248 # This will fail if the bug is present
2249 conv = ensure_conversion('symbolic-name-overfill')
2252 # Helpers for tests involving file contents and properties.
2254 class NodeTreeWalkException:
2255 "Exception class for node tree traversals."
2256 pass
2258 def node_for_path(node, path):
2259 "In the tree rooted under SVNTree NODE, return the node at PATH."
2260 if node.name != '__SVN_ROOT_NODE':
2261 raise NodeTreeWalkException()
2262 path = path.strip('/')
2263 components = path.split('/')
2264 for component in components:
2265 node = svntest.tree.get_child(node, component)
2266 return node
2268 # Helper for tests involving properties.
2269 def props_for_path(node, path):
2270 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2271 return node_for_path(node, path).props
2274 class EOLMime(Cvs2SvnPropertiesTestCase):
2275 """eol settings and mime types together
2277 The files are as follows:
2279 trunk/foo.txt: no -kb, mime file says nothing.
2280 trunk/foo.xml: no -kb, mime file says text.
2281 trunk/foo.zip: no -kb, mime file says non-text.
2282 trunk/foo.bin: has -kb, mime file says nothing.
2283 trunk/foo.csv: has -kb, mime file says text.
2284 trunk/foo.dbf: has -kb, mime file says non-text.
2287 def __init__(self, args, **kw):
2288 # TODO: It's a bit klugey to construct this path here. But so far
2289 # there's only one test with a mime.types file. If we have more,
2290 # we should abstract this into some helper, which would be located
2291 # near ensure_conversion(). Note that it is a convention of this
2292 # test suite for a mime.types file to be located in the top level
2293 # of the CVS repository to which it applies.
2294 self.mime_path = os.path.join(
2295 test_data_dir, 'eol-mime-cvsrepos', 'mime.types')
2297 Cvs2SvnPropertiesTestCase.__init__(
2298 self, 'eol-mime',
2299 props_to_test=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2300 args=['--mime-types=%s' % self.mime_path] + args,
2301 **kw)
2304 # We do four conversions. Each time, we pass --mime-types=FILE with
2305 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2306 # Thus there's one conversion with neither flag, one with just the
2307 # former, one with just the latter, and one with both.
2310 # Neither --no-default-eol nor --eol-from-mime-type:
2311 eol_mime1 = EOLMime(
2312 variant=1,
2313 args=[],
2314 expected_props=[
2315 ('trunk/foo.txt', [None, None, None]),
2316 ('trunk/foo.xml', [None, 'text/xml', None]),
2317 ('trunk/foo.zip', [None, 'application/zip', None]),
2318 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2319 ('trunk/foo.csv', [None, 'text/csv', None]),
2320 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2324 # Just --no-default-eol, not --eol-from-mime-type:
2325 eol_mime2 = EOLMime(
2326 variant=2,
2327 args=['--default-eol=native'],
2328 expected_props=[
2329 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2330 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2331 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS]),
2332 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2333 ('trunk/foo.csv', [None, 'text/csv', None]),
2334 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2338 # Just --eol-from-mime-type, not --no-default-eol:
2339 eol_mime3 = EOLMime(
2340 variant=3,
2341 args=['--eol-from-mime-type'],
2342 expected_props=[
2343 ('trunk/foo.txt', [None, None, None]),
2344 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2345 ('trunk/foo.zip', [None, 'application/zip', None]),
2346 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2347 ('trunk/foo.csv', [None, 'text/csv', None]),
2348 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2352 # Both --no-default-eol and --eol-from-mime-type:
2353 eol_mime4 = EOLMime(
2354 variant=4,
2355 args=['--eol-from-mime-type', '--default-eol=native'],
2356 expected_props=[
2357 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2358 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2359 ('trunk/foo.zip', [None, 'application/zip', None]),
2360 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2361 ('trunk/foo.csv', [None, 'text/csv', None]),
2362 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2366 cvs_revnums_off = Cvs2SvnPropertiesTestCase(
2367 'eol-mime',
2368 doc='test non-setting of cvs2svn:cvs-rev property',
2369 args=[],
2370 props_to_test=['cvs2svn:cvs-rev'],
2371 expected_props=[
2372 ('trunk/foo.txt', [None]),
2373 ('trunk/foo.xml', [None]),
2374 ('trunk/foo.zip', [None]),
2375 ('trunk/foo.bin', [None]),
2376 ('trunk/foo.csv', [None]),
2377 ('trunk/foo.dbf', [None]),
2381 cvs_revnums_on = Cvs2SvnPropertiesTestCase(
2382 'eol-mime',
2383 doc='test setting of cvs2svn:cvs-rev property',
2384 args=['--cvs-revnums'],
2385 props_to_test=['cvs2svn:cvs-rev'],
2386 expected_props=[
2387 ('trunk/foo.txt', ['1.2']),
2388 ('trunk/foo.xml', ['1.2']),
2389 ('trunk/foo.zip', ['1.2']),
2390 ('trunk/foo.bin', ['1.2']),
2391 ('trunk/foo.csv', ['1.2']),
2392 ('trunk/foo.dbf', ['1.2']),
2396 keywords = Cvs2SvnPropertiesTestCase(
2397 'keywords',
2398 doc='test setting of svn:keywords property among others',
2399 args=['--default-eol=native'],
2400 props_to_test=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2401 expected_props=[
2402 ('trunk/foo.default', [KEYWORDS, 'native', None]),
2403 ('trunk/foo.kkvl', [KEYWORDS, 'native', None]),
2404 ('trunk/foo.kkv', [KEYWORDS, 'native', None]),
2405 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2406 ('trunk/foo.kk', [None, 'native', None]),
2407 ('trunk/foo.ko', [None, 'native', None]),
2408 ('trunk/foo.kv', [None, 'native', None]),
2412 @Cvs2SvnTestFunction
2413 def ignore():
2414 "test setting of svn:ignore property"
2415 conv = ensure_conversion('cvsignore')
2416 wc_tree = conv.get_wc_tree()
2417 topdir_props = props_for_path(wc_tree, 'trunk/proj')
2418 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
2420 if topdir_props['svn:ignore'] != \
2421 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2422 raise Failure()
2424 if subdir_props['svn:ignore'] != \
2425 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2426 raise Failure()
2429 @Cvs2SvnTestFunction
2430 def requires_cvs():
2431 "test that CVS can still do what RCS can't"
2432 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2433 conv = ensure_conversion(
2434 'requires-cvs', args=['--use-cvs', '--default-eol=native'],
2437 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
2438 cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read()
2440 if atsign_contents[-1:] == "@":
2441 raise Failure()
2442 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
2443 raise Failure()
2445 if not (conv.logs[6].author == "William Lyon Phelps III" and
2446 conv.logs[5].author == "j random"):
2447 raise Failure()
2450 @Cvs2SvnTestFunction
2451 def questionable_branch_names():
2452 "test that we can handle weird branch names"
2453 conv = ensure_conversion('questionable-symbols')
2454 # If the conversion succeeds, then we're okay. We could check the
2455 # actual branch paths, too, but the main thing is to know that the
2456 # conversion doesn't fail.
2459 @Cvs2SvnTestFunction
2460 def questionable_tag_names():
2461 "test that we can handle weird tag names"
2462 conv = ensure_conversion('questionable-symbols')
2463 conv.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2464 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2466 conv.find_tag_log('TagWith/Backslash_E').check(
2467 sym_log_msg('TagWith/Backslash_E',1),
2469 ('/%(tags)s/TagWith', 'A'),
2470 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2473 conv.find_tag_log('TagWith/Slash_Z').check(
2474 sym_log_msg('TagWith/Slash_Z',1),
2476 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2481 @Cvs2SvnTestFunction
2482 def revision_reorder_bug():
2483 "reveal a bug that reorders file revisions"
2484 conv = ensure_conversion('revision-reorder-bug')
2485 # If the conversion succeeds, then we're okay. We could check the
2486 # actual revisions, too, but the main thing is to know that the
2487 # conversion doesn't fail.
2490 @Cvs2SvnTestFunction
2491 def exclude():
2492 "test that exclude really excludes everything"
2493 conv = ensure_conversion('main', args=['--exclude=.*'])
2494 for log in conv.logs.values():
2495 for item in log.changed_paths.keys():
2496 if item.startswith('/branches/') or item.startswith('/tags/'):
2497 raise Failure()
2500 @Cvs2SvnTestFunction
2501 def vendor_branch_delete_add():
2502 "add trunk file that was deleted on vendor branch"
2503 # This will error if the bug is present
2504 conv = ensure_conversion('vendor-branch-delete-add')
2507 @Cvs2SvnTestFunction
2508 def resync_pass2_pull_forward():
2509 "ensure pass2 doesn't pull rev too far forward"
2510 conv = ensure_conversion('resync-pass2-pull-forward')
2511 # If the conversion succeeds, then we're okay. We could check the
2512 # actual revisions, too, but the main thing is to know that the
2513 # conversion doesn't fail.
2516 @Cvs2SvnTestFunction
2517 def native_eol():
2518 "only LFs for svn:eol-style=native files"
2519 conv = ensure_conversion('native-eol', args=['--default-eol=native'])
2520 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
2521 conv.repos)
2522 # Verify that all files in the dump have LF EOLs. We're actually
2523 # testing the whole dump file, but the dump file itself only uses
2524 # LF EOLs, so we're safe.
2525 for line in lines:
2526 if line[-1] != '\n' or line[:-1].find('\r') != -1:
2527 raise Failure()
2530 @Cvs2SvnTestFunction
2531 def double_fill():
2532 "reveal a bug that created a branch twice"
2533 conv = ensure_conversion('double-fill')
2534 # If the conversion succeeds, then we're okay. We could check the
2535 # actual revisions, too, but the main thing is to know that the
2536 # conversion doesn't fail.
2539 @XFail_deco()
2540 @Cvs2SvnTestFunction
2541 def double_fill2():
2542 "reveal a second bug that created a branch twice"
2543 conv = ensure_conversion('double-fill2')
2544 conv.logs[6].check_msg(sym_log_msg('BRANCH1'))
2545 conv.logs[7].check_msg(sym_log_msg('BRANCH2'))
2546 try:
2547 # This check should fail:
2548 conv.logs[8].check_msg(sym_log_msg('BRANCH2'))
2549 except Failure:
2550 pass
2551 else:
2552 raise Failure('Symbol filled twice in a row')
2555 @Cvs2SvnTestFunction
2556 def resync_pass2_push_backward():
2557 "ensure pass2 doesn't push rev too far backward"
2558 conv = ensure_conversion('resync-pass2-push-backward')
2559 # If the conversion succeeds, then we're okay. We could check the
2560 # actual revisions, too, but the main thing is to know that the
2561 # conversion doesn't fail.
2564 @Cvs2SvnTestFunction
2565 def double_add():
2566 "reveal a bug that added a branch file twice"
2567 conv = ensure_conversion('double-add')
2568 # If the conversion succeeds, then we're okay. We could check the
2569 # actual revisions, too, but the main thing is to know that the
2570 # conversion doesn't fail.
2573 @Cvs2SvnTestFunction
2574 def bogus_branch_copy():
2575 "reveal a bug that copies a branch file wrongly"
2576 conv = ensure_conversion('bogus-branch-copy')
2577 # If the conversion succeeds, then we're okay. We could check the
2578 # actual revisions, too, but the main thing is to know that the
2579 # conversion doesn't fail.
2582 @Cvs2SvnTestFunction
2583 def nested_ttb_directories():
2584 "require error if ttb directories are not disjoint"
2585 opts_list = [
2586 {'trunk' : 'a', 'branches' : 'a',},
2587 {'trunk' : 'a', 'tags' : 'a',},
2588 {'branches' : 'a', 'tags' : 'a',},
2589 # This option conflicts with the default trunk path:
2590 {'branches' : 'trunk',},
2591 # Try some nested directories:
2592 {'trunk' : 'a', 'branches' : 'a/b',},
2593 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2594 {'branches' : 'a', 'tags' : 'a/b',},
2597 for opts in opts_list:
2598 ensure_conversion(
2599 'main', error_re=r'The following paths are not disjoint\:', **opts
2603 class AutoProps(Cvs2SvnPropertiesTestCase):
2604 """Test auto-props.
2606 The files are as follows:
2608 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2609 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2610 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2611 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2612 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2613 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2614 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2615 trunk/foo.UPCASE1: no -kb, no mime type.
2616 trunk/foo.UPCASE2: no -kb, no mime type.
2619 def __init__(self, args, **kw):
2620 ### TODO: It's a bit klugey to construct this path here. See also
2621 ### the comment in eol_mime().
2622 auto_props_path = os.path.join(
2623 test_data_dir, 'eol-mime-cvsrepos', 'auto-props')
2625 Cvs2SvnPropertiesTestCase.__init__(
2626 self, 'eol-mime',
2627 props_to_test=[
2628 'myprop',
2629 'svn:eol-style',
2630 'svn:mime-type',
2631 'svn:keywords',
2632 'svn:executable',
2634 args=[
2635 '--auto-props=%s' % auto_props_path,
2636 '--eol-from-mime-type'
2637 ] + args,
2638 **kw)
2641 auto_props_ignore_case = AutoProps(
2642 doc="test auto-props",
2643 args=['--default-eol=native'],
2644 expected_props=[
2645 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS, None]),
2646 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS, None]),
2647 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2648 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2649 ('trunk/foo.bin',
2650 ['bin', None, 'application/octet-stream', None, '']),
2651 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2652 ('trunk/foo.dbf',
2653 ['dbf', None, 'application/what-is-dbf', None, None]),
2654 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS, None]),
2655 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS, None]),
2659 @Cvs2SvnTestFunction
2660 def ctrl_char_in_filename():
2661 "do not allow control characters in filenames"
2663 try:
2664 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
2665 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
2666 if os.path.exists(dstrepos_path):
2667 safe_rmtree(dstrepos_path)
2669 # create repos from existing main repos
2670 shutil.copytree(srcrepos_path, dstrepos_path)
2671 base_path = os.path.join(dstrepos_path, 'single-files')
2672 try:
2673 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2674 os.path.join(base_path, 'two\rquick,v'))
2675 except:
2676 # Operating systems that don't allow control characters in
2677 # filenames will hopefully have thrown an exception; in that
2678 # case, just skip this test.
2679 raise svntest.Skip()
2681 conv = ensure_conversion(
2682 'ctrl-char-filename',
2683 error_re=(r'.*Subversion does not allow character .*.'),
2685 finally:
2686 safe_rmtree(dstrepos_path)
2689 @Cvs2SvnTestFunction
2690 def commit_dependencies():
2691 "interleaved and multi-branch commits to same files"
2692 conv = ensure_conversion("commit-dependencies")
2693 conv.logs[2].check('adding', (
2694 ('/%(trunk)s/interleaved', 'A'),
2695 ('/%(trunk)s/interleaved/file1', 'A'),
2696 ('/%(trunk)s/interleaved/file2', 'A'),
2698 conv.logs[3].check('big commit', (
2699 ('/%(trunk)s/interleaved/file1', 'M'),
2700 ('/%(trunk)s/interleaved/file2', 'M'),
2702 conv.logs[4].check('dependant small commit', (
2703 ('/%(trunk)s/interleaved/file1', 'M'),
2705 conv.logs[5].check('adding', (
2706 ('/%(trunk)s/multi-branch', 'A'),
2707 ('/%(trunk)s/multi-branch/file1', 'A'),
2708 ('/%(trunk)s/multi-branch/file2', 'A'),
2710 conv.logs[6].check(sym_log_msg("branch"), (
2711 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2712 ('/%(branches)s/branch/interleaved', 'D'),
2714 conv.logs[7].check('multi-branch-commit', (
2715 ('/%(trunk)s/multi-branch/file1', 'M'),
2716 ('/%(trunk)s/multi-branch/file2', 'M'),
2717 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2718 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2722 @Cvs2SvnTestFunction
2723 def double_branch_delete():
2724 "fill branches before modifying files on them"
2725 conv = ensure_conversion('double-branch-delete')
2727 # Test for issue #102. The file IMarshalledValue.java is branched,
2728 # deleted, readded on the branch, and then deleted again. If the
2729 # fill for the file on the branch is postponed until after the
2730 # modification, the file will end up live on the branch instead of
2731 # dead! Make sure it happens at the right time.
2733 conv.logs[6].check('JBAS-2436 - Adding LGPL Header2', (
2734 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2737 conv.logs[7].check('JBAS-3025 - Removing dependency', (
2738 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2742 @Cvs2SvnTestFunction
2743 def symbol_mismatches():
2744 "error for conflicting tag/branch"
2746 ensure_conversion(
2747 'symbol-mess',
2748 args=['--symbol-default=strict'],
2749 error_re=r'.*Problems determining how symbols should be converted',
2753 @Cvs2SvnTestFunction
2754 def overlook_symbol_mismatches():
2755 "overlook conflicting tag/branch when --trunk-only"
2757 # This is a test for issue #85.
2759 ensure_conversion('symbol-mess', args=['--trunk-only'])
2762 @Cvs2SvnTestFunction
2763 def force_symbols():
2764 "force symbols to be tags/branches"
2766 conv = ensure_conversion(
2767 'symbol-mess',
2768 args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2769 if conv.path_exists('tags', 'BRANCH') \
2770 or not conv.path_exists('branches', 'BRANCH'):
2771 raise Failure()
2772 if not conv.path_exists('tags', 'TAG') \
2773 or conv.path_exists('branches', 'TAG'):
2774 raise Failure()
2775 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2776 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2777 raise Failure()
2778 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2779 or conv.path_exists('branches', 'MOSTLY_TAG'):
2780 raise Failure()
2783 @Cvs2SvnTestFunction
2784 def commit_blocks_tags():
2785 "commit prevents forced tag"
2787 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2788 ensure_conversion(
2789 'symbol-mess',
2790 args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']),
2791 error_re=(
2792 r'.*The following branches cannot be forced to be tags '
2793 r'because they have commits'
2798 @Cvs2SvnTestFunction
2799 def blocked_excludes():
2800 "error for blocked excludes"
2802 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2803 for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']:
2804 try:
2805 ensure_conversion(
2806 'symbol-mess',
2807 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker]))
2808 raise MissingErrorException()
2809 except Failure:
2810 pass
2813 @Cvs2SvnTestFunction
2814 def unblock_blocked_excludes():
2815 "excluding blocker removes blockage"
2817 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2818 for blocker in ['BRANCH', 'COMMIT']:
2819 ensure_conversion(
2820 'symbol-mess',
2821 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker,
2822 '--exclude=BLOCKING_%s' % blocker]))
2825 @Cvs2SvnTestFunction
2826 def regexp_force_symbols():
2827 "force symbols via regular expressions"
2829 conv = ensure_conversion(
2830 'symbol-mess',
2831 args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2832 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2833 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2834 raise Failure()
2835 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2836 or conv.path_exists('branches', 'MOSTLY_TAG'):
2837 raise Failure()
2840 @Cvs2SvnTestFunction
2841 def heuristic_symbol_default():
2842 "test 'heuristic' symbol default"
2844 conv = ensure_conversion(
2845 'symbol-mess', args=['--symbol-default=heuristic'])
2846 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2847 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2848 raise Failure()
2849 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2850 or conv.path_exists('branches', 'MOSTLY_TAG'):
2851 raise Failure()
2854 @Cvs2SvnTestFunction
2855 def branch_symbol_default():
2856 "test 'branch' symbol default"
2858 conv = ensure_conversion(
2859 'symbol-mess', args=['--symbol-default=branch'])
2860 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2861 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2862 raise Failure()
2863 if conv.path_exists('tags', 'MOSTLY_TAG') \
2864 or not conv.path_exists('branches', 'MOSTLY_TAG'):
2865 raise Failure()
2868 @Cvs2SvnTestFunction
2869 def tag_symbol_default():
2870 "test 'tag' symbol default"
2872 conv = ensure_conversion(
2873 'symbol-mess', args=['--symbol-default=tag'])
2874 if not conv.path_exists('tags', 'MOSTLY_BRANCH') \
2875 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
2876 raise Failure()
2877 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2878 or conv.path_exists('branches', 'MOSTLY_TAG'):
2879 raise Failure()
2882 @Cvs2SvnTestFunction
2883 def symbol_transform():
2884 "test --symbol-transform"
2886 conv = ensure_conversion(
2887 'symbol-mess',
2888 args=[
2889 '--symbol-default=heuristic',
2890 '--symbol-transform=BRANCH:branch',
2891 '--symbol-transform=TAG:tag',
2892 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2894 if not conv.path_exists('branches', 'branch'):
2895 raise Failure()
2896 if not conv.path_exists('tags', 'tag'):
2897 raise Failure()
2898 if not conv.path_exists('branches', 'MOSTLY.BRANCH'):
2899 raise Failure()
2900 if not conv.path_exists('tags', 'MOSTLY.TAG'):
2901 raise Failure()
2904 @Cvs2SvnTestFunction
2905 def write_symbol_info():
2906 "test --write-symbol-info"
2908 expected_lines = [
2909 ['0', '.trunk.',
2910 'trunk', 'trunk', '.'],
2911 ['0', 'BLOCKED_BY_UNNAMED',
2912 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2913 ['0', 'BLOCKING_COMMIT',
2914 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2915 ['0', 'BLOCKED_BY_COMMIT',
2916 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2917 ['0', 'BLOCKING_BRANCH',
2918 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2919 ['0', 'BLOCKED_BY_BRANCH',
2920 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2921 ['0', 'MOSTLY_BRANCH',
2922 '.', '.', '.'],
2923 ['0', 'MOSTLY_TAG',
2924 '.', '.', '.'],
2925 ['0', 'BRANCH_WITH_COMMIT',
2926 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2927 ['0', 'BRANCH',
2928 'branch', 'branches/BRANCH', '.trunk.'],
2929 ['0', 'TAG',
2930 'tag', 'tags/TAG', '.trunk.'],
2931 ['0', 'unlabeled-1.1.12.1.2',
2932 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2934 expected_lines.sort()
2936 symbol_info_file = os.path.join(tmp_dir, 'symbol-mess-symbol-info.txt')
2937 try:
2938 ensure_conversion(
2939 'symbol-mess',
2940 args=[
2941 '--symbol-default=strict',
2942 '--write-symbol-info=%s' % (symbol_info_file,),
2943 '--passes=:CollateSymbolsPass',
2946 raise MissingErrorException()
2947 except Failure:
2948 pass
2949 lines = []
2950 comment_re = re.compile(r'^\s*\#')
2951 for l in open(symbol_info_file, 'r'):
2952 if comment_re.match(l):
2953 continue
2954 lines.append(l.strip().split())
2955 lines.sort()
2956 if lines != expected_lines:
2957 s = ['Symbol info incorrect\n']
2958 differ = Differ()
2959 for diffline in differ.compare(
2960 [' '.join(line) + '\n' for line in expected_lines],
2961 [' '.join(line) + '\n' for line in lines],
2963 s.append(diffline)
2964 raise Failure(''.join(s))
2967 @Cvs2SvnTestFunction
2968 def symbol_hints():
2969 "test --symbol-hints for setting branch/tag"
2971 conv = ensure_conversion(
2972 'symbol-mess', symbol_hints_file='symbol-mess-symbol-hints.txt',
2974 if not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2975 raise Failure()
2976 if not conv.path_exists('tags', 'MOSTLY_TAG'):
2977 raise Failure()
2978 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2979 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2981 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2982 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
2984 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2985 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
2989 @Cvs2SvnTestFunction
2990 def parent_hints():
2991 "test --symbol-hints for setting parent"
2993 conv = ensure_conversion(
2994 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints.txt',
2996 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2997 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3001 @Cvs2SvnTestFunction
3002 def parent_hints_invalid():
3003 "test --symbol-hints with an invalid parent"
3005 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3006 # this symbol hints file sets the preferred parent to BRANCH
3007 # instead:
3008 conv = ensure_conversion(
3009 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-invalid.txt',
3010 error_re=(
3011 r"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
3016 @Cvs2SvnTestFunction
3017 def parent_hints_wildcards():
3018 "test --symbol-hints wildcards"
3020 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3021 # this symbol hints file sets the preferred parent to BRANCH
3022 # instead:
3023 conv = ensure_conversion(
3024 'symbol-mess',
3025 symbol_hints_file='symbol-mess-parent-hints-wildcards.txt',
3027 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3028 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3032 @Cvs2SvnTestFunction
3033 def path_hints():
3034 "test --symbol-hints for setting svn paths"
3036 conv = ensure_conversion(
3037 'symbol-mess', symbol_hints_file='symbol-mess-path-hints.txt',
3039 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3040 ('/trunk', 'A'),
3041 ('/a', 'A'),
3042 ('/a/strange', 'A'),
3043 ('/a/strange/trunk', 'A'),
3044 ('/a/strange/trunk/path', 'A'),
3045 ('/branches', 'A'),
3046 ('/tags', 'A'),
3048 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3049 ('/special', 'A'),
3050 ('/special/tag', 'A'),
3051 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
3053 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3054 ('/special/other', 'A'),
3055 ('/special/other/branch', 'A'),
3056 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
3058 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3059 ('/special/branch', 'A'),
3060 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
3064 @Cvs2SvnTestFunction
3065 def issue_99():
3066 "test problem from issue 99"
3068 conv = ensure_conversion('issue-99')
3071 @Cvs2SvnTestFunction
3072 def issue_100():
3073 "test problem from issue 100"
3075 conv = ensure_conversion('issue-100')
3076 file1 = conv.get_wc('trunk', 'file1.txt')
3077 if file(file1).read() != 'file1.txt<1.2>\n':
3078 raise Failure()
3081 @Cvs2SvnTestFunction
3082 def issue_106():
3083 "test problem from issue 106"
3085 conv = ensure_conversion('issue-106')
3088 @Cvs2SvnTestFunction
3089 def options_option():
3090 "use of the --options option"
3092 conv = ensure_conversion('main', options_file='cvs2svn.options')
3095 @Cvs2SvnTestFunction
3096 def multiproject():
3097 "multiproject conversion"
3099 conv = ensure_conversion(
3100 'main', options_file='cvs2svn-multiproject.options'
3102 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3103 ('/partial-prune', 'A'),
3104 ('/partial-prune/trunk', 'A'),
3105 ('/partial-prune/branches', 'A'),
3106 ('/partial-prune/tags', 'A'),
3107 ('/partial-prune/releases', 'A'),
3111 @Cvs2SvnTestFunction
3112 def crossproject():
3113 "multiproject conversion with cross-project commits"
3115 conv = ensure_conversion(
3116 'main', options_file='cvs2svn-crossproject.options'
3120 @Cvs2SvnTestFunction
3121 def tag_with_no_revision():
3122 "tag defined but revision is deleted"
3124 conv = ensure_conversion('tag-with-no-revision')
3127 @Cvs2SvnTestFunction
3128 def delete_cvsignore():
3129 "svn:ignore should vanish when .cvsignore does"
3131 # This is issue #81.
3133 conv = ensure_conversion('delete-cvsignore')
3135 wc_tree = conv.get_wc_tree()
3136 props = props_for_path(wc_tree, 'trunk/proj')
3138 if props.has_key('svn:ignore'):
3139 raise Failure()
3142 @Cvs2SvnTestFunction
3143 def repeated_deltatext():
3144 "ignore repeated deltatext blocks with warning"
3146 conv = ensure_conversion('repeated-deltatext')
3147 warning_re = r'.*Deltatext block for revision 1.1 appeared twice'
3148 if not conv.output_found(warning_re):
3149 raise Failure()
3152 @Cvs2SvnTestFunction
3153 def nasty_graphs():
3154 "process some nasty dependency graphs"
3156 # It's not how well the bear can dance, but that the bear can dance
3157 # at all:
3158 conv = ensure_conversion('nasty-graphs')
3161 @XFail_deco()
3162 @Cvs2SvnTestFunction
3163 def tagging_after_delete():
3164 "optimal tag after deleting files"
3166 conv = ensure_conversion('tagging-after-delete')
3168 # tag should be 'clean', no deletes
3169 log = conv.find_tag_log('tag1')
3170 expected = (
3171 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
3173 log.check_changes(expected)
3176 @Cvs2SvnTestFunction
3177 def crossed_branches():
3178 "branches created in inconsistent orders"
3180 conv = ensure_conversion('crossed-branches')
3183 @Cvs2SvnTestFunction
3184 def file_directory_conflict():
3185 "error when filename conflicts with directory name"
3187 conv = ensure_conversion(
3188 'file-directory-conflict',
3189 error_re=r'.*Directory name conflicts with filename',
3193 @Cvs2SvnTestFunction
3194 def attic_directory_conflict():
3195 "error when attic filename conflicts with dirname"
3197 # This tests the problem reported in issue #105.
3199 conv = ensure_conversion(
3200 'attic-directory-conflict',
3201 error_re=r'.*Directory name conflicts with filename',
3205 @Cvs2SvnTestFunction
3206 def use_rcs():
3207 "verify that --use-rcs and --use-internal-co agree"
3209 rcs_conv = ensure_conversion(
3210 'main', args=['--use-rcs', '--default-eol=native'], dumpfile='use-rcs-rcs.dump',
3212 conv = ensure_conversion(
3213 'main', args=['--default-eol=native'], dumpfile='use-rcs-int.dump',
3215 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3216 raise Failure()
3217 rcs_lines = list(open(rcs_conv.dumpfile, 'rb'))
3218 lines = list(open(conv.dumpfile, 'rb'))
3219 # Compare all lines following the repository UUID:
3220 if lines[3:] != rcs_lines[3:]:
3221 raise Failure()
3224 @Cvs2SvnTestFunction
3225 def internal_co_exclude():
3226 "verify that --use-internal-co --exclude=... works"
3228 rcs_conv = ensure_conversion(
3229 'internal-co',
3230 args=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3231 dumpfile='internal-co-exclude-rcs.dump',
3233 conv = ensure_conversion(
3234 'internal-co',
3235 args=['--exclude=BRANCH', '--default-eol=native'],
3236 dumpfile='internal-co-exclude-int.dump',
3238 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3239 raise Failure()
3240 rcs_lines = list(open(rcs_conv.dumpfile, 'rb'))
3241 lines = list(open(conv.dumpfile, 'rb'))
3242 # Compare all lines following the repository UUID:
3243 if lines[3:] != rcs_lines[3:]:
3244 raise Failure()
3247 @Cvs2SvnTestFunction
3248 def internal_co_trunk_only():
3249 "verify that --use-internal-co --trunk-only works"
3251 conv = ensure_conversion(
3252 'internal-co',
3253 args=['--trunk-only', '--default-eol=native'],
3255 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3256 raise Failure()
3259 @Cvs2SvnTestFunction
3260 def leftover_revs():
3261 "check for leftover checked-out revisions"
3263 conv = ensure_conversion(
3264 'leftover-revs',
3265 args=['--exclude=BRANCH', '--default-eol=native'],
3267 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3268 raise Failure()
3271 @Cvs2SvnTestFunction
3272 def requires_internal_co():
3273 "test that internal co can do more than RCS"
3274 # See issues 4, 11 for the bugs whose regression we're testing for.
3275 # Unlike in requires_cvs above, issue 29 is not covered.
3276 conv = ensure_conversion('requires-cvs')
3278 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
3280 if atsign_contents[-1:] == "@":
3281 raise Failure()
3283 if not (conv.logs[6].author == "William Lyon Phelps III" and
3284 conv.logs[5].author == "j random"):
3285 raise Failure()
3288 @Cvs2SvnTestFunction
3289 def internal_co_keywords():
3290 "test that internal co handles keywords correctly"
3291 conv_ic = ensure_conversion('internal-co-keywords',
3292 args=["--keywords-off"])
3293 conv_cvs = ensure_conversion('internal-co-keywords',
3294 args=["--use-cvs", "--keywords-off"])
3296 ko_ic = file(conv_ic.get_wc('trunk', 'dir', 'ko.txt')).read()
3297 ko_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'ko.txt')).read()
3298 kk_ic = file(conv_ic.get_wc('trunk', 'dir', 'kk.txt')).read()
3299 kk_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kk.txt')).read()
3300 kv_ic = file(conv_ic.get_wc('trunk', 'dir', 'kv.txt')).read()
3301 kv_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kv.txt')).read()
3302 # Ensure proper "/Attic" expansion of $Source$ keyword in files
3303 # which are in a deleted state in trunk
3304 del_ic = file(conv_ic.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3305 del_cvs = file(conv_cvs.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3308 if ko_ic != ko_cvs:
3309 raise Failure()
3310 if kk_ic != kk_cvs:
3311 raise Failure()
3312 if del_ic != del_cvs:
3313 raise Failure()
3315 # The date format changed between cvs and co ('/' instead of '-').
3316 # Accept either one:
3317 date_substitution_re = re.compile(r' ([0-9]*)-([0-9]*)-([0-9]*) ')
3318 if kv_ic != kv_cvs \
3319 and date_substitution_re.sub(r' \1/\2/\3 ', kv_ic) != kv_cvs:
3320 raise Failure()
3323 @Cvs2SvnTestFunction
3324 def timestamp_chaos():
3325 "test timestamp adjustments"
3327 conv = ensure_conversion('timestamp-chaos', args=["-v"])
3329 # The times are expressed here in UTC:
3330 times = [
3331 '2007-01-01 21:00:00', # Initial commit
3332 '2007-01-01 21:00:00', # revision 1.1 of both files
3333 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3334 '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards
3335 '2007-01-01 22:00:00', # revision 1.3 of both files
3338 # Convert the times to seconds since the epoch, in UTC:
3339 times = [calendar.timegm(svn_strptime(t)) for t in times]
3341 for i in range(len(times)):
3342 if abs(conv.logs[i + 1].date - times[i]) > 0.1:
3343 raise Failure()
3346 @Cvs2SvnTestFunction
3347 def symlinks():
3348 "convert a repository that contains symlinks"
3350 # This is a test for issue #97.
3352 proj = os.path.join(test_data_dir, 'symlinks-cvsrepos', 'proj')
3353 links = [
3355 os.path.join('..', 'file.txt,v'),
3356 os.path.join(proj, 'dir1', 'file.txt,v'),
3359 'dir1',
3360 os.path.join(proj, 'dir2'),
3364 try:
3365 os.symlink
3366 except AttributeError:
3367 # Apparently this OS doesn't support symlinks, so skip test.
3368 raise svntest.Skip()
3370 try:
3371 for (src,dst) in links:
3372 os.symlink(src, dst)
3374 conv = ensure_conversion('symlinks')
3375 conv.logs[2].check('', (
3376 ('/%(trunk)s/proj', 'A'),
3377 ('/%(trunk)s/proj/file.txt', 'A'),
3378 ('/%(trunk)s/proj/dir1', 'A'),
3379 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3380 ('/%(trunk)s/proj/dir2', 'A'),
3381 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3383 finally:
3384 for (src,dst) in links:
3385 os.remove(dst)
3388 @Cvs2SvnTestFunction
3389 def empty_trunk_path():
3390 "allow --trunk to be empty if --trunk-only"
3392 # This is a test for issue #53.
3394 conv = ensure_conversion(
3395 'main', args=['--trunk-only', '--trunk='],
3399 @Cvs2SvnTestFunction
3400 def preferred_parent_cycle():
3401 "handle a cycle in branch parent preferences"
3403 conv = ensure_conversion('preferred-parent-cycle')
3406 @Cvs2SvnTestFunction
3407 def branch_from_empty_dir():
3408 "branch from an empty directory"
3410 conv = ensure_conversion('branch-from-empty-dir')
3413 @Cvs2SvnTestFunction
3414 def trunk_readd():
3415 "add a file on a branch then on trunk"
3417 conv = ensure_conversion('trunk-readd')
3420 @Cvs2SvnTestFunction
3421 def branch_from_deleted_1_1():
3422 "branch from a 1.1 revision that will be deleted"
3424 conv = ensure_conversion('branch-from-deleted-1-1')
3425 conv.logs[5].check('Adding b.txt:1.1.2.1', (
3426 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3428 conv.logs[6].check('Adding b.txt:1.1.4.1', (
3429 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3431 conv.logs[7].check('Adding b.txt:1.2', (
3432 ('/%(trunk)s/proj/b.txt', 'A'),
3435 conv.logs[8].check('Adding c.txt:1.1.2.1', (
3436 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3438 conv.logs[9].check('Adding c.txt:1.1.4.1', (
3439 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3443 @Cvs2SvnTestFunction
3444 def add_on_branch():
3445 "add a file on a branch using newer CVS"
3447 conv = ensure_conversion('add-on-branch')
3448 conv.logs[6].check('Adding b.txt:1.1', (
3449 ('/%(trunk)s/proj/b.txt', 'A'),
3451 conv.logs[7].check('Adding b.txt:1.1.2.2', (
3452 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3454 conv.logs[8].check('Adding c.txt:1.1', (
3455 ('/%(trunk)s/proj/c.txt', 'A'),
3457 conv.logs[9].check('Removing c.txt:1.2', (
3458 ('/%(trunk)s/proj/c.txt', 'D'),
3460 conv.logs[10].check('Adding c.txt:1.2.2.2', (
3461 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3463 conv.logs[11].check('Adding d.txt:1.1', (
3464 ('/%(trunk)s/proj/d.txt', 'A'),
3466 conv.logs[12].check('Adding d.txt:1.1.2.2', (
3467 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3471 @Cvs2SvnTestFunction
3472 def main_git():
3473 "test output in git-fast-import format"
3475 # Note: To test importing into git, do
3477 # ./run-tests <this-test-number>
3478 # rm -rf cvs2svn-tmp/main.git
3479 # git init --bare cvs2svn-tmp/main.git
3480 # cd cvs2svn-tmp/main.git
3481 # cat ../git-{blob,dump}.dat | git fast-import
3483 # Or, to load the dumpfiles separately:
3485 # cat ../git-blob.dat | git fast-import --export-marks=../git-marks.dat
3486 # cat ../git-dump.dat | git fast-import --import-marks=../git-marks.dat
3488 # Then use "gitk --all", "git log", etc. to test the contents of the
3489 # repository or "git clone" to make a non-bare clone.
3491 # We don't have the infrastructure to check that the resulting git
3492 # repository is correct, so we just check that the conversion runs
3493 # to completion:
3494 conv = GitConversion('main', None, [
3495 '--blobfile=cvs2svn-tmp/git-blob.dat',
3496 '--dumpfile=cvs2svn-tmp/git-dump.dat',
3497 '--username=cvs2git',
3498 'test-data/main-cvsrepos',
3502 @Cvs2SvnTestFunction
3503 def main_git2():
3504 "test cvs2git --use-external-blob-generator option"
3506 # See comment in main_git() for more information.
3508 conv = GitConversion('main', None, [
3509 '--use-external-blob-generator',
3510 '--blobfile=cvs2svn-tmp/blobfile.out',
3511 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3512 '--username=cvs2git',
3513 'test-data/main-cvsrepos',
3517 @Cvs2SvnTestFunction
3518 def git_options():
3519 "test cvs2git using options file"
3521 conv = GitConversion('main', None, [], options_file='cvs2git.options')
3524 @Cvs2SvnTestFunction
3525 def main_hg():
3526 "output in git-fast-import format with inline data"
3528 # The output should be suitable for import by Mercurial.
3530 # We don't have the infrastructure to check that the resulting
3531 # Mercurial repository is correct, so we just check that the
3532 # conversion runs to completion:
3533 conv = GitConversion('main', None, [], options_file='cvs2hg.options')
3536 @Cvs2SvnTestFunction
3537 def invalid_symbol():
3538 "a symbol with the incorrect format"
3540 conv = ensure_conversion('invalid-symbol')
3541 if not conv.output_found(
3542 r".*branch 'SYMBOL' references invalid revision 1$"
3544 raise Failure()
3547 @Cvs2SvnTestFunction
3548 def invalid_symbol_ignore():
3549 "ignore a symbol using a SymbolMapper"
3551 conv = ensure_conversion(
3552 'invalid-symbol', options_file='cvs2svn-ignore.options'
3556 @Cvs2SvnTestFunction
3557 def invalid_symbol_ignore2():
3558 "ignore a symbol using an IgnoreSymbolTransform"
3560 conv = ensure_conversion(
3561 'invalid-symbol', options_file='cvs2svn-ignore2.options'
3565 class EOLVariants(Cvs2SvnTestCase):
3566 "handle various --eol-style options"
3568 eol_style_strings = {
3569 'LF' : '\n',
3570 'CR' : '\r',
3571 'CRLF' : '\r\n',
3572 'native' : '\n',
3575 def __init__(self, eol_style):
3576 self.eol_style = eol_style
3577 self.dumpfile = 'eol-variants-%s.dump' % (self.eol_style,)
3578 Cvs2SvnTestCase.__init__(
3579 self, 'eol-variants', variant=self.eol_style,
3580 dumpfile=self.dumpfile,
3581 args=[
3582 '--default-eol=%s' % (self.eol_style,),
3586 def run(self, sbox):
3587 conv = self.ensure_conversion()
3588 dump_contents = open(conv.dumpfile, 'rb').read()
3589 expected_text = self.eol_style_strings[self.eol_style].join(
3590 ['line 1', 'line 2', '\n\n']
3592 if not dump_contents.endswith(expected_text):
3593 raise Failure()
3596 @Cvs2SvnTestFunction
3597 def no_revs_file():
3598 "handle a file with no revisions (issue #80)"
3600 conv = ensure_conversion('no-revs-file')
3603 @Cvs2SvnTestFunction
3604 def mirror_keyerror_test():
3605 "a case that gave KeyError in SVNRepositoryMirror"
3607 conv = ensure_conversion('mirror-keyerror')
3610 @Cvs2SvnTestFunction
3611 def exclude_ntdb_test():
3612 "exclude a non-trunk default branch"
3614 symbol_info_file = os.path.join(tmp_dir, 'exclude-ntdb-symbol-info.txt')
3615 conv = ensure_conversion(
3616 'exclude-ntdb',
3617 args=[
3618 '--write-symbol-info=%s' % (symbol_info_file,),
3619 '--exclude=branch3',
3620 '--exclude=tag3',
3621 '--exclude=vendortag3',
3622 '--exclude=vendorbranch',
3627 @Cvs2SvnTestFunction
3628 def mirror_keyerror2_test():
3629 "a case that gave KeyError in RepositoryMirror"
3631 conv = ensure_conversion('mirror-keyerror2')
3634 @Cvs2SvnTestFunction
3635 def mirror_keyerror3_test():
3636 "a case that gave KeyError in RepositoryMirror"
3638 conv = ensure_conversion('mirror-keyerror3')
3641 @XFail_deco()
3642 @Cvs2SvnTestFunction
3643 def add_cvsignore_to_branch_test():
3644 "check adding .cvsignore to an existing branch"
3646 # This a test for issue #122.
3648 conv = ensure_conversion('add-cvsignore-to-branch')
3649 wc_tree = conv.get_wc_tree()
3650 trunk_props = props_for_path(wc_tree, 'trunk/dir')
3651 if trunk_props['svn:ignore'] != '*.o\n\n':
3652 raise Failure()
3654 branch_props = props_for_path(wc_tree, 'branches/BRANCH/dir')
3655 if branch_props['svn:ignore'] != '*.o\n\n':
3656 raise Failure()
3659 @Cvs2SvnTestFunction
3660 def missing_deltatext():
3661 "a revision's deltatext is missing"
3663 # This is a type of RCS file corruption that has been observed.
3664 conv = ensure_conversion(
3665 'missing-deltatext',
3666 error_re=(
3667 r"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3672 @Cvs2SvnTestFunction
3673 def transform_unlabeled_branch_name():
3674 "transform name of unlabeled branch"
3676 conv = ensure_conversion(
3677 'unlabeled-branch',
3678 args=[
3679 '--symbol-transform=unlabeled-1.1.4:BRANCH2',
3682 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3683 raise Failure('Branch unlabeled-1.1.4 not excluded')
3684 if not conv.path_exists('branches', 'BRANCH2'):
3685 raise Failure('Branch BRANCH2 not found')
3688 @Cvs2SvnTestFunction
3689 def ignore_unlabeled_branch():
3690 "ignoring an unlabeled branch is not allowed"
3692 conv = ensure_conversion(
3693 'unlabeled-branch',
3694 options_file='cvs2svn-ignore.options',
3695 error_re=(
3696 r"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' "
3697 r"in \'.*\' contains commits"
3702 @Cvs2SvnTestFunction
3703 def exclude_unlabeled_branch():
3704 "exclude unlabeled branch"
3706 conv = ensure_conversion(
3707 'unlabeled-branch',
3708 args=['--exclude=unlabeled-.*'],
3710 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3711 raise Failure('Branch unlabeled-1.1.4 not excluded')
3714 @Cvs2SvnTestFunction
3715 def unlabeled_branch_name_collision():
3716 "transform unlabeled branch to same name as branch"
3718 conv = ensure_conversion(
3719 'unlabeled-branch',
3720 args=[
3721 '--symbol-transform=unlabeled-1.1.4:BRANCH',
3723 error_re=(
3724 r"ERROR\: Symbol name \'BRANCH\' is already used"
3729 @Cvs2SvnTestFunction
3730 def collision_with_unlabeled_branch_name():
3731 "transform branch to same name as unlabeled branch"
3733 conv = ensure_conversion(
3734 'unlabeled-branch',
3735 args=[
3736 '--symbol-transform=BRANCH:unlabeled-1.1.4',
3738 error_re=(
3739 r"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used"
3744 @Cvs2SvnTestFunction
3745 def many_deletes():
3746 "a repo with many removable dead revisions"
3748 conv = ensure_conversion('many-deletes')
3749 conv.logs[5].check('Add files on BRANCH', (
3750 ('/%(branches)s/BRANCH/proj/b.txt', 'A'),
3752 conv.logs[6].check('Add files on BRANCH2', (
3753 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3754 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3755 ('/%(branches)s/BRANCH2/proj/d.txt', 'A'),
3759 cvs_description = Cvs2SvnPropertiesTestCase(
3760 'main',
3761 doc='test handling of CVS file descriptions',
3762 props_to_test=['cvs:description'],
3763 expected_props=[
3764 ('trunk/proj/default', ['This is an example file description.']),
3765 ('trunk/proj/sub1/default', [None]),
3769 @Cvs2SvnTestFunction
3770 def include_empty_directories():
3771 "test --include-empty-directories option"
3773 conv = ensure_conversion(
3774 'empty-directories', args=['--include-empty-directories'],
3776 conv.logs[1].check('Standard project directories', (
3777 ('/%(trunk)s', 'A'),
3778 ('/%(branches)s', 'A'),
3779 ('/%(tags)s', 'A'),
3780 ('/%(trunk)s/root-empty-directory', 'A'),
3781 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3783 conv.logs[3].check('Add b.txt.', (
3784 ('/%(trunk)s/direct', 'A'),
3785 ('/%(trunk)s/direct/b.txt', 'A'),
3786 ('/%(trunk)s/direct/empty-directory', 'A'),
3787 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3789 conv.logs[4].check('Add c.txt.', (
3790 ('/%(trunk)s/indirect', 'A'),
3791 ('/%(trunk)s/indirect/subdirectory', 'A'),
3792 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3793 ('/%(trunk)s/indirect/empty-directory', 'A'),
3794 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3796 conv.logs[5].check('Remove b.txt', (
3797 ('/%(trunk)s/direct', 'D'),
3799 conv.logs[6].check('Remove c.txt', (
3800 ('/%(trunk)s/indirect', 'D'),
3802 conv.logs[7].check('Re-add b.txt.', (
3803 ('/%(trunk)s/direct', 'A'),
3804 ('/%(trunk)s/direct/b.txt', 'A'),
3805 ('/%(trunk)s/direct/empty-directory', 'A'),
3806 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3808 conv.logs[8].check('Re-add c.txt.', (
3809 ('/%(trunk)s/indirect', 'A'),
3810 ('/%(trunk)s/indirect/subdirectory', 'A'),
3811 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3812 ('/%(trunk)s/indirect/empty-directory', 'A'),
3813 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3815 conv.logs[9].check('This commit was manufactured', (
3816 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3818 conv.logs[10].check('This commit was manufactured', (
3819 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3821 conv.logs[11].check('Import d.txt.', (
3822 ('/%(branches)s/VENDORBRANCH', 'A'),
3823 ('/%(branches)s/VENDORBRANCH/import', 'A'),
3824 ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'),
3825 ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'),
3826 ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory',
3827 'A'),
3828 ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'),
3829 ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory',
3830 'A'),
3832 conv.logs[12].check('This commit was generated', (
3833 ('/%(trunk)s/import', 'A'),
3834 ('/%(trunk)s/import/d.txt '
3835 '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'),
3836 ('/%(trunk)s/import/empty-directory', 'A'),
3837 ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'),
3841 @Cvs2SvnTestFunction
3842 def include_empty_directories_no_prune():
3843 "test --include-empty-directories with --no-prune"
3845 conv = ensure_conversion(
3846 'empty-directories', args=['--include-empty-directories', '--no-prune'],
3848 conv.logs[1].check('Standard project directories', (
3849 ('/%(trunk)s', 'A'),
3850 ('/%(branches)s', 'A'),
3851 ('/%(tags)s', 'A'),
3852 ('/%(trunk)s/root-empty-directory', 'A'),
3853 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3855 conv.logs[3].check('Add b.txt.', (
3856 ('/%(trunk)s/direct', 'A'),
3857 ('/%(trunk)s/direct/b.txt', 'A'),
3858 ('/%(trunk)s/direct/empty-directory', 'A'),
3859 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3861 conv.logs[4].check('Add c.txt.', (
3862 ('/%(trunk)s/indirect', 'A'),
3863 ('/%(trunk)s/indirect/subdirectory', 'A'),
3864 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3865 ('/%(trunk)s/indirect/empty-directory', 'A'),
3866 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3868 conv.logs[5].check('Remove b.txt', (
3869 ('/%(trunk)s/direct/b.txt', 'D'),
3871 conv.logs[6].check('Remove c.txt', (
3872 ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'),
3874 conv.logs[7].check('Re-add b.txt.', (
3875 ('/%(trunk)s/direct/b.txt', 'A'),
3877 conv.logs[8].check('Re-add c.txt.', (
3878 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3880 conv.logs[9].check('This commit was manufactured', (
3881 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3883 conv.logs[10].check('This commit was manufactured', (
3884 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3888 @Cvs2SvnTestFunction
3889 def exclude_symbol_default():
3890 "test 'exclude' symbol default"
3892 conv = ensure_conversion(
3893 'symbol-mess', args=['--symbol-default=exclude'])
3894 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
3895 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
3896 raise Failure()
3897 if conv.path_exists('tags', 'MOSTLY_TAG') \
3898 or conv.path_exists('branches', 'MOSTLY_TAG'):
3899 raise Failure()
3902 @Cvs2SvnTestFunction
3903 def add_on_branch2():
3904 "another add-on-branch test case"
3906 conv = ensure_conversion('add-on-branch2')
3907 if len(conv.logs) != 2:
3908 raise Failure()
3909 conv.logs[2].check('add file on branch', (
3910 ('/%(branches)s/BRANCH', 'A'),
3911 ('/%(branches)s/BRANCH/file1', 'A'),
3915 @Cvs2SvnTestFunction
3916 def branch_from_vendor_branch():
3917 "branch from vendor branch"
3919 ensure_conversion(
3920 'branch-from-vendor-branch',
3921 symbol_hints_file='branch-from-vendor-branch-symbol-hints.txt',
3925 @Cvs2SvnTestFunction
3926 def strange_default_branch():
3927 "default branch too deep in the hierarchy"
3929 ensure_conversion(
3930 'strange-default-branch',
3931 error_re=(
3932 r'ERROR\: The default branch 1\.2\.4\.3\.2\.1\.2 '
3933 r'in file .* is not a top-level branch'
3938 @Cvs2SvnTestFunction
3939 def move_parent():
3940 "graft onto preferred parent that was itself moved"
3942 conv = ensure_conversion(
3943 'move-parent',
3945 conv.logs[2].check('first', (
3946 ('/%(trunk)s/file1', 'A'),
3947 ('/%(trunk)s/file2', 'A'),
3949 conv.logs[3].check('This commit was manufactured', (
3950 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
3952 conv.logs[4].check('second', (
3953 ('/%(branches)s/b2/file1', 'M'),
3955 conv.logs[5].check('This commit was manufactured', (
3956 ('/%(branches)s/b1 (from /%(branches)s/b2:4)', 'A'),
3959 # b2 and b1 are equally good parents for b3, so accept either one.
3960 # (Currently, cvs2svn chooses b1 as the preferred parent because it
3961 # comes earlier than b2 in alphabetical order.)
3962 try:
3963 conv.logs[6].check('This commit was manufactured', (
3964 ('/%(branches)s/b3 (from /%(branches)s/b1:5)', 'A'),
3966 except Failure:
3967 conv.logs[6].check('This commit was manufactured', (
3968 ('/%(branches)s/b3 (from /%(branches)s/b2:4)', 'A'),
3972 @Cvs2SvnTestFunction
3973 def log_message_eols():
3974 "nonstandard EOLs in log messages"
3976 conv = ensure_conversion(
3977 'log-message-eols',
3979 conv.logs[2].check('The CRLF at the end of this line\nshould', (
3980 ('/%(trunk)s/lottalogs', 'A'),
3982 conv.logs[3].check('The CR at the end of this line\nshould', (
3983 ('/%(trunk)s/lottalogs', 'M'),
3987 @Cvs2SvnTestFunction
3988 def missing_vendor_branch():
3989 "default branch not present in RCS file"
3991 conv = ensure_conversion(
3992 'missing-vendor-branch',
3994 if not conv.output_found(
3995 r'.*vendor branch \'1\.1\.1\' is not present in file and will be ignored'
3997 raise Failure()
4000 @Cvs2SvnTestFunction
4001 def newphrases():
4002 "newphrases in RCS files"
4004 ensure_conversion(
4005 'newphrases',
4009 ########################################################################
4010 # Run the tests
4012 # list all tests here, starting with None:
4013 test_list = [
4014 None,
4015 # 1:
4016 show_usage,
4017 cvs2svn_manpage,
4018 cvs2git_manpage,
4019 cvs2hg_manpage,
4020 attr_exec,
4021 space_fname,
4022 two_quick,
4023 PruneWithCare(),
4024 PruneWithCare(variant=1, trunk='a', branches='b', tags='c'),
4025 # 10:
4026 PruneWithCare(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4027 PruneWithCare(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4028 interleaved_commits,
4029 simple_commits,
4030 SimpleTags(),
4031 SimpleTags(variant=1, trunk='a', branches='b', tags='c'),
4032 SimpleTags(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4033 SimpleTags(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4034 simple_branch_commits,
4035 mixed_time_tag,
4036 # 20:
4037 mixed_time_branch_with_added_file,
4038 mixed_commit,
4039 split_time_branch,
4040 bogus_tag,
4041 overlapping_branch,
4042 PhoenixBranch(),
4043 PhoenixBranch(variant=1, trunk='a/1', branches='b/1', tags='c/1'),
4044 ctrl_char_in_log,
4045 overdead,
4046 NoTrunkPrune(),
4047 # 30:
4048 NoTrunkPrune(variant=1, trunk='a', branches='b', tags='c'),
4049 NoTrunkPrune(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4050 NoTrunkPrune(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4051 double_delete,
4052 split_branch,
4053 resync_misgroups,
4054 TaggedBranchAndTrunk(),