Allow the svn:executable property to affect the git filemode
[cvs2svn.git] / run-tests.py
blobb519ec24a4bede226d69a0b13da3e2429c66948c
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 True:
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.'
320 def __init__(self, lines):
321 self.lines = list(reversed(lines))
323 def readline(self):
324 if len(self.lines) > 0:
325 return self.lines.pop()
326 else:
327 return None
329 def absorb_message_body(out, num_lines, log):
330 """Read NUM_LINES of log message body from OUT into Log item LOG."""
332 for i in range(num_lines):
333 log.msg += out.readline()
335 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
336 '(?P<author>[^\|]+) \| '
337 '(?P<date>[^\|]+) '
338 '\| (?P<lines>[0-9]+) (line|lines)$')
340 log_separator = '-' * 72
342 logs = { }
344 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
346 while True:
347 this_log = None
348 line = out.readline()
349 if not line: break
350 line = line[:-1]
352 if line.find(log_separator) == 0:
353 line = out.readline()
354 if not line: break
355 line = line[:-1]
356 m = log_start_re.match(line)
357 if m:
358 this_log = Log(
359 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
360 line = out.readline()
361 if line == '\n':
362 # No changed paths
363 pass
364 elif line.startswith('Changed paths:'):
365 this_log.absorb_changed_paths(out)
366 else:
367 print 'unexpected log output'
368 print "Line: '%s'" % line
369 sys.exit(1)
371 absorb_message_body(out, int(m.group('lines')), this_log)
372 logs[this_log.revision] = this_log
373 elif len(line) == 0:
374 break # We've reached the end of the log output.
375 else:
376 print 'unexpected log output (missing revision line)'
377 print "Line: '%s'" % line
378 sys.exit(1)
379 else:
380 print 'unexpected log output (missing log separator)'
381 print "Line: '%s'" % line
382 sys.exit(1)
384 return logs
387 def erase(path):
388 """Unconditionally remove PATH and its subtree, if any. PATH may be
389 non-existent, a file or symlink, or a directory."""
390 if os.path.isdir(path):
391 safe_rmtree(path)
392 elif os.path.exists(path):
393 os.remove(path)
396 log_msg_text_wrapper = textwrap.TextWrapper(width=76, break_long_words=False)
398 def sym_log_msg(symbolic_name, is_tag=None):
399 """Return the expected log message for a cvs2svn-synthesized revision
400 creating branch or tag SYMBOLIC_NAME."""
402 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
403 if is_tag:
404 type = 'tag'
405 else:
406 type = 'branch'
408 return log_msg_text_wrapper.fill(
409 "This commit was manufactured by cvs2svn to create %s '%s'."
410 % (type, symbolic_name)
414 def make_conversion_id(
415 name, args, passbypass, options_file=None, symbol_hints_file=None
417 """Create an identifying tag for a conversion.
419 The return value can also be used as part of a filesystem path.
421 NAME is the name of the CVS repository.
423 ARGS are the extra arguments to be passed to cvs2svn.
425 PASSBYPASS is a boolean indicating whether the conversion is to be
426 run one pass at a time.
428 If OPTIONS_FILE is specified, it is an options file that will be
429 used for the conversion.
431 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
432 will be used for the conversion.
434 The 1-to-1 mapping between cvs2svn command parameters and
435 conversion_ids allows us to avoid running the same conversion more
436 than once, when multiple tests use exactly the same conversion."""
438 conv_id = name
440 args = args[:]
442 if passbypass:
443 args.append('--passbypass')
445 if symbol_hints_file is not None:
446 args.append('--symbol-hints=%s' % (symbol_hints_file,))
448 # There are some characters that are forbidden in filenames, and
449 # there is a limit on the total length of a path to a file. So use
450 # a hash of the parameters rather than concatenating the parameters
451 # into a string.
452 if args:
453 conv_id += "-" + md5('\0'.join(args)).hexdigest()
455 # Some options-file based tests rely on knowing the paths to which
456 # the repository should be written, so we handle that option as a
457 # predictable string:
458 if options_file is not None:
459 conv_id += '--options=%s' % (options_file,)
461 return conv_id
464 class Conversion:
465 """A record of a cvs2svn conversion.
467 Fields:
469 conv_id -- the conversion id for this Conversion.
471 name -- a one-word name indicating the involved repositories.
473 dumpfile -- the name of the SVN dumpfile created by the conversion
474 (if the DUMPFILE constructor argument was used); otherwise,
475 None.
477 repos -- the path to the svn repository. Unset if DUMPFILE was
478 specified.
480 logs -- a dictionary of Log instances, as returned by parse_log().
481 Unset if DUMPFILE was specified.
483 symbols -- a dictionary of symbols used for string interpolation
484 in path names.
486 stdout -- a list of lines written by cvs2svn to stdout
488 _wc -- the basename of the svn working copy (within tmp_dir).
489 Unset if DUMPFILE was specified.
491 _wc_path -- the path to the svn working copy, if it has already
492 been created; otherwise, None. (The working copy is created
493 lazily when get_wc() is called.) Unset if DUMPFILE was
494 specified.
496 _wc_tree -- the tree built from the svn working copy, if it has
497 already been created; otherwise, None. The tree is created
498 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
499 specified.
501 _svnrepos -- the basename of the svn repository (within tmp_dir).
502 Unset if DUMPFILE was specified."""
504 # The number of the last cvs2svn pass (determined lazily by
505 # get_last_pass()).
506 last_pass = None
508 @classmethod
509 def get_last_pass(cls):
510 """Return the number of cvs2svn's last pass."""
512 if cls.last_pass is None:
513 out = run_script(cvs2svn, None, '--help-passes')
514 cls.last_pass = int(out[-1].split()[0])
515 return cls.last_pass
517 def __init__(
518 self, conv_id, name, error_re, passbypass, symbols, args,
519 verbosity=None, options_file=None, symbol_hints_file=None, dumpfile=None,
521 self.conv_id = conv_id
522 self.name = name
523 self.symbols = symbols
524 if not os.path.isdir(tmp_dir):
525 os.mkdir(tmp_dir)
527 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
529 if dumpfile:
530 self.dumpfile = os.path.join(tmp_dir, dumpfile)
531 # Clean up from any previous invocations of this script.
532 erase(self.dumpfile)
533 else:
534 self.dumpfile = None
535 self.repos = os.path.join(tmp_dir, '%s-svnrepos' % self.conv_id)
536 self._wc = os.path.join(tmp_dir, '%s-wc' % self.conv_id)
537 self._wc_path = None
538 self._wc_tree = None
540 # Clean up from any previous invocations of this script.
541 erase(self.repos)
542 erase(self._wc)
544 args = list(args)
545 if svntest.main.svnadmin_binary != 'svnadmin':
546 args.extend([
547 '--svnadmin=%s' % (svntest.main.svnadmin_binary,),
549 if options_file:
550 self.options_file = os.path.join(cvsrepos, options_file)
551 args.extend([
552 '--options=%s' % self.options_file,
554 args.append(verbosity or '-qqqqqq')
555 assert not symbol_hints_file
556 else:
557 self.options_file = None
558 args.extend([
559 '--tmpdir=%s' % tmp_dir,
562 args.append(verbosity or '-qqqqqq')
564 if symbol_hints_file:
565 self.symbol_hints_file = os.path.join(cvsrepos, symbol_hints_file)
566 args.extend([
567 '--symbol-hints=%s' % self.symbol_hints_file,
570 if self.dumpfile:
571 args.extend(['--dumpfile=%s' % (self.dumpfile,)])
572 else:
573 args.extend(['-s', self.repos])
574 args.extend([cvsrepos])
576 if passbypass:
577 self.stdout = []
578 for p in range(1, self.get_last_pass() + 1):
579 self.stdout += run_script(cvs2svn, error_re, '-p', str(p), *args)
580 else:
581 self.stdout = run_script(cvs2svn, error_re, *args)
583 if self.dumpfile:
584 if not os.path.isfile(self.dumpfile):
585 raise Failure(
586 "Dumpfile not created: '%s'"
587 % os.path.join(os.getcwd(), self.dumpfile)
589 else:
590 if os.path.isdir(self.repos):
591 self.logs = parse_log(self.repos, self.symbols)
592 elif error_re is None:
593 raise Failure(
594 "Repository not created: '%s'"
595 % os.path.join(os.getcwd(), self.repos)
598 def output_found(self, pattern):
599 """Return True if PATTERN matches any line in self.stdout.
601 PATTERN is a regular expression pattern as a string.
604 pattern_re = re.compile(pattern)
606 for line in self.stdout:
607 if pattern_re.match(line):
608 # We found the pattern that we were looking for.
609 return True
610 else:
611 return False
613 def find_tag_log(self, tagname):
614 """Search LOGS for a log message containing 'TAGNAME' and return the
615 log in which it was found."""
616 for i in xrange(len(self.logs), 0, -1):
617 if self.logs[i].msg.find("'"+tagname+"'") != -1:
618 return self.logs[i]
619 raise ValueError("Tag %s not found in logs" % tagname)
621 def get_wc(self, *args):
622 """Return the path to the svn working copy, or a path within the WC.
624 If a working copy has not been created yet, create it now.
626 If ARGS are specified, then they should be strings that form
627 fragments of a path within the WC. They are joined using
628 os.path.join() and appended to the WC path."""
630 if self._wc_path is None:
631 run_svn('co', repos_to_url(self.repos), self._wc)
632 self._wc_path = self._wc
633 return os.path.join(self._wc_path, *args)
635 def get_wc_tree(self):
636 if self._wc_tree is None:
637 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
638 return self._wc_tree
640 def path_exists(self, *args):
641 """Return True if the specified path exists within the repository.
643 (The strings in ARGS are first joined into a path using
644 os.path.join().)"""
646 return os.path.exists(self.get_wc(*args))
648 def check_props(self, keys, checks):
649 """Helper function for checking lots of properties. For a list of
650 files in the conversion, check that the values of the properties
651 listed in KEYS agree with those listed in CHECKS. CHECKS is a
652 list of tuples: [ (filename, [value, value, ...]), ...], where the
653 values are listed in the same order as the key names are listed in
654 KEYS."""
656 for (file, values) in checks:
657 assert len(values) == len(keys)
658 props = props_for_path(self.get_wc_tree(), file)
659 for i in range(len(keys)):
660 if props.get(keys[i]) != values[i]:
661 raise Failure(
662 "File %s has property %s set to \"%s\" "
663 "(it should have been \"%s\").\n"
664 % (file, keys[i], props.get(keys[i]), values[i],)
668 class GitConversion:
669 """A record of a cvs2svn conversion.
671 Fields:
673 name -- a one-word name indicating the CVS repository to be converted.
675 stdout -- a list of lines written by cvs2svn to stdout."""
677 def __init__(self, name, error_re, args, verbosity=None, options_file=None):
678 self.name = name
679 if not os.path.isdir(tmp_dir):
680 os.mkdir(tmp_dir)
682 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
684 args = list(args)
685 if options_file:
686 self.options_file = os.path.join(cvsrepos, options_file)
687 args.extend([
688 '--options=%s' % self.options_file,
690 else:
691 self.options_file = None
693 args.append(verbosity or '-qqqqqq')
695 self.stdout = run_script(cvs2git, error_re, *args)
698 # Cache of conversions that have already been done. Keys are conv_id;
699 # values are Conversion instances.
700 already_converted = { }
702 def ensure_conversion(
703 name, error_re=None, passbypass=None,
704 trunk=None, branches=None, tags=None,
705 args=None, verbosity=None,
706 options_file=None, symbol_hints_file=None, dumpfile=None,
708 """Convert CVS repository NAME to Subversion, but only if it has not
709 been converted before by this invocation of this script. If it has
710 been converted before, return the Conversion object from the
711 previous invocation.
713 If no error, return a Conversion instance.
715 If ERROR_RE is a string, it is a regular expression expected to
716 match some line of stderr printed by the conversion. If there is an
717 error and ERROR_RE is not set, then raise Failure.
719 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
720 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
722 NAME is just one word. For example, 'main' would mean to convert
723 './test-data/main-cvsrepos', and after the conversion, the resulting
724 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
725 a checked out head working copy in './cvs2svn-tmp/main-wc'.
727 Any other options to pass to cvs2svn should be in ARGS, each element
728 being one option, e.g., '--trunk-only'. If the option takes an
729 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
730 are passed to cvs2svn in the order that they appear in ARGS.
732 If VERBOSITY is set, then it is passed to cvs2svn as an option.
733 Otherwise, the verbosity is turned way down so that only error
734 messages are emitted.
736 If OPTIONS_FILE is specified, then it should be the name of a file
737 within the main directory of the cvs repository associated with this
738 test. It is passed to cvs2svn using the --options option (which
739 suppresses some other options that are incompatible with --options).
741 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
742 file within the main directory of the cvs repository associated with
743 this test. It is passed to cvs2svn using the --symbol-hints option.
745 If DUMPFILE is specified, then it is the name of a dumpfile within
746 the temporary directory to which the conversion output should be
747 written."""
749 if args is None:
750 args = []
751 else:
752 args = list(args)
754 if trunk is None:
755 trunk = 'trunk'
756 else:
757 args.append('--trunk=%s' % (trunk,))
759 if branches is None:
760 branches = 'branches'
761 else:
762 args.append('--branches=%s' % (branches,))
764 if tags is None:
765 tags = 'tags'
766 else:
767 args.append('--tags=%s' % (tags,))
769 conv_id = make_conversion_id(
770 name, args, passbypass, options_file, symbol_hints_file
773 if conv_id not in already_converted:
774 try:
775 # Run the conversion and store the result for the rest of this
776 # session:
777 already_converted[conv_id] = Conversion(
778 conv_id, name, error_re, passbypass,
779 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
780 args, verbosity, options_file, symbol_hints_file, dumpfile,
782 except Failure:
783 # Remember the failure so that a future attempt to run this conversion
784 # does not bother to retry, but fails immediately.
785 already_converted[conv_id] = None
786 raise
788 conv = already_converted[conv_id]
789 if conv is None:
790 raise Failure()
791 return conv
794 class Cvs2SvnTestFunction(TestCase):
795 """A TestCase based on a naked Python function object.
797 FUNC should be a function that returns None on success and throws an
798 svntest.Failure exception on failure. It should have a brief
799 docstring describing what it does (and fulfilling certain
800 conditions). FUNC must take no arguments.
802 This class is almost identical to svntest.testcase.FunctionTestCase,
803 except that the test function does not require a sandbox and does
804 not accept any parameter (not even sandbox=None).
806 This class can be used as an annotation on a Python function.
810 def __init__(self, func):
811 # it better be a function that accepts no parameters and has a
812 # docstring on it.
813 assert isinstance(func, types.FunctionType)
815 name = func.func_name
817 assert func.func_code.co_argcount == 0, \
818 '%s must not take any arguments' % name
820 doc = func.__doc__.strip()
821 assert doc, '%s must have a docstring' % name
823 # enforce stylistic guidelines for the function docstrings:
824 # - no longer than 50 characters
825 # - should not end in a period
826 # - should not be capitalized
827 assert len(doc) <= 50, \
828 "%s's docstring must be 50 characters or less" % name
829 assert doc[-1] != '.', \
830 "%s's docstring should not end in a period" % name
831 assert doc[0].lower() == doc[0], \
832 "%s's docstring should not be capitalized" % name
834 TestCase.__init__(self, doc=doc)
835 self.func = func
837 def get_function_name(self):
838 return self.func.func_name
840 def get_sandbox_name(self):
841 return None
843 def run(self, sandbox):
844 return self.func()
847 class Cvs2HgTestFunction(Cvs2SvnTestFunction):
848 """Same as Cvs2SvnTestFunction, but for test cases that should be
849 skipped if Mercurial is not available.
851 def run(self, sandbox):
852 if not have_hg:
853 raise svntest.Skip()
854 else:
855 return self.func()
858 class Cvs2SvnTestCase(TestCase):
859 def __init__(
860 self, name, doc=None, variant=None,
861 error_re=None, passbypass=None,
862 trunk=None, branches=None, tags=None,
863 args=None,
864 options_file=None, symbol_hints_file=None, dumpfile=None,
866 self.name = name
868 if doc is None:
869 # By default, use the first line of the class docstring as the
870 # doc:
871 doc = self.__doc__.splitlines()[0]
873 if variant is not None:
874 # Modify doc to show the variant. Trim doc first if necessary
875 # to stay within the 50-character limit.
876 suffix = '...variant %s' % (variant,)
877 doc = doc[:50 - len(suffix)] + suffix
879 TestCase.__init__(self, doc=doc)
881 self.error_re = error_re
882 self.passbypass = passbypass
883 self.trunk = trunk
884 self.branches = branches
885 self.tags = tags
886 self.args = args
887 self.options_file = options_file
888 self.symbol_hints_file = symbol_hints_file
889 self.dumpfile = dumpfile
891 def ensure_conversion(self):
892 return ensure_conversion(
893 self.name,
894 error_re=self.error_re, passbypass=self.passbypass,
895 trunk=self.trunk, branches=self.branches, tags=self.tags,
896 args=self.args,
897 options_file=self.options_file,
898 symbol_hints_file=self.symbol_hints_file,
899 dumpfile=self.dumpfile,
902 def get_sandbox_name(self):
903 return None
906 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase):
907 """Test properties resulting from a conversion."""
909 def __init__(self, name, props_to_test, expected_props, **kw):
910 """Initialize an instance of Cvs2SvnPropertiesTestCase.
912 NAME is the name of the test, passed to Cvs2SvnTestCase.
913 PROPS_TO_TEST is a list of the names of svn properties that should
914 be tested. EXPECTED_PROPS is a list of tuples [(filename,
915 [value,...])], where the second item in each tuple is a list of
916 values expected for the properties listed in PROPS_TO_TEST for the
917 specified filename. If a property must *not* be set, then its
918 value should be listed as None."""
920 Cvs2SvnTestCase.__init__(self, name, **kw)
921 self.props_to_test = props_to_test
922 self.expected_props = expected_props
924 def run(self, sbox):
925 conv = self.ensure_conversion()
926 conv.check_props(self.props_to_test, self.expected_props)
929 #----------------------------------------------------------------------
930 # Tests.
931 #----------------------------------------------------------------------
934 @Cvs2SvnTestFunction
935 def show_usage():
936 "cvs2svn with no arguments shows usage"
937 out = run_script(cvs2svn, None)
938 if (len(out) > 2 and out[0].find('ERROR:') == 0
939 and out[1].find('DBM module')):
940 print 'cvs2svn cannot execute due to lack of proper DBM module.'
941 print 'Exiting without running any further tests.'
942 sys.exit(1)
943 if out[0].find('Usage:') < 0:
944 raise Failure('Basic cvs2svn invocation failed.')
947 @Cvs2SvnTestFunction
948 def cvs2svn_manpage():
949 "generate a manpage for cvs2svn"
950 out = run_script(cvs2svn, None, '--man')
953 @Cvs2SvnTestFunction
954 def cvs2git_manpage():
955 "generate a manpage for cvs2git"
956 out = run_script(cvs2git, None, '--man')
959 @XFail_deco()
960 @Cvs2HgTestFunction
961 def cvs2hg_manpage():
962 "generate a manpage for cvs2hg"
963 out = run_script(cvs2hg, None, '--man')
966 @Cvs2SvnTestFunction
967 def show_help_passes():
968 "cvs2svn --help-passes shows pass information"
969 out = run_script(cvs2svn, None, '--help-passes')
970 if out[0].find('PASSES') < 0:
971 raise Failure('cvs2svn --help-passes failed.')
974 @Cvs2SvnTestFunction
975 def attr_exec():
976 "detection of the executable flag"
977 if sys.platform == 'win32':
978 raise svntest.Skip()
979 st = os.stat(os.path.join('test-data', 'main-cvsrepos', 'single-files', 'attr-exec,v'))
980 if not st.st_mode & stat.S_IXUSR:
981 # This might be the case if the test is being run on a filesystem
982 # that is mounted "noexec".
983 raise svntest.Skip()
984 conv = ensure_conversion('main')
985 st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec'))
986 if not st.st_mode & stat.S_IXUSR:
987 raise Failure()
990 @Cvs2SvnTestFunction
991 def space_fname():
992 "conversion of filename with a space"
993 conv = ensure_conversion('main')
994 if not conv.path_exists('trunk', 'single-files', 'space fname'):
995 raise Failure()
998 @Cvs2SvnTestFunction
999 def two_quick():
1000 "two commits in quick succession"
1001 conv = ensure_conversion('main')
1002 logs = parse_log(
1003 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
1004 if len(logs) != 2:
1005 raise Failure()
1008 class PruneWithCare(Cvs2SvnTestCase):
1009 "prune, but never too much"
1011 def __init__(self, **kw):
1012 Cvs2SvnTestCase.__init__(self, 'main', **kw)
1014 def run(self, sbox):
1015 # Robert Pluim encountered this lovely one while converting the
1016 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
1017 # repository (see issue #1302). Step 4 is the doozy:
1019 # revision 1: adds trunk/blah/, adds trunk/blah/first
1020 # revision 2: adds trunk/blah/second
1021 # revision 3: deletes trunk/blah/first
1022 # revision 4: deletes blah [re-deleting trunk/blah/first pruned blah!]
1023 # revision 5: does nothing
1025 # After fixing cvs2svn, the sequence (correctly) looks like this:
1027 # revision 1: adds trunk/blah/, adds trunk/blah/first
1028 # revision 2: adds trunk/blah/second
1029 # revision 3: deletes trunk/blah/first
1030 # revision 4: does nothing [because trunk/blah/first already deleted]
1031 # revision 5: deletes blah
1033 # The difference is in 4 and 5. In revision 4, it's not correct
1034 # to prune blah/, because second is still in there, so revision 4
1035 # does nothing now. But when we delete second in 5, that should
1036 # bubble up and prune blah/ instead.
1038 # ### Note that empty revisions like 4 are probably going to become
1039 # ### at least optional, if not banished entirely from cvs2svn's
1040 # ### output. Hmmm, or they may stick around, with an extra
1041 # ### revision property explaining what happened. Need to think
1042 # ### about that. In some sense, it's a bug in Subversion itself,
1043 # ### that such revisions don't show up in 'svn log' output.
1045 conv = self.ensure_conversion()
1047 # Confirm that revision 4 removes '/trunk/full-prune/first',
1048 # and that revision 6 removes '/trunk/full-prune'.
1050 # Also confirm similar things about '/full-prune-reappear/...',
1051 # which is similar, except that later on it reappears, restored
1052 # from pruneland, because a file gets added to it.
1054 # And finally, a similar thing for '/partial-prune/...', except that
1055 # in its case, a permanent file on the top level prevents the
1056 # pruning from going farther than the subdirectory containing first
1057 # and second.
1059 for path in ('full-prune/first',
1060 'full-prune-reappear/sub/first',
1061 'partial-prune/sub/first'):
1062 conv.logs[5].check_change('/%(trunk)s/' + path, 'D')
1064 for path in ('full-prune',
1065 'full-prune-reappear',
1066 'partial-prune/sub'):
1067 conv.logs[7].check_change('/%(trunk)s/' + path, 'D')
1069 for path in ('full-prune-reappear',
1070 'full-prune-reappear/appears-later'):
1071 conv.logs[33].check_change('/%(trunk)s/' + path, 'A')
1074 @Cvs2SvnTestFunction
1075 def interleaved_commits():
1076 "two interleaved trunk commits, different log msgs"
1077 # See test-data/main-cvsrepos/proj/README.
1078 conv = ensure_conversion('main')
1080 # The initial import.
1081 rev = 26
1082 conv.logs[rev].check('Initial import.', (
1083 ('/%(trunk)s/interleaved', 'A'),
1084 ('/%(trunk)s/interleaved/1', 'A'),
1085 ('/%(trunk)s/interleaved/2', 'A'),
1086 ('/%(trunk)s/interleaved/3', 'A'),
1087 ('/%(trunk)s/interleaved/4', 'A'),
1088 ('/%(trunk)s/interleaved/5', 'A'),
1089 ('/%(trunk)s/interleaved/a', 'A'),
1090 ('/%(trunk)s/interleaved/b', 'A'),
1091 ('/%(trunk)s/interleaved/c', 'A'),
1092 ('/%(trunk)s/interleaved/d', 'A'),
1093 ('/%(trunk)s/interleaved/e', 'A'),
1096 def check_letters(rev):
1097 """Check if REV is the rev where only letters were committed."""
1099 conv.logs[rev].check('Committing letters only.', (
1100 ('/%(trunk)s/interleaved/a', 'M'),
1101 ('/%(trunk)s/interleaved/b', 'M'),
1102 ('/%(trunk)s/interleaved/c', 'M'),
1103 ('/%(trunk)s/interleaved/d', 'M'),
1104 ('/%(trunk)s/interleaved/e', 'M'),
1107 def check_numbers(rev):
1108 """Check if REV is the rev where only numbers were committed."""
1110 conv.logs[rev].check('Committing numbers only.', (
1111 ('/%(trunk)s/interleaved/1', 'M'),
1112 ('/%(trunk)s/interleaved/2', 'M'),
1113 ('/%(trunk)s/interleaved/3', 'M'),
1114 ('/%(trunk)s/interleaved/4', 'M'),
1115 ('/%(trunk)s/interleaved/5', 'M'),
1118 # One of the commits was letters only, the other was numbers only.
1119 # But they happened "simultaneously", so we don't assume anything
1120 # about which commit appeared first, so we just try both ways.
1121 rev += 1
1122 try:
1123 check_letters(rev)
1124 check_numbers(rev + 1)
1125 except Failure:
1126 check_numbers(rev)
1127 check_letters(rev + 1)
1130 @Cvs2SvnTestFunction
1131 def simple_commits():
1132 "simple trunk commits"
1133 # See test-data/main-cvsrepos/proj/README.
1134 conv = ensure_conversion('main')
1136 # The initial import.
1137 conv.logs[13].check('Initial import.', (
1138 ('/%(trunk)s/proj', 'A'),
1139 ('/%(trunk)s/proj/default', 'A'),
1140 ('/%(trunk)s/proj/sub1', 'A'),
1141 ('/%(trunk)s/proj/sub1/default', 'A'),
1142 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1143 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1144 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1145 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1146 ('/%(trunk)s/proj/sub2', 'A'),
1147 ('/%(trunk)s/proj/sub2/default', 'A'),
1148 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1149 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1150 ('/%(trunk)s/proj/sub3', 'A'),
1151 ('/%(trunk)s/proj/sub3/default', 'A'),
1154 # The first commit.
1155 conv.logs[18].check('First commit to proj, affecting two files.', (
1156 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1157 ('/%(trunk)s/proj/sub3/default', 'M'),
1160 # The second commit.
1161 conv.logs[19].check('Second commit to proj, affecting all 7 files.', (
1162 ('/%(trunk)s/proj/default', 'M'),
1163 ('/%(trunk)s/proj/sub1/default', 'M'),
1164 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1165 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1166 ('/%(trunk)s/proj/sub2/default', 'M'),
1167 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1168 ('/%(trunk)s/proj/sub3/default', 'M')
1172 class SimpleTags(Cvs2SvnTestCase):
1173 "simple tags and branches, no commits"
1175 def __init__(self, **kw):
1176 # See test-data/main-cvsrepos/proj/README.
1177 Cvs2SvnTestCase.__init__(self, 'main', **kw)
1179 def run(self, sbox):
1180 conv = self.ensure_conversion()
1182 # Verify the copy source for the tags we are about to check
1183 # No need to verify the copyfrom revision, as simple_commits did that
1184 conv.logs[13].check('Initial import.', (
1185 ('/%(trunk)s/proj', 'A'),
1186 ('/%(trunk)s/proj/default', 'A'),
1187 ('/%(trunk)s/proj/sub1', 'A'),
1188 ('/%(trunk)s/proj/sub1/default', 'A'),
1189 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1190 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1191 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1192 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1193 ('/%(trunk)s/proj/sub2', 'A'),
1194 ('/%(trunk)s/proj/sub2/default', 'A'),
1195 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1196 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1197 ('/%(trunk)s/proj/sub3', 'A'),
1198 ('/%(trunk)s/proj/sub3/default', 'A'),
1201 # Tag on rev 1.1.1.1 of all files in proj
1202 conv.logs[16].check(sym_log_msg('B_FROM_INITIALS'), (
1203 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1204 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1205 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1208 # The same, as a tag
1209 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
1210 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1211 ('/%(tags)s/T_ALL_INITIAL_FILES (from /%(trunk)s:13)', 'A'),
1212 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
1213 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
1216 # Tag on rev 1.1.1.1 of all files in proj, except one
1217 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1218 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1219 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE (from /%(trunk)s:13)', 'A'),
1220 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
1221 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
1222 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1225 # The same, as a branch
1226 conv.logs[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1227 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE (from /%(trunk)s:13)', 'A'),
1228 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1229 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
1230 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
1234 @Cvs2SvnTestFunction
1235 def simple_branch_commits():
1236 "simple branch commits"
1237 # See test-data/main-cvsrepos/proj/README.
1238 conv = ensure_conversion('main')
1240 conv.logs[23].check('Modify three files, on branch B_MIXED.', (
1241 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1242 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1243 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1247 @Cvs2SvnTestFunction
1248 def mixed_time_tag():
1249 "mixed-time tag"
1250 # See test-data/main-cvsrepos/proj/README.
1251 conv = ensure_conversion('main')
1253 log = conv.find_tag_log('T_MIXED')
1254 log.check_changes((
1255 ('/%(tags)s/T_MIXED (from /%(trunk)s:19)', 'A'),
1256 ('/%(tags)s/T_MIXED/single-files', 'D'),
1257 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
1258 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
1259 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1260 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1264 @Cvs2SvnTestFunction
1265 def mixed_time_branch_with_added_file():
1266 "mixed-time branch, and a file added to the branch"
1267 # See test-data/main-cvsrepos/proj/README.
1268 conv = ensure_conversion('main')
1270 # A branch from the same place as T_MIXED in the previous test,
1271 # plus a file added directly to the branch
1272 conv.logs[21].check(sym_log_msg('B_MIXED'), (
1273 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1274 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1275 ('/%(branches)s/B_MIXED/single-files', 'D'),
1276 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1277 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1278 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1281 conv.logs[22].check('Add a file on branch B_MIXED.', (
1282 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1286 @Cvs2SvnTestFunction
1287 def mixed_commit():
1288 "a commit affecting both trunk and a branch"
1289 # See test-data/main-cvsrepos/proj/README.
1290 conv = ensure_conversion('main')
1292 conv.logs[24].check(
1293 'A single commit affecting one file on branch B_MIXED '
1294 'and one on trunk.', (
1295 ('/%(trunk)s/proj/sub2/default', 'M'),
1296 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1300 @Cvs2SvnTestFunction
1301 def split_time_branch():
1302 "branch some trunk files, and later branch the rest"
1303 # See test-data/main-cvsrepos/proj/README.
1304 conv = ensure_conversion('main')
1306 # First change on the branch, creating it
1307 conv.logs[25].check(sym_log_msg('B_SPLIT'), (
1308 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1309 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1310 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1311 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1314 conv.logs[29].check('First change on branch B_SPLIT.', (
1315 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1316 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1317 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1318 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1319 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1322 # A trunk commit for the file which was not branched
1323 conv.logs[30].check('A trunk change to sub1/subsubB/default. '
1324 'This was committed about an', (
1325 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1328 # Add the file not already branched to the branch, with modification:w
1329 conv.logs[31].check(sym_log_msg('B_SPLIT'), (
1330 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1331 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1334 conv.logs[32].check('This change affects sub3/default and '
1335 'sub1/subsubB/default, on branch', (
1336 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1337 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1341 @Cvs2SvnTestFunction
1342 def multiple_tags():
1343 "multiple tags referring to same revision"
1344 conv = ensure_conversion('main')
1345 if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1346 raise Failure()
1347 if not conv.path_exists(
1348 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1349 raise Failure()
1352 @Cvs2SvnTestFunction
1353 def multiply_defined_symbols():
1354 "multiple definitions of symbol names"
1356 # We can only check one line of the error output at a time, so test
1357 # twice. (The conversion only have to be done once because the
1358 # results are cached.)
1359 conv = ensure_conversion(
1360 'multiply-defined-symbols',
1361 error_re=(
1362 r"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1363 r"1\.2\.4 1\.2\.2"
1366 conv = ensure_conversion(
1367 'multiply-defined-symbols',
1368 error_re=(
1369 r"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1370 r"1\.2 1\.1"
1375 @Cvs2SvnTestFunction
1376 def multiply_defined_symbols_renamed():
1377 "rename multiply defined symbols"
1379 conv = ensure_conversion(
1380 'multiply-defined-symbols',
1381 options_file='cvs2svn-rename.options',
1385 @Cvs2SvnTestFunction
1386 def multiply_defined_symbols_ignored():
1387 "ignore multiply defined symbols"
1389 conv = ensure_conversion(
1390 'multiply-defined-symbols',
1391 options_file='cvs2svn-ignore.options',
1395 @Cvs2SvnTestFunction
1396 def repeatedly_defined_symbols():
1397 "multiple identical definitions of symbol names"
1399 # If a symbol is defined multiple times but has the same value each
1400 # time, that should not be an error.
1402 conv = ensure_conversion('repeatedly-defined-symbols')
1405 @Cvs2SvnTestFunction
1406 def bogus_tag():
1407 "conversion of invalid symbolic names"
1408 conv = ensure_conversion('bogus-tag')
1411 @Cvs2SvnTestFunction
1412 def overlapping_branch():
1413 "ignore a file with a branch with two names"
1414 conv = ensure_conversion(
1415 'overlapping-branch',
1416 verbosity='-qq',
1417 error_re='.*cannot also have name \'vendorB\'',
1420 conv.logs[2].check('imported', (
1421 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1422 ('/%(trunk)s/overlapping-branch', 'A'),
1425 if len(conv.logs) != 2:
1426 raise Failure()
1429 class PhoenixBranch(Cvs2SvnTestCase):
1430 "convert a branch file rooted in a 'dead' revision"
1432 def __init__(self, **kw):
1433 Cvs2SvnTestCase.__init__(self, 'phoenix', **kw)
1435 def run(self, sbox):
1436 conv = self.ensure_conversion()
1437 conv.logs[8].check('This file was supplied by Jack Moffitt', (
1438 ('/%(branches)s/volsung_20010721', 'A'),
1439 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1441 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1442 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1446 ###TODO: We check for 4 changed paths here to accomodate creating tags
1447 ###and branches in rev 1, but that will change, so this will
1448 ###eventually change back.
1449 @Cvs2SvnTestFunction
1450 def ctrl_char_in_log():
1451 "handle a control char in a log message"
1452 # This was issue #1106.
1453 rev = 2
1454 conv = ensure_conversion('ctrl-char-in-log')
1455 conv.logs[rev].check_changes((
1456 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1458 if conv.logs[rev].msg.find('\x04') < 0:
1459 raise Failure(
1460 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1463 @Cvs2SvnTestFunction
1464 def overdead():
1465 "handle tags rooted in a redeleted revision"
1466 conv = ensure_conversion('overdead')
1469 class NoTrunkPrune(Cvs2SvnTestCase):
1470 "ensure that trunk doesn't get pruned"
1472 def __init__(self, **kw):
1473 Cvs2SvnTestCase.__init__(self, 'overdead', **kw)
1475 def run(self, sbox):
1476 conv = self.ensure_conversion()
1477 for rev in conv.logs.keys():
1478 rev_logs = conv.logs[rev]
1479 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1480 raise Failure()
1483 @Cvs2SvnTestFunction
1484 def double_delete():
1485 "file deleted twice, in the root of the repository"
1486 # This really tests several things: how we handle a file that's
1487 # removed (state 'dead') in two successive revisions; how we
1488 # handle a file in the root of the repository (there were some
1489 # bugs in cvs2svn's svn path construction for top-level files); and
1490 # the --no-prune option.
1491 conv = ensure_conversion(
1492 'double-delete', args=['--trunk-only', '--no-prune'])
1494 path = '/%(trunk)s/twice-removed'
1495 rev = 2
1496 conv.logs[rev].check('Updated CVS', (
1497 (path, 'A'),
1499 conv.logs[rev + 1].check('Remove this file for the first time.', (
1500 (path, 'D'),
1502 conv.logs[rev + 2].check('Remove this file for the second time,', (
1506 @Cvs2SvnTestFunction
1507 def split_branch():
1508 "branch created from both trunk and another branch"
1509 # See test-data/split-branch-cvsrepos/README.
1511 # The conversion will fail if the bug is present, and
1512 # ensure_conversion will raise Failure.
1513 conv = ensure_conversion('split-branch')
1516 @Cvs2SvnTestFunction
1517 def resync_misgroups():
1518 "resyncing should not misorder commit groups"
1519 # See test-data/resync-misgroups-cvsrepos/README.
1521 # The conversion will fail if the bug is present, and
1522 # ensure_conversion will raise Failure.
1523 conv = ensure_conversion('resync-misgroups')
1526 class TaggedBranchAndTrunk(Cvs2SvnTestCase):
1527 "allow tags with mixed trunk and branch sources"
1529 def __init__(self, **kw):
1530 Cvs2SvnTestCase.__init__(self, 'tagged-branch-n-trunk', **kw)
1532 def run(self, sbox):
1533 conv = self.ensure_conversion()
1535 tags = conv.symbols.get('tags', 'tags')
1537 a_path = conv.get_wc(tags, 'some-tag', 'a.txt')
1538 b_path = conv.get_wc(tags, 'some-tag', 'b.txt')
1539 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1540 raise Failure()
1541 if (open(a_path, 'r').read().find('1.24') == -1) \
1542 or (open(b_path, 'r').read().find('1.5') == -1):
1543 raise Failure()
1546 @Cvs2SvnTestFunction
1547 def enroot_race():
1548 "never use the rev-in-progress as a copy source"
1550 # See issue #1427 and r8544.
1551 conv = ensure_conversion('enroot-race')
1552 rev = 6
1553 conv.logs[rev].check_changes((
1554 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1555 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1556 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1558 conv.logs[rev + 1].check_changes((
1559 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1560 ('/%(trunk)s/proj/a.txt', 'M'),
1561 ('/%(trunk)s/proj/b.txt', 'M'),
1565 @Cvs2SvnTestFunction
1566 def enroot_race_obo():
1567 "do use the last completed rev as a copy source"
1568 conv = ensure_conversion('enroot-race-obo')
1569 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1570 if not len(conv.logs) == 3:
1571 raise Failure()
1574 class BranchDeleteFirst(Cvs2SvnTestCase):
1575 "correctly handle deletion as initial branch action"
1577 def __init__(self, **kw):
1578 Cvs2SvnTestCase.__init__(self, 'branch-delete-first', **kw)
1580 def run(self, sbox):
1581 # See test-data/branch-delete-first-cvsrepos/README.
1583 # The conversion will fail if the bug is present, and
1584 # ensure_conversion would raise Failure.
1585 conv = self.ensure_conversion()
1587 branches = conv.symbols.get('branches', 'branches')
1589 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1590 if conv.path_exists(branches, 'branch-1', 'file'):
1591 raise Failure()
1592 if conv.path_exists(branches, 'branch-2', 'file'):
1593 raise Failure()
1594 if not conv.path_exists(branches, 'branch-3', 'file'):
1595 raise Failure()
1598 @Cvs2SvnTestFunction
1599 def nonascii_cvsignore():
1600 "non ascii files in .cvsignore"
1602 # The output seems to be in the C locale, where it looks like this
1603 # (at least on one test system):
1604 expected = (
1605 'Sp?\\195?\\164tzle\n'
1606 'Cr?\\195?\\168meBr?\\195?\\187l?\\195?\\169e\n'
1607 'Jam?\\195?\\179nIb?\\195?\\169rico\n'
1608 'Am?\\195?\\170ijoas?\\195?\\128Bulh?\\195?\\163oPato\n'
1611 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1612 props = props_for_path(conv.get_wc_tree(), 'trunk/single-files')
1614 if props['svn:ignore'] != expected:
1615 raise Failure()
1618 @Cvs2SvnTestFunction
1619 def nonascii_filenames():
1620 "non ascii files converted incorrectly"
1621 # see issue #1255
1623 # on a en_US.iso-8859-1 machine this test fails with
1624 # svn: Can't recode ...
1626 # as described in the issue
1628 # on a en_US.UTF-8 machine this test fails with
1629 # svn: Malformed XML ...
1631 # which means at least it fails. Unfortunately it won't fail
1632 # with the same error...
1634 # mangle current locale settings so we know we're not running
1635 # a UTF-8 locale (which does not exhibit this problem)
1636 current_locale = locale.getlocale()
1637 new_locale = 'en_US.ISO8859-1'
1638 locale_changed = None
1640 # From http://docs.python.org/lib/module-sys.html
1642 # getfilesystemencoding():
1644 # Return the name of the encoding used to convert Unicode filenames
1645 # into system file names, or None if the system default encoding is
1646 # used. The result value depends on the operating system:
1648 # - On Windows 9x, the encoding is ``mbcs''.
1649 # - On Mac OS X, the encoding is ``utf-8''.
1650 # - On Unix, the encoding is the user's preference according to the
1651 # result of nl_langinfo(CODESET), or None if the
1652 # nl_langinfo(CODESET) failed.
1653 # - On Windows NT+, file names are Unicode natively, so no conversion is
1654 # performed.
1656 # So we're going to skip this test on Mac OS X for now.
1657 if sys.platform == "darwin":
1658 raise svntest.Skip()
1660 try:
1661 # change locale to non-UTF-8 locale to generate latin1 names
1662 locale.setlocale(locale.LC_ALL, # this might be too broad?
1663 new_locale)
1664 locale_changed = 1
1665 except locale.Error:
1666 raise svntest.Skip()
1668 try:
1669 srcrepos_path = os.path.join(test_data_dir, 'non-ascii-cvsrepos')
1670 dstrepos_path = os.path.join(test_data_dir, 'non-ascii-copy-cvsrepos')
1671 if not os.path.exists(dstrepos_path):
1672 # create repos from existing main repos
1673 shutil.copytree(srcrepos_path, dstrepos_path)
1674 base_path = os.path.join(dstrepos_path, 'single-files')
1675 os.remove(os.path.join(base_path, '.cvsignore,v'))
1676 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1677 os.path.join(base_path, 'two\366uick,v'))
1678 new_path = os.path.join(dstrepos_path, 'single\366files')
1679 os.rename(base_path, new_path)
1681 conv = ensure_conversion('non-ascii-copy', args=['--encoding=latin1'])
1682 finally:
1683 if locale_changed:
1684 locale.setlocale(locale.LC_ALL, current_locale)
1685 safe_rmtree(dstrepos_path)
1688 class UnicodeTest(Cvs2SvnTestCase):
1689 "metadata contains Unicode"
1691 warning_pattern = r'ERROR\: There were warnings converting .* messages'
1693 def __init__(self, name, warning_expected, **kw):
1694 if warning_expected:
1695 error_re = self.warning_pattern
1696 else:
1697 error_re = None
1699 Cvs2SvnTestCase.__init__(self, name, error_re=error_re, **kw)
1700 self.warning_expected = warning_expected
1702 def run(self, sbox):
1703 try:
1704 # ensure the availability of the "utf_8" encoding:
1705 u'a'.encode('utf_8').decode('utf_8')
1706 except LookupError:
1707 raise svntest.Skip()
1709 self.ensure_conversion()
1712 class UnicodeAuthor(UnicodeTest):
1713 "author name contains Unicode"
1715 def __init__(self, warning_expected, **kw):
1716 UnicodeTest.__init__(self, 'unicode-author', warning_expected, **kw)
1719 class UnicodeLog(UnicodeTest):
1720 "log message contains Unicode"
1722 def __init__(self, warning_expected, **kw):
1723 UnicodeTest.__init__(self, 'unicode-log', warning_expected, **kw)
1726 @Cvs2SvnTestFunction
1727 def vendor_branch_sameness():
1728 "avoid spurious changes for initial revs"
1729 conv = ensure_conversion(
1730 'vendor-branch-sameness', args=['--keep-trivial-imports']
1733 # The following files are in this repository:
1735 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1736 # the same contents, the file's default branch is 1.1.1,
1737 # and both revisions are in state 'Exp'.
1739 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1740 # 1.1 (the addition of a line of text).
1742 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1744 # d.txt: This file was created by 'cvs add' instead of import, so
1745 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1746 # The timestamp on the add is exactly the same as for the
1747 # imports of the other files.
1749 # e.txt: Like a.txt, except that the log message for revision 1.1
1750 # is not the standard import log message.
1752 # (Aside from e.txt, the log messages for the same revisions are the
1753 # same in all files.)
1755 # We expect that only a.txt is recognized as an import whose 1.1
1756 # revision can be omitted. The other files should be added on trunk
1757 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1758 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1759 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1760 # and c.txt should be 'D'eleted.
1762 rev = 2
1763 conv.logs[rev].check('Initial revision', (
1764 ('/%(trunk)s/proj', 'A'),
1765 ('/%(trunk)s/proj/b.txt', 'A'),
1766 ('/%(trunk)s/proj/c.txt', 'A'),
1767 ('/%(trunk)s/proj/d.txt', 'A'),
1770 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1771 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1772 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1775 conv.logs[rev + 2].check('First vendor branch revision.', (
1776 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1777 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1778 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1781 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1782 'to compensate for changes in r4,', (
1783 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1784 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1785 ('/%(trunk)s/proj/c.txt', 'D'),
1788 rev = 7
1789 conv.logs[rev].check('This log message is not the standard', (
1790 ('/%(trunk)s/proj/e.txt', 'A'),
1793 conv.logs[rev + 2].check('First vendor branch revision', (
1794 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1797 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1798 'to compensate for changes in r9,', (
1799 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1803 @Cvs2SvnTestFunction
1804 def vendor_branch_trunk_only():
1805 "handle vendor branches with --trunk-only"
1806 conv = ensure_conversion('vendor-branch-sameness', args=['--trunk-only'])
1808 rev = 2
1809 conv.logs[rev].check('Initial revision', (
1810 ('/%(trunk)s/proj', 'A'),
1811 ('/%(trunk)s/proj/b.txt', 'A'),
1812 ('/%(trunk)s/proj/c.txt', 'A'),
1813 ('/%(trunk)s/proj/d.txt', 'A'),
1816 conv.logs[rev + 1].check('First vendor branch revision', (
1817 ('/%(trunk)s/proj/a.txt', 'A'),
1818 ('/%(trunk)s/proj/b.txt', 'M'),
1819 ('/%(trunk)s/proj/c.txt', 'D'),
1822 conv.logs[rev + 2].check('This log message is not the standard', (
1823 ('/%(trunk)s/proj/e.txt', 'A'),
1826 conv.logs[rev + 3].check('First vendor branch revision', (
1827 ('/%(trunk)s/proj/e.txt', 'M'),
1831 @Cvs2SvnTestFunction
1832 def default_branches():
1833 "handle default branches correctly"
1834 conv = ensure_conversion('default-branches')
1836 # There are seven files in the repository:
1838 # a.txt:
1839 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1840 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1841 # committed (thus losing the default branch "1.1.1"), then
1842 # 1.1.1.4 was imported. All vendor import release tags are
1843 # still present.
1845 # b.txt:
1846 # Like a.txt, but without rev 1.2.
1848 # c.txt:
1849 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1851 # d.txt:
1852 # Same as the previous two, but 1.1.1 branch is unlabeled.
1854 # e.txt:
1855 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1857 # deleted-on-vendor-branch.txt,v:
1858 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1860 # added-then-imported.txt,v:
1861 # Added with 'cvs add' to create 1.1, then imported with
1862 # completely different contents to create 1.1.1.1, therefore
1863 # never had a default branch.
1866 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1867 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1868 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1869 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1870 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1871 ('/%(branches)s/vbranchA', 'A'),
1872 ('/%(branches)s/vbranchA/proj', 'A'),
1873 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1874 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1875 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1876 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1879 conv.logs[3].check("This commit was generated by cvs2svn "
1880 "to compensate for changes in r2,", (
1881 ('/%(trunk)s/proj', 'A'),
1882 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1883 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1884 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1885 ('/%(trunk)s/proj/d.txt '
1886 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1887 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1888 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1889 ('/%(trunk)s/proj/e.txt '
1890 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1893 conv.logs[4].check(sym_log_msg('vtag-1',1), (
1894 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1895 ('/%(tags)s/vtag-1/proj/d.txt '
1896 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1899 conv.logs[5].check("Import (vbranchA, vtag-2).", (
1900 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1901 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1902 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1903 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1904 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1905 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1908 conv.logs[6].check("This commit was generated by cvs2svn "
1909 "to compensate for changes in r5,", (
1910 ('/%(trunk)s/proj/a.txt '
1911 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1912 ('/%(trunk)s/proj/b.txt '
1913 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1914 ('/%(trunk)s/proj/c.txt '
1915 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1916 ('/%(trunk)s/proj/d.txt '
1917 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1918 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1919 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1920 'R'),
1921 ('/%(trunk)s/proj/e.txt '
1922 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1925 conv.logs[7].check(sym_log_msg('vtag-2',1), (
1926 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1927 ('/%(tags)s/vtag-2/proj/d.txt '
1928 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1931 conv.logs[8].check("Import (vbranchA, vtag-3).", (
1932 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1933 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1934 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1935 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1936 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1937 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1940 conv.logs[9].check("This commit was generated by cvs2svn "
1941 "to compensate for changes in r8,", (
1942 ('/%(trunk)s/proj/a.txt '
1943 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1944 ('/%(trunk)s/proj/b.txt '
1945 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1946 ('/%(trunk)s/proj/c.txt '
1947 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1948 ('/%(trunk)s/proj/d.txt '
1949 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1950 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1951 ('/%(trunk)s/proj/e.txt '
1952 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1955 conv.logs[10].check(sym_log_msg('vtag-3',1), (
1956 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1957 ('/%(tags)s/vtag-3/proj/d.txt '
1958 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1959 ('/%(tags)s/vtag-3/proj/e.txt '
1960 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1963 conv.logs[11].check("First regular commit, to a.txt, on vtag-3.", (
1964 ('/%(trunk)s/proj/a.txt', 'M'),
1967 conv.logs[12].check("Add a file to the working copy.", (
1968 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1971 conv.logs[13].check(sym_log_msg('vbranchA'), (
1972 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1973 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1976 conv.logs[14].check("Import (vbranchA, vtag-4).", (
1977 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1978 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1979 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1980 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1981 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1982 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1983 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1986 conv.logs[15].check("This commit was generated by cvs2svn "
1987 "to compensate for changes in r14,", (
1988 ('/%(trunk)s/proj/b.txt '
1989 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1990 ('/%(trunk)s/proj/c.txt '
1991 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1992 ('/%(trunk)s/proj/d.txt '
1993 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1994 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1995 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1996 'A'),
1997 ('/%(trunk)s/proj/e.txt '
1998 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
2001 conv.logs[16].check(sym_log_msg('vtag-4',1), (
2002 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
2003 ('/%(tags)s/vtag-4/proj/d.txt '
2004 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
2008 @Cvs2SvnTestFunction
2009 def default_branches_trunk_only():
2010 "handle default branches with --trunk-only"
2012 conv = ensure_conversion('default-branches', args=['--trunk-only'])
2014 conv.logs[2].check("Import (vbranchA, vtag-1).", (
2015 ('/%(trunk)s/proj', 'A'),
2016 ('/%(trunk)s/proj/a.txt', 'A'),
2017 ('/%(trunk)s/proj/b.txt', 'A'),
2018 ('/%(trunk)s/proj/c.txt', 'A'),
2019 ('/%(trunk)s/proj/d.txt', 'A'),
2020 ('/%(trunk)s/proj/e.txt', 'A'),
2021 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2024 conv.logs[3].check("Import (vbranchA, vtag-2).", (
2025 ('/%(trunk)s/proj/a.txt', 'M'),
2026 ('/%(trunk)s/proj/b.txt', 'M'),
2027 ('/%(trunk)s/proj/c.txt', 'M'),
2028 ('/%(trunk)s/proj/d.txt', 'M'),
2029 ('/%(trunk)s/proj/e.txt', 'M'),
2030 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
2033 conv.logs[4].check("Import (vbranchA, vtag-3).", (
2034 ('/%(trunk)s/proj/a.txt', 'M'),
2035 ('/%(trunk)s/proj/b.txt', 'M'),
2036 ('/%(trunk)s/proj/c.txt', 'M'),
2037 ('/%(trunk)s/proj/d.txt', 'M'),
2038 ('/%(trunk)s/proj/e.txt', 'M'),
2039 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
2042 conv.logs[5].check("First regular commit, to a.txt, on vtag-3.", (
2043 ('/%(trunk)s/proj/a.txt', 'M'),
2046 conv.logs[6].check("Add a file to the working copy.", (
2047 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
2050 conv.logs[7].check("Import (vbranchA, vtag-4).", (
2051 ('/%(trunk)s/proj/b.txt', 'M'),
2052 ('/%(trunk)s/proj/c.txt', 'M'),
2053 ('/%(trunk)s/proj/d.txt', 'M'),
2054 ('/%(trunk)s/proj/e.txt', 'M'),
2055 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2059 @Cvs2SvnTestFunction
2060 def default_branch_and_1_2():
2061 "do not allow 1.2 revision with default branch"
2063 conv = ensure_conversion(
2064 'default-branch-and-1-2',
2065 error_re=(
2066 r'.*File \'.*\' has default branch=1\.1\.1 but also a revision 1\.2'
2071 @Cvs2SvnTestFunction
2072 def compose_tag_three_sources():
2073 "compose a tag from three sources"
2074 conv = ensure_conversion('compose-tag-three-sources')
2076 conv.logs[2].check("Add on trunk", (
2077 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
2078 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
2079 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
2080 ('/%(trunk)s/tagged-on-b1', 'A'),
2081 ('/%(trunk)s/tagged-on-b2', 'A'),
2084 conv.logs[3].check(sym_log_msg('b1'), (
2085 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
2088 conv.logs[4].check(sym_log_msg('b2'), (
2089 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
2092 conv.logs[5].check("Commit on branch b1", (
2093 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
2094 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
2095 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
2096 ('/%(branches)s/b1/tagged-on-b1', 'M'),
2097 ('/%(branches)s/b1/tagged-on-b2', 'M'),
2100 conv.logs[6].check("Commit on branch b2", (
2101 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
2102 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
2103 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
2104 ('/%(branches)s/b2/tagged-on-b1', 'M'),
2105 ('/%(branches)s/b2/tagged-on-b2', 'M'),
2108 conv.logs[7].check("Commit again on trunk", (
2109 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
2110 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
2111 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
2112 ('/%(trunk)s/tagged-on-b1', 'M'),
2113 ('/%(trunk)s/tagged-on-b2', 'M'),
2116 conv.logs[8].check(sym_log_msg('T',1), (
2117 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
2118 ('/%(tags)s/T/tagged-on-trunk-1.1 '
2119 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
2120 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:5)', 'R'),
2121 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:6)', 'R'),
2125 @Cvs2SvnTestFunction
2126 def pass5_when_to_fill():
2127 "reserve a svn revnum for a fill only when required"
2128 # The conversion will fail if the bug is present, and
2129 # ensure_conversion would raise Failure.
2130 conv = ensure_conversion('pass5-when-to-fill')
2133 class EmptyTrunk(Cvs2SvnTestCase):
2134 "don't break when the trunk is empty"
2136 def __init__(self, **kw):
2137 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
2139 def run(self, sbox):
2140 # The conversion will fail if the bug is present, and
2141 # ensure_conversion would raise Failure.
2142 conv = self.ensure_conversion()
2145 @Cvs2SvnTestFunction
2146 def no_spurious_svn_commits():
2147 "ensure that we don't create any spurious commits"
2148 conv = ensure_conversion('phoenix')
2150 # Check spurious commit that could be created in
2151 # SVNCommitCreator._pre_commit()
2153 # (When you add a file on a branch, CVS creates a trunk revision
2154 # in state 'dead'. If the log message of that commit is equal to
2155 # the one that CVS generates, we do not ever create a 'fill'
2156 # SVNCommit for it.)
2158 # and spurious commit that could be created in
2159 # SVNCommitCreator._commit()
2161 # (When you add a file on a branch, CVS creates a trunk revision
2162 # in state 'dead'. If the log message of that commit is equal to
2163 # the one that CVS generates, we do not create a primary SVNCommit
2164 # for it.)
2165 conv.logs[17].check('File added on branch xiphophorus', (
2166 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
2169 # Check to make sure that a commit *is* generated:
2170 # (When you add a file on a branch, CVS creates a trunk revision
2171 # in state 'dead'. If the log message of that commit is NOT equal
2172 # to the one that CVS generates, we create a primary SVNCommit to
2173 # serve as a home for the log message in question.
2174 conv.logs[18].check('file added-on-branch2.txt was initially added on '
2175 + 'branch xiphophorus,\nand this log message was tweaked', ())
2177 # Check spurious commit that could be created in
2178 # SVNCommitCreator._commit_symbols().
2179 conv.logs[19].check('This file was also added on branch xiphophorus,', (
2180 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
2184 class PeerPathPruning(Cvs2SvnTestCase):
2185 "make sure that filling prunes paths correctly"
2187 def __init__(self, **kw):
2188 Cvs2SvnTestCase.__init__(self, 'peer-path-pruning', **kw)
2190 def run(self, sbox):
2191 conv = self.ensure_conversion()
2192 conv.logs[6].check(sym_log_msg('BRANCH'), (
2193 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A'),
2194 ('/%(branches)s/BRANCH/bar', 'D'),
2195 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:5)', 'R'),
2199 @Cvs2SvnTestFunction
2200 def invalid_closings_on_trunk():
2201 "verify correct revs are copied to default branches"
2202 # The conversion will fail if the bug is present, and
2203 # ensure_conversion would raise Failure.
2204 conv = ensure_conversion('invalid-closings-on-trunk')
2207 @Cvs2SvnTestFunction
2208 def individual_passes():
2209 "run each pass individually"
2210 conv = ensure_conversion('main')
2211 conv2 = ensure_conversion('main', passbypass=1)
2213 if conv.logs != conv2.logs:
2214 raise Failure()
2217 @Cvs2SvnTestFunction
2218 def resync_bug():
2219 "reveal a big bug in our resync algorithm"
2220 # This will fail if the bug is present
2221 conv = ensure_conversion('resync-bug')
2224 @Cvs2SvnTestFunction
2225 def branch_from_default_branch():
2226 "reveal a bug in our default branch detection code"
2227 conv = ensure_conversion('branch-from-default-branch')
2229 # This revision will be a default branch synchronization only
2230 # if cvs2svn is correctly determining default branch revisions.
2232 # The bug was that cvs2svn was treating revisions on branches off of
2233 # default branches as default branch revisions, resulting in
2234 # incorrectly regarding the branch off of the default branch as a
2235 # non-trunk default branch. Crystal clear? I thought so. See
2236 # issue #42 for more incoherent blathering.
2237 conv.logs[5].check("This commit was generated by cvs2svn", (
2238 ('/%(trunk)s/proj/file.txt '
2239 '(from /%(branches)s/upstream/proj/file.txt:4)', 'R'),
2243 @Cvs2SvnTestFunction
2244 def file_in_attic_too():
2245 "die if a file exists in and out of the attic"
2246 ensure_conversion(
2247 'file-in-attic-too',
2248 error_re=(
2249 r'.*A CVS repository cannot contain both '
2250 r'(.*)' + re.escape(os.sep) + r'(.*) '
2251 + r'and '
2252 r'\1' + re.escape(os.sep) + r'Attic' + re.escape(os.sep) + r'\2'
2257 @Cvs2SvnTestFunction
2258 def retain_file_in_attic_too():
2259 "test --retain-conflicting-attic-files option"
2260 conv = ensure_conversion(
2261 'file-in-attic-too', args=['--retain-conflicting-attic-files'])
2262 if not conv.path_exists('trunk', 'file.txt'):
2263 raise Failure()
2264 if not conv.path_exists('trunk', 'Attic', 'file.txt'):
2265 raise Failure()
2268 @Cvs2SvnTestFunction
2269 def symbolic_name_filling_guide():
2270 "reveal a big bug in our SymbolFillingGuide"
2271 # This will fail if the bug is present
2272 conv = ensure_conversion('symbolic-name-overfill')
2275 # Helpers for tests involving file contents and properties.
2277 class NodeTreeWalkException:
2278 "Exception class for node tree traversals."
2279 pass
2281 def node_for_path(node, path):
2282 "In the tree rooted under SVNTree NODE, return the node at PATH."
2283 if node.name != '__SVN_ROOT_NODE':
2284 raise NodeTreeWalkException()
2285 path = path.strip('/')
2286 components = path.split('/')
2287 for component in components:
2288 node = svntest.tree.get_child(node, component)
2289 return node
2291 # Helper for tests involving properties.
2292 def props_for_path(node, path):
2293 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2294 return node_for_path(node, path).props
2297 class EOLMime(Cvs2SvnPropertiesTestCase):
2298 """eol settings and mime types together
2300 The files are as follows:
2302 trunk/foo.txt: no -kb, mime file says nothing.
2303 trunk/foo.xml: no -kb, mime file says text.
2304 trunk/foo.zip: no -kb, mime file says non-text.
2305 trunk/foo.bin: has -kb, mime file says nothing.
2306 trunk/foo.csv: has -kb, mime file says text.
2307 trunk/foo.dbf: has -kb, mime file says non-text.
2310 def __init__(self, args, **kw):
2311 # TODO: It's a bit klugey to construct this path here. But so far
2312 # there's only one test with a mime.types file. If we have more,
2313 # we should abstract this into some helper, which would be located
2314 # near ensure_conversion(). Note that it is a convention of this
2315 # test suite for a mime.types file to be located in the top level
2316 # of the CVS repository to which it applies.
2317 self.mime_path = os.path.join(
2318 test_data_dir, 'eol-mime-cvsrepos', 'mime.types')
2320 Cvs2SvnPropertiesTestCase.__init__(
2321 self, 'eol-mime',
2322 props_to_test=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2323 args=['--mime-types=%s' % self.mime_path] + args,
2324 **kw)
2327 # We do four conversions. Each time, we pass --mime-types=FILE with
2328 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2329 # Thus there's one conversion with neither flag, one with just the
2330 # former, one with just the latter, and one with both.
2333 # Neither --no-default-eol nor --eol-from-mime-type:
2334 eol_mime1 = EOLMime(
2335 variant=1,
2336 args=[],
2337 expected_props=[
2338 ('trunk/foo.txt', [None, None, None]),
2339 ('trunk/foo.xml', [None, 'text/xml', None]),
2340 ('trunk/foo.zip', [None, 'application/zip', None]),
2341 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2342 ('trunk/foo.csv', [None, 'text/csv', None]),
2343 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2347 # Just --no-default-eol, not --eol-from-mime-type:
2348 eol_mime2 = EOLMime(
2349 variant=2,
2350 args=['--default-eol=native'],
2351 expected_props=[
2352 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2353 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2354 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS]),
2355 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2356 ('trunk/foo.csv', [None, 'text/csv', None]),
2357 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2361 # Just --eol-from-mime-type, not --no-default-eol:
2362 eol_mime3 = EOLMime(
2363 variant=3,
2364 args=['--eol-from-mime-type'],
2365 expected_props=[
2366 ('trunk/foo.txt', [None, None, None]),
2367 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2368 ('trunk/foo.zip', [None, 'application/zip', None]),
2369 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2370 ('trunk/foo.csv', [None, 'text/csv', None]),
2371 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2375 # Both --no-default-eol and --eol-from-mime-type:
2376 eol_mime4 = EOLMime(
2377 variant=4,
2378 args=['--eol-from-mime-type', '--default-eol=native'],
2379 expected_props=[
2380 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2381 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2382 ('trunk/foo.zip', [None, 'application/zip', None]),
2383 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2384 ('trunk/foo.csv', [None, 'text/csv', None]),
2385 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2389 cvs_revnums_off = Cvs2SvnPropertiesTestCase(
2390 'eol-mime',
2391 doc='test non-setting of cvs2svn:cvs-rev property',
2392 args=[],
2393 props_to_test=['cvs2svn:cvs-rev'],
2394 expected_props=[
2395 ('trunk/foo.txt', [None]),
2396 ('trunk/foo.xml', [None]),
2397 ('trunk/foo.zip', [None]),
2398 ('trunk/foo.bin', [None]),
2399 ('trunk/foo.csv', [None]),
2400 ('trunk/foo.dbf', [None]),
2404 cvs_revnums_on = Cvs2SvnPropertiesTestCase(
2405 'eol-mime',
2406 doc='test setting of cvs2svn:cvs-rev property',
2407 args=['--cvs-revnums'],
2408 props_to_test=['cvs2svn:cvs-rev'],
2409 expected_props=[
2410 ('trunk/foo.txt', ['1.2']),
2411 ('trunk/foo.xml', ['1.2']),
2412 ('trunk/foo.zip', ['1.2']),
2413 ('trunk/foo.bin', ['1.2']),
2414 ('trunk/foo.csv', ['1.2']),
2415 ('trunk/foo.dbf', ['1.2']),
2419 keywords = Cvs2SvnPropertiesTestCase(
2420 'keywords',
2421 doc='test setting of svn:keywords property among others',
2422 args=['--default-eol=native'],
2423 props_to_test=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2424 expected_props=[
2425 ('trunk/foo.default', [KEYWORDS, 'native', None]),
2426 ('trunk/foo.kkvl', [KEYWORDS, 'native', None]),
2427 ('trunk/foo.kkv', [KEYWORDS, 'native', None]),
2428 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2429 ('trunk/foo.kk', [None, 'native', None]),
2430 ('trunk/foo.ko', [None, 'native', None]),
2431 ('trunk/foo.kv', [None, 'native', None]),
2435 @Cvs2SvnTestFunction
2436 def ignore():
2437 "test setting of svn:ignore property"
2438 conv = ensure_conversion('cvsignore')
2439 wc_tree = conv.get_wc_tree()
2440 topdir_props = props_for_path(wc_tree, 'trunk/proj')
2441 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
2443 if topdir_props['svn:ignore'] != \
2444 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2445 raise Failure()
2447 if subdir_props['svn:ignore'] != \
2448 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2449 raise Failure()
2452 @Cvs2SvnTestFunction
2453 def requires_cvs():
2454 "test that CVS can still do what RCS can't"
2455 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2456 conv = ensure_conversion(
2457 'requires-cvs', args=['--use-cvs', '--default-eol=native'],
2460 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
2461 cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read()
2463 if atsign_contents[-1:] == "@":
2464 raise Failure()
2465 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
2466 raise Failure()
2468 if not (conv.logs[6].author == "William Lyon Phelps III" and
2469 conv.logs[5].author == "j random"):
2470 raise Failure()
2473 @Cvs2SvnTestFunction
2474 def questionable_branch_names():
2475 "test that we can handle weird branch names"
2476 conv = ensure_conversion('questionable-symbols')
2477 # If the conversion succeeds, then we're okay. We could check the
2478 # actual branch paths, too, but the main thing is to know that the
2479 # conversion doesn't fail.
2482 @Cvs2SvnTestFunction
2483 def questionable_tag_names():
2484 "test that we can handle weird tag names"
2485 conv = ensure_conversion('questionable-symbols')
2486 conv.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2487 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2489 conv.find_tag_log('TagWith/Backslash_E').check(
2490 sym_log_msg('TagWith/Backslash_E',1),
2492 ('/%(tags)s/TagWith', 'A'),
2493 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2496 conv.find_tag_log('TagWith/Slash_Z').check(
2497 sym_log_msg('TagWith/Slash_Z',1),
2499 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2504 @Cvs2SvnTestFunction
2505 def revision_reorder_bug():
2506 "reveal a bug that reorders file revisions"
2507 conv = ensure_conversion('revision-reorder-bug')
2508 # If the conversion succeeds, then we're okay. We could check the
2509 # actual revisions, too, but the main thing is to know that the
2510 # conversion doesn't fail.
2513 @Cvs2SvnTestFunction
2514 def exclude():
2515 "test that exclude really excludes everything"
2516 conv = ensure_conversion('main', args=['--exclude=.*'])
2517 for log in conv.logs.values():
2518 for item in log.changed_paths.keys():
2519 if item.startswith('/branches/') or item.startswith('/tags/'):
2520 raise Failure()
2523 @Cvs2SvnTestFunction
2524 def vendor_branch_delete_add():
2525 "add trunk file that was deleted on vendor branch"
2526 # This will error if the bug is present
2527 conv = ensure_conversion('vendor-branch-delete-add')
2530 @Cvs2SvnTestFunction
2531 def resync_pass2_pull_forward():
2532 "ensure pass2 doesn't pull rev too far forward"
2533 conv = ensure_conversion('resync-pass2-pull-forward')
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 @Cvs2SvnTestFunction
2540 def native_eol():
2541 "only LFs for svn:eol-style=native files"
2542 conv = ensure_conversion('native-eol', args=['--default-eol=native'])
2543 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
2544 conv.repos)
2545 # Verify that all files in the dump have LF EOLs. We're actually
2546 # testing the whole dump file, but the dump file itself only uses
2547 # LF EOLs, so we're safe.
2548 for line in lines:
2549 if line[-1] != '\n' or line[:-1].find('\r') != -1:
2550 raise Failure()
2553 @Cvs2SvnTestFunction
2554 def double_fill():
2555 "reveal a bug that created a branch twice"
2556 conv = ensure_conversion('double-fill')
2557 # If the conversion succeeds, then we're okay. We could check the
2558 # actual revisions, too, but the main thing is to know that the
2559 # conversion doesn't fail.
2562 @XFail_deco()
2563 @Cvs2SvnTestFunction
2564 def double_fill2():
2565 "reveal a second bug that created a branch twice"
2566 conv = ensure_conversion('double-fill2')
2567 conv.logs[6].check_msg(sym_log_msg('BRANCH1'))
2568 conv.logs[7].check_msg(sym_log_msg('BRANCH2'))
2569 try:
2570 # This check should fail:
2571 conv.logs[8].check_msg(sym_log_msg('BRANCH2'))
2572 except Failure:
2573 pass
2574 else:
2575 raise Failure('Symbol filled twice in a row')
2578 @Cvs2SvnTestFunction
2579 def resync_pass2_push_backward():
2580 "ensure pass2 doesn't push rev too far backward"
2581 conv = ensure_conversion('resync-pass2-push-backward')
2582 # If the conversion succeeds, then we're okay. We could check the
2583 # actual revisions, too, but the main thing is to know that the
2584 # conversion doesn't fail.
2587 @Cvs2SvnTestFunction
2588 def double_add():
2589 "reveal a bug that added a branch file twice"
2590 conv = ensure_conversion('double-add')
2591 # If the conversion succeeds, then we're okay. We could check the
2592 # actual revisions, too, but the main thing is to know that the
2593 # conversion doesn't fail.
2596 @Cvs2SvnTestFunction
2597 def bogus_branch_copy():
2598 "reveal a bug that copies a branch file wrongly"
2599 conv = ensure_conversion('bogus-branch-copy')
2600 # If the conversion succeeds, then we're okay. We could check the
2601 # actual revisions, too, but the main thing is to know that the
2602 # conversion doesn't fail.
2605 @Cvs2SvnTestFunction
2606 def nested_ttb_directories():
2607 "require error if ttb directories are not disjoint"
2608 opts_list = [
2609 {'trunk' : 'a', 'branches' : 'a',},
2610 {'trunk' : 'a', 'tags' : 'a',},
2611 {'branches' : 'a', 'tags' : 'a',},
2612 # This option conflicts with the default trunk path:
2613 {'branches' : 'trunk',},
2614 # Try some nested directories:
2615 {'trunk' : 'a', 'branches' : 'a/b',},
2616 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2617 {'branches' : 'a', 'tags' : 'a/b',},
2620 for opts in opts_list:
2621 ensure_conversion(
2622 'main', error_re=r'The following paths are not disjoint\:', **opts
2626 class AutoProps(Cvs2SvnPropertiesTestCase):
2627 """Test auto-props.
2629 The files are as follows:
2631 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2632 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2633 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2634 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2635 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2636 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2637 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2638 trunk/foo.UPCASE1: no -kb, no mime type.
2639 trunk/foo.UPCASE2: no -kb, no mime type.
2642 def __init__(self, args, **kw):
2643 ### TODO: It's a bit klugey to construct this path here. See also
2644 ### the comment in eol_mime().
2645 auto_props_path = os.path.join(
2646 test_data_dir, 'eol-mime-cvsrepos', 'auto-props')
2648 Cvs2SvnPropertiesTestCase.__init__(
2649 self, 'eol-mime',
2650 props_to_test=[
2651 'myprop',
2652 'svn:eol-style',
2653 'svn:mime-type',
2654 'svn:keywords',
2655 'svn:executable',
2657 args=[
2658 '--auto-props=%s' % auto_props_path,
2659 '--eol-from-mime-type'
2660 ] + args,
2661 **kw)
2664 auto_props_ignore_case = AutoProps(
2665 doc="test auto-props",
2666 args=['--default-eol=native'],
2667 expected_props=[
2668 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS, None]),
2669 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS, None]),
2670 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2671 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2672 ('trunk/foo.bin',
2673 ['bin', None, 'application/octet-stream', None, '']),
2674 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2675 ('trunk/foo.dbf',
2676 ['dbf', None, 'application/what-is-dbf', None, None]),
2677 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS, None]),
2678 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS, None]),
2682 @Cvs2SvnTestFunction
2683 def ctrl_char_in_filename():
2684 "do not allow control characters in filenames"
2686 try:
2687 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
2688 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
2689 if os.path.exists(dstrepos_path):
2690 safe_rmtree(dstrepos_path)
2692 # create repos from existing main repos
2693 shutil.copytree(srcrepos_path, dstrepos_path)
2694 base_path = os.path.join(dstrepos_path, 'single-files')
2695 try:
2696 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2697 os.path.join(base_path, 'two\rquick,v'))
2698 except:
2699 # Operating systems that don't allow control characters in
2700 # filenames will hopefully have thrown an exception; in that
2701 # case, just skip this test.
2702 raise svntest.Skip()
2704 conv = ensure_conversion(
2705 'ctrl-char-filename',
2706 error_re=(r'.*Subversion does not allow character .*.'),
2708 finally:
2709 safe_rmtree(dstrepos_path)
2712 @Cvs2SvnTestFunction
2713 def commit_dependencies():
2714 "interleaved and multi-branch commits to same files"
2715 conv = ensure_conversion("commit-dependencies")
2716 conv.logs[2].check('adding', (
2717 ('/%(trunk)s/interleaved', 'A'),
2718 ('/%(trunk)s/interleaved/file1', 'A'),
2719 ('/%(trunk)s/interleaved/file2', 'A'),
2721 conv.logs[3].check('big commit', (
2722 ('/%(trunk)s/interleaved/file1', 'M'),
2723 ('/%(trunk)s/interleaved/file2', 'M'),
2725 conv.logs[4].check('dependant small commit', (
2726 ('/%(trunk)s/interleaved/file1', 'M'),
2728 conv.logs[5].check('adding', (
2729 ('/%(trunk)s/multi-branch', 'A'),
2730 ('/%(trunk)s/multi-branch/file1', 'A'),
2731 ('/%(trunk)s/multi-branch/file2', 'A'),
2733 conv.logs[6].check(sym_log_msg("branch"), (
2734 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2735 ('/%(branches)s/branch/interleaved', 'D'),
2737 conv.logs[7].check('multi-branch-commit', (
2738 ('/%(trunk)s/multi-branch/file1', 'M'),
2739 ('/%(trunk)s/multi-branch/file2', 'M'),
2740 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2741 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2745 @Cvs2SvnTestFunction
2746 def double_branch_delete():
2747 "fill branches before modifying files on them"
2748 conv = ensure_conversion('double-branch-delete')
2750 # Test for issue #102. The file IMarshalledValue.java is branched,
2751 # deleted, readded on the branch, and then deleted again. If the
2752 # fill for the file on the branch is postponed until after the
2753 # modification, the file will end up live on the branch instead of
2754 # dead! Make sure it happens at the right time.
2756 conv.logs[6].check('JBAS-2436 - Adding LGPL Header2', (
2757 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2760 conv.logs[7].check('JBAS-3025 - Removing dependency', (
2761 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2765 @Cvs2SvnTestFunction
2766 def symbol_mismatches():
2767 "error for conflicting tag/branch"
2769 ensure_conversion(
2770 'symbol-mess',
2771 args=['--symbol-default=strict'],
2772 error_re=r'.*Problems determining how symbols should be converted',
2776 @Cvs2SvnTestFunction
2777 def overlook_symbol_mismatches():
2778 "overlook conflicting tag/branch when --trunk-only"
2780 # This is a test for issue #85.
2782 ensure_conversion('symbol-mess', args=['--trunk-only'])
2785 @Cvs2SvnTestFunction
2786 def force_symbols():
2787 "force symbols to be tags/branches"
2789 conv = ensure_conversion(
2790 'symbol-mess',
2791 args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2792 if conv.path_exists('tags', 'BRANCH') \
2793 or not conv.path_exists('branches', 'BRANCH'):
2794 raise Failure()
2795 if not conv.path_exists('tags', 'TAG') \
2796 or conv.path_exists('branches', 'TAG'):
2797 raise Failure()
2798 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2799 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2800 raise Failure()
2801 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2802 or conv.path_exists('branches', 'MOSTLY_TAG'):
2803 raise Failure()
2806 @Cvs2SvnTestFunction
2807 def commit_blocks_tags():
2808 "commit prevents forced tag"
2810 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2811 ensure_conversion(
2812 'symbol-mess',
2813 args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']),
2814 error_re=(
2815 r'.*The following branches cannot be forced to be tags '
2816 r'because they have commits'
2821 @Cvs2SvnTestFunction
2822 def blocked_excludes():
2823 "error for blocked excludes"
2825 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2826 for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']:
2827 try:
2828 ensure_conversion(
2829 'symbol-mess',
2830 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker]))
2831 raise MissingErrorException()
2832 except Failure:
2833 pass
2836 @Cvs2SvnTestFunction
2837 def unblock_blocked_excludes():
2838 "excluding blocker removes blockage"
2840 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2841 for blocker in ['BRANCH', 'COMMIT']:
2842 ensure_conversion(
2843 'symbol-mess',
2844 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker,
2845 '--exclude=BLOCKING_%s' % blocker]))
2848 @Cvs2SvnTestFunction
2849 def regexp_force_symbols():
2850 "force symbols via regular expressions"
2852 conv = ensure_conversion(
2853 'symbol-mess',
2854 args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2855 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2856 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2857 raise Failure()
2858 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2859 or conv.path_exists('branches', 'MOSTLY_TAG'):
2860 raise Failure()
2863 @Cvs2SvnTestFunction
2864 def heuristic_symbol_default():
2865 "test 'heuristic' symbol default"
2867 conv = ensure_conversion(
2868 'symbol-mess', args=['--symbol-default=heuristic'])
2869 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2870 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2871 raise Failure()
2872 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2873 or conv.path_exists('branches', 'MOSTLY_TAG'):
2874 raise Failure()
2877 @Cvs2SvnTestFunction
2878 def branch_symbol_default():
2879 "test 'branch' symbol default"
2881 conv = ensure_conversion(
2882 'symbol-mess', args=['--symbol-default=branch'])
2883 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2884 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2885 raise Failure()
2886 if conv.path_exists('tags', 'MOSTLY_TAG') \
2887 or not conv.path_exists('branches', 'MOSTLY_TAG'):
2888 raise Failure()
2891 @Cvs2SvnTestFunction
2892 def tag_symbol_default():
2893 "test 'tag' symbol default"
2895 conv = ensure_conversion(
2896 'symbol-mess', args=['--symbol-default=tag'])
2897 if not conv.path_exists('tags', 'MOSTLY_BRANCH') \
2898 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
2899 raise Failure()
2900 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2901 or conv.path_exists('branches', 'MOSTLY_TAG'):
2902 raise Failure()
2905 @Cvs2SvnTestFunction
2906 def symbol_transform():
2907 "test --symbol-transform"
2909 conv = ensure_conversion(
2910 'symbol-mess',
2911 args=[
2912 '--symbol-default=heuristic',
2913 '--symbol-transform=BRANCH:branch',
2914 '--symbol-transform=TAG:tag',
2915 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2917 if not conv.path_exists('branches', 'branch'):
2918 raise Failure()
2919 if not conv.path_exists('tags', 'tag'):
2920 raise Failure()
2921 if not conv.path_exists('branches', 'MOSTLY.BRANCH'):
2922 raise Failure()
2923 if not conv.path_exists('tags', 'MOSTLY.TAG'):
2924 raise Failure()
2927 @Cvs2SvnTestFunction
2928 def write_symbol_info():
2929 "test --write-symbol-info"
2931 expected_lines = [
2932 ['0', '.trunk.',
2933 'trunk', 'trunk', '.'],
2934 ['0', 'BLOCKED_BY_UNNAMED',
2935 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2936 ['0', 'BLOCKING_COMMIT',
2937 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2938 ['0', 'BLOCKED_BY_COMMIT',
2939 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2940 ['0', 'BLOCKING_BRANCH',
2941 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2942 ['0', 'BLOCKED_BY_BRANCH',
2943 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2944 ['0', 'MOSTLY_BRANCH',
2945 '.', '.', '.'],
2946 ['0', 'MOSTLY_TAG',
2947 '.', '.', '.'],
2948 ['0', 'BRANCH_WITH_COMMIT',
2949 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2950 ['0', 'BRANCH',
2951 'branch', 'branches/BRANCH', '.trunk.'],
2952 ['0', 'TAG',
2953 'tag', 'tags/TAG', '.trunk.'],
2954 ['0', 'unlabeled-1.1.12.1.2',
2955 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2957 expected_lines.sort()
2959 symbol_info_file = os.path.join(tmp_dir, 'symbol-mess-symbol-info.txt')
2960 try:
2961 ensure_conversion(
2962 'symbol-mess',
2963 args=[
2964 '--symbol-default=strict',
2965 '--write-symbol-info=%s' % (symbol_info_file,),
2966 '--passes=:CollateSymbolsPass',
2969 raise MissingErrorException()
2970 except Failure:
2971 pass
2972 lines = []
2973 comment_re = re.compile(r'^\s*\#')
2974 for l in open(symbol_info_file, 'r'):
2975 if comment_re.match(l):
2976 continue
2977 lines.append(l.strip().split())
2978 lines.sort()
2979 if lines != expected_lines:
2980 s = ['Symbol info incorrect\n']
2981 differ = Differ()
2982 for diffline in differ.compare(
2983 [' '.join(line) + '\n' for line in expected_lines],
2984 [' '.join(line) + '\n' for line in lines],
2986 s.append(diffline)
2987 raise Failure(''.join(s))
2990 @Cvs2SvnTestFunction
2991 def symbol_hints():
2992 "test --symbol-hints for setting branch/tag"
2994 conv = ensure_conversion(
2995 'symbol-mess', symbol_hints_file='symbol-mess-symbol-hints.txt',
2997 if not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2998 raise Failure()
2999 if not conv.path_exists('tags', 'MOSTLY_TAG'):
3000 raise Failure()
3001 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3002 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
3004 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3005 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
3007 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3008 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
3012 @Cvs2SvnTestFunction
3013 def parent_hints():
3014 "test --symbol-hints for setting parent"
3016 conv = ensure_conversion(
3017 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints.txt',
3019 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3020 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3024 @Cvs2SvnTestFunction
3025 def parent_hints_invalid():
3026 "test --symbol-hints with an invalid parent"
3028 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3029 # this symbol hints file sets the preferred parent to BRANCH
3030 # instead:
3031 conv = ensure_conversion(
3032 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-invalid.txt',
3033 error_re=(
3034 r"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
3039 @Cvs2SvnTestFunction
3040 def parent_hints_wildcards():
3041 "test --symbol-hints wildcards"
3043 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3044 # this symbol hints file sets the preferred parent to BRANCH
3045 # instead:
3046 conv = ensure_conversion(
3047 'symbol-mess',
3048 symbol_hints_file='symbol-mess-parent-hints-wildcards.txt',
3050 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3051 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3055 @Cvs2SvnTestFunction
3056 def path_hints():
3057 "test --symbol-hints for setting svn paths"
3059 conv = ensure_conversion(
3060 'symbol-mess', symbol_hints_file='symbol-mess-path-hints.txt',
3062 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3063 ('/trunk', 'A'),
3064 ('/a', 'A'),
3065 ('/a/strange', 'A'),
3066 ('/a/strange/trunk', 'A'),
3067 ('/a/strange/trunk/path', 'A'),
3068 ('/branches', 'A'),
3069 ('/tags', 'A'),
3071 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3072 ('/special', 'A'),
3073 ('/special/tag', 'A'),
3074 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
3076 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3077 ('/special/other', 'A'),
3078 ('/special/other/branch', 'A'),
3079 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
3081 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3082 ('/special/branch', 'A'),
3083 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
3087 @Cvs2SvnTestFunction
3088 def issue_99():
3089 "test problem from issue 99"
3091 conv = ensure_conversion('issue-99')
3094 @Cvs2SvnTestFunction
3095 def issue_100():
3096 "test problem from issue 100"
3098 conv = ensure_conversion('issue-100')
3099 file1 = conv.get_wc('trunk', 'file1.txt')
3100 if file(file1).read() != 'file1.txt<1.2>\n':
3101 raise Failure()
3104 @Cvs2SvnTestFunction
3105 def issue_106():
3106 "test problem from issue 106"
3108 conv = ensure_conversion('issue-106')
3111 @Cvs2SvnTestFunction
3112 def options_option():
3113 "use of the --options option"
3115 conv = ensure_conversion('main', options_file='cvs2svn.options')
3118 @Cvs2SvnTestFunction
3119 def multiproject():
3120 "multiproject conversion"
3122 conv = ensure_conversion(
3123 'main', options_file='cvs2svn-multiproject.options'
3125 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3126 ('/partial-prune', 'A'),
3127 ('/partial-prune/trunk', 'A'),
3128 ('/partial-prune/branches', 'A'),
3129 ('/partial-prune/tags', 'A'),
3130 ('/partial-prune/releases', 'A'),
3134 @Cvs2SvnTestFunction
3135 def crossproject():
3136 "multiproject conversion with cross-project commits"
3138 conv = ensure_conversion(
3139 'main', options_file='cvs2svn-crossproject.options'
3143 @Cvs2SvnTestFunction
3144 def tag_with_no_revision():
3145 "tag defined but revision is deleted"
3147 conv = ensure_conversion('tag-with-no-revision')
3150 @Cvs2SvnTestFunction
3151 def delete_cvsignore():
3152 "svn:ignore should vanish when .cvsignore does"
3154 # This is issue #81.
3156 conv = ensure_conversion('delete-cvsignore')
3158 wc_tree = conv.get_wc_tree()
3159 props = props_for_path(wc_tree, 'trunk/proj')
3161 if props.has_key('svn:ignore'):
3162 raise Failure()
3165 @Cvs2SvnTestFunction
3166 def repeated_deltatext():
3167 "ignore repeated deltatext blocks with warning"
3169 conv = ensure_conversion(
3170 'repeated-deltatext',
3171 verbosity='-qq',
3172 error_re=r'.*Deltatext block for revision 1.1 appeared twice',
3176 @Cvs2SvnTestFunction
3177 def nasty_graphs():
3178 "process some nasty dependency graphs"
3180 # It's not how well the bear can dance, but that the bear can dance
3181 # at all:
3182 conv = ensure_conversion('nasty-graphs')
3185 @XFail_deco()
3186 @Cvs2SvnTestFunction
3187 def tagging_after_delete():
3188 "optimal tag after deleting files"
3190 conv = ensure_conversion('tagging-after-delete')
3192 # tag should be 'clean', no deletes
3193 log = conv.find_tag_log('tag1')
3194 expected = (
3195 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
3197 log.check_changes(expected)
3200 @Cvs2SvnTestFunction
3201 def crossed_branches():
3202 "branches created in inconsistent orders"
3204 conv = ensure_conversion('crossed-branches')
3207 @Cvs2SvnTestFunction
3208 def file_directory_conflict():
3209 "error when filename conflicts with directory name"
3211 conv = ensure_conversion(
3212 'file-directory-conflict',
3213 error_re=r'.*Directory name conflicts with filename',
3217 @Cvs2SvnTestFunction
3218 def attic_directory_conflict():
3219 "error when attic filename conflicts with dirname"
3221 # This tests the problem reported in issue #105.
3223 conv = ensure_conversion(
3224 'attic-directory-conflict',
3225 error_re=r'.*Directory name conflicts with filename',
3229 @Cvs2SvnTestFunction
3230 def use_rcs():
3231 "verify that --use-rcs and --use-internal-co agree"
3233 rcs_conv = ensure_conversion(
3234 'main', args=['--use-rcs', '--default-eol=native'], dumpfile='use-rcs-rcs.dump',
3236 conv = ensure_conversion(
3237 'main', args=['--default-eol=native'], dumpfile='use-rcs-int.dump',
3239 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3240 raise Failure()
3241 rcs_lines = list(open(rcs_conv.dumpfile, 'rb'))
3242 lines = list(open(conv.dumpfile, 'rb'))
3243 # Compare all lines following the repository UUID:
3244 if lines[3:] != rcs_lines[3:]:
3245 raise Failure()
3248 @Cvs2SvnTestFunction
3249 def internal_co_exclude():
3250 "verify that --use-internal-co --exclude=... works"
3252 rcs_conv = ensure_conversion(
3253 'internal-co',
3254 args=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3255 dumpfile='internal-co-exclude-rcs.dump',
3257 conv = ensure_conversion(
3258 'internal-co',
3259 args=['--exclude=BRANCH', '--default-eol=native'],
3260 dumpfile='internal-co-exclude-int.dump',
3262 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3263 raise Failure()
3264 rcs_lines = list(open(rcs_conv.dumpfile, 'rb'))
3265 lines = list(open(conv.dumpfile, 'rb'))
3266 # Compare all lines following the repository UUID:
3267 if lines[3:] != rcs_lines[3:]:
3268 raise Failure()
3271 @Cvs2SvnTestFunction
3272 def internal_co_trunk_only():
3273 "verify that --use-internal-co --trunk-only works"
3275 conv = ensure_conversion(
3276 'internal-co',
3277 args=['--trunk-only', '--default-eol=native'],
3279 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3280 raise Failure()
3283 @Cvs2SvnTestFunction
3284 def leftover_revs():
3285 "check for leftover checked-out revisions"
3287 conv = ensure_conversion(
3288 'leftover-revs',
3289 args=['--exclude=BRANCH', '--default-eol=native'],
3291 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3292 raise Failure()
3295 @Cvs2SvnTestFunction
3296 def requires_internal_co():
3297 "test that internal co can do more than RCS"
3298 # See issues 4, 11 for the bugs whose regression we're testing for.
3299 # Unlike in requires_cvs above, issue 29 is not covered.
3300 conv = ensure_conversion('requires-cvs')
3302 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
3304 if atsign_contents[-1:] == "@":
3305 raise Failure()
3307 if not (conv.logs[6].author == "William Lyon Phelps III" and
3308 conv.logs[5].author == "j random"):
3309 raise Failure()
3312 @Cvs2SvnTestFunction
3313 def internal_co_keywords():
3314 "test that internal co handles keywords correctly"
3315 conv_ic = ensure_conversion('internal-co-keywords',
3316 args=["--keywords-off"])
3317 conv_cvs = ensure_conversion('internal-co-keywords',
3318 args=["--use-cvs", "--keywords-off"])
3320 ko_ic = file(conv_ic.get_wc('trunk', 'dir', 'ko.txt')).read()
3321 ko_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'ko.txt')).read()
3322 kk_ic = file(conv_ic.get_wc('trunk', 'dir', 'kk.txt')).read()
3323 kk_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kk.txt')).read()
3324 kv_ic = file(conv_ic.get_wc('trunk', 'dir', 'kv.txt')).read()
3325 kv_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kv.txt')).read()
3326 # Ensure proper "/Attic" expansion of $Source$ keyword in files
3327 # which are in a deleted state in trunk
3328 del_ic = file(conv_ic.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3329 del_cvs = file(conv_cvs.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3332 if ko_ic != ko_cvs:
3333 raise Failure()
3334 if kk_ic != kk_cvs:
3335 raise Failure()
3336 if del_ic != del_cvs:
3337 raise Failure()
3339 # The date format changed between cvs and co ('/' instead of '-').
3340 # Accept either one:
3341 date_substitution_re = re.compile(r' ([0-9]*)-([0-9]*)-([0-9]*) ')
3342 if kv_ic != kv_cvs \
3343 and date_substitution_re.sub(r' \1/\2/\3 ', kv_ic) != kv_cvs:
3344 raise Failure()
3347 @Cvs2SvnTestFunction
3348 def timestamp_chaos():
3349 "test timestamp adjustments"
3351 conv = ensure_conversion('timestamp-chaos')
3353 # The times are expressed here in UTC:
3354 times = [
3355 '2007-01-01 21:00:00', # Initial commit
3356 '2007-01-01 21:00:00', # revision 1.1 of both files
3357 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3358 '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards
3359 '2007-01-01 22:00:00', # revision 1.3 of both files
3362 # Convert the times to seconds since the epoch, in UTC:
3363 times = [calendar.timegm(svn_strptime(t)) for t in times]
3365 for i in range(len(times)):
3366 if abs(conv.logs[i + 1].date - times[i]) > 0.1:
3367 raise Failure()
3370 @Cvs2SvnTestFunction
3371 def symlinks():
3372 "convert a repository that contains symlinks"
3374 # This is a test for issue #97.
3376 proj = os.path.join(test_data_dir, 'symlinks-cvsrepos', 'proj')
3377 links = [
3379 os.path.join('..', 'file.txt,v'),
3380 os.path.join(proj, 'dir1', 'file.txt,v'),
3383 'dir1',
3384 os.path.join(proj, 'dir2'),
3388 try:
3389 os.symlink
3390 except AttributeError:
3391 # Apparently this OS doesn't support symlinks, so skip test.
3392 raise svntest.Skip()
3394 try:
3395 for (src,dst) in links:
3396 os.symlink(src, dst)
3398 conv = ensure_conversion('symlinks')
3399 conv.logs[2].check('', (
3400 ('/%(trunk)s/proj', 'A'),
3401 ('/%(trunk)s/proj/file.txt', 'A'),
3402 ('/%(trunk)s/proj/dir1', 'A'),
3403 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3404 ('/%(trunk)s/proj/dir2', 'A'),
3405 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3407 finally:
3408 for (src,dst) in links:
3409 os.remove(dst)
3412 @Cvs2SvnTestFunction
3413 def empty_trunk_path():
3414 "allow --trunk to be empty if --trunk-only"
3416 # This is a test for issue #53.
3418 conv = ensure_conversion(
3419 'main', args=['--trunk-only', '--trunk='],
3423 @Cvs2SvnTestFunction
3424 def preferred_parent_cycle():
3425 "handle a cycle in branch parent preferences"
3427 conv = ensure_conversion('preferred-parent-cycle')
3430 @Cvs2SvnTestFunction
3431 def branch_from_empty_dir():
3432 "branch from an empty directory"
3434 conv = ensure_conversion('branch-from-empty-dir')
3437 @Cvs2SvnTestFunction
3438 def trunk_readd():
3439 "add a file on a branch then on trunk"
3441 conv = ensure_conversion('trunk-readd')
3444 @Cvs2SvnTestFunction
3445 def branch_from_deleted_1_1():
3446 "branch from a 1.1 revision that will be deleted"
3448 conv = ensure_conversion('branch-from-deleted-1-1')
3449 conv.logs[5].check('Adding b.txt:1.1.2.1', (
3450 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3452 conv.logs[6].check('Adding b.txt:1.1.4.1', (
3453 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3455 conv.logs[7].check('Adding b.txt:1.2', (
3456 ('/%(trunk)s/proj/b.txt', 'A'),
3459 conv.logs[8].check('Adding c.txt:1.1.2.1', (
3460 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3462 conv.logs[9].check('Adding c.txt:1.1.4.1', (
3463 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3467 @Cvs2SvnTestFunction
3468 def add_on_branch():
3469 "add a file on a branch using newer CVS"
3471 conv = ensure_conversion('add-on-branch')
3472 conv.logs[6].check('Adding b.txt:1.1', (
3473 ('/%(trunk)s/proj/b.txt', 'A'),
3475 conv.logs[7].check('Adding b.txt:1.1.2.2', (
3476 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3478 conv.logs[8].check('Adding c.txt:1.1', (
3479 ('/%(trunk)s/proj/c.txt', 'A'),
3481 conv.logs[9].check('Removing c.txt:1.2', (
3482 ('/%(trunk)s/proj/c.txt', 'D'),
3484 conv.logs[10].check('Adding c.txt:1.2.2.2', (
3485 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3487 conv.logs[11].check('Adding d.txt:1.1', (
3488 ('/%(trunk)s/proj/d.txt', 'A'),
3490 conv.logs[12].check('Adding d.txt:1.1.2.2', (
3491 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3495 @Cvs2SvnTestFunction
3496 def main_git():
3497 "test output in git-fast-import format"
3499 # Note: To test importing into git, do
3501 # ./run-tests <this-test-number>
3502 # rm -rf cvs2svn-tmp/main.git
3503 # git init --bare cvs2svn-tmp/main.git
3504 # cd cvs2svn-tmp/main.git
3505 # cat ../git-{blob,dump}.dat | git fast-import
3507 # Or, to load the dumpfiles separately:
3509 # cat ../git-blob.dat | git fast-import --export-marks=../git-marks.dat
3510 # cat ../git-dump.dat | git fast-import --import-marks=../git-marks.dat
3512 # Then use "gitk --all", "git log", etc. to test the contents of the
3513 # repository or "git clone" to make a non-bare clone.
3515 # We don't have the infrastructure to check that the resulting git
3516 # repository is correct, so we just check that the conversion runs
3517 # to completion:
3518 conv = GitConversion('main', None, [
3519 '--blobfile=cvs2svn-tmp/git-blob.dat',
3520 '--dumpfile=cvs2svn-tmp/git-dump.dat',
3521 '--username=cvs2git',
3522 'test-data/main-cvsrepos',
3526 @Cvs2SvnTestFunction
3527 def main_git2():
3528 "test cvs2git --use-external-blob-generator option"
3530 # See comment in main_git() for more information.
3532 conv = GitConversion('main', None, [
3533 '--use-external-blob-generator',
3534 '--blobfile=cvs2svn-tmp/blobfile.out',
3535 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3536 '--username=cvs2git',
3537 'test-data/main-cvsrepos',
3541 @Cvs2SvnTestFunction
3542 def main_git_merged():
3543 "cvs2git with no blobfile"
3545 # Note: To test importing into git, do
3547 # ./run-tests <this-test-number>
3548 # rm -rf cvs2svn-tmp/main.git
3549 # git init --bare cvs2svn-tmp/main.git
3550 # cd cvs2svn-tmp/main.git
3551 # cat ../git-dump.dat | git fast-import
3553 conv = GitConversion('main', None, [
3554 '--dumpfile=cvs2svn-tmp/git-dump.dat',
3555 '--username=cvs2git',
3556 'test-data/main-cvsrepos',
3560 @Cvs2SvnTestFunction
3561 def main_git2_merged():
3562 "cvs2git external with no blobfile"
3564 # See comment in main_git_merged() for more information.
3566 conv = GitConversion('main', None, [
3567 '--use-external-blob-generator',
3568 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3569 '--username=cvs2git',
3570 'test-data/main-cvsrepos',
3574 @Cvs2SvnTestFunction
3575 def git_options():
3576 "test cvs2git using options file"
3578 conv = GitConversion('main', None, [], options_file='cvs2git.options')
3581 @Cvs2SvnTestFunction
3582 def main_hg():
3583 "output in git-fast-import format with inline data"
3585 # The output should be suitable for import by Mercurial.
3587 # We don't have the infrastructure to check that the resulting
3588 # Mercurial repository is correct, so we just check that the
3589 # conversion runs to completion:
3590 conv = GitConversion('main', None, [], options_file='cvs2hg.options')
3593 @Cvs2SvnTestFunction
3594 def invalid_symbol():
3595 "a symbol with the incorrect format"
3597 conv = ensure_conversion(
3598 'invalid-symbol',
3599 verbosity='-qq',
3600 error_re=r".*branch 'SYMBOL' references invalid revision 1$",
3604 @Cvs2SvnTestFunction
3605 def invalid_symbol_ignore():
3606 "ignore a symbol using a SymbolMapper"
3608 conv = ensure_conversion(
3609 'invalid-symbol', options_file='cvs2svn-ignore.options'
3613 @Cvs2SvnTestFunction
3614 def invalid_symbol_ignore2():
3615 "ignore a symbol using an IgnoreSymbolTransform"
3617 conv = ensure_conversion(
3618 'invalid-symbol', options_file='cvs2svn-ignore2.options'
3622 class EOLVariants(Cvs2SvnTestCase):
3623 "handle various --eol-style options"
3625 eol_style_strings = {
3626 'LF' : '\n',
3627 'CR' : '\r',
3628 'CRLF' : '\r\n',
3629 'native' : '\n',
3632 def __init__(self, eol_style):
3633 self.eol_style = eol_style
3634 self.dumpfile = 'eol-variants-%s.dump' % (self.eol_style,)
3635 Cvs2SvnTestCase.__init__(
3636 self, 'eol-variants', variant=self.eol_style,
3637 dumpfile=self.dumpfile,
3638 args=[
3639 '--default-eol=%s' % (self.eol_style,),
3643 def run(self, sbox):
3644 conv = self.ensure_conversion()
3645 dump_contents = open(conv.dumpfile, 'rb').read()
3646 expected_text = self.eol_style_strings[self.eol_style].join(
3647 ['line 1', 'line 2', '\n\n']
3649 if not dump_contents.endswith(expected_text):
3650 raise Failure()
3653 @Cvs2SvnTestFunction
3654 def no_revs_file():
3655 "handle a file with no revisions (issue #80)"
3657 conv = ensure_conversion('no-revs-file')
3660 @Cvs2SvnTestFunction
3661 def mirror_keyerror_test():
3662 "a case that gave KeyError in SVNRepositoryMirror"
3664 conv = ensure_conversion('mirror-keyerror')
3667 @Cvs2SvnTestFunction
3668 def exclude_ntdb_test():
3669 "exclude a non-trunk default branch"
3671 symbol_info_file = os.path.join(tmp_dir, 'exclude-ntdb-symbol-info.txt')
3672 conv = ensure_conversion(
3673 'exclude-ntdb',
3674 args=[
3675 '--write-symbol-info=%s' % (symbol_info_file,),
3676 '--exclude=branch3',
3677 '--exclude=tag3',
3678 '--exclude=vendortag3',
3679 '--exclude=vendorbranch',
3684 @Cvs2SvnTestFunction
3685 def mirror_keyerror2_test():
3686 "a case that gave KeyError in RepositoryMirror"
3688 conv = ensure_conversion('mirror-keyerror2')
3691 @Cvs2SvnTestFunction
3692 def mirror_keyerror3_test():
3693 "a case that gave KeyError in RepositoryMirror"
3695 conv = ensure_conversion('mirror-keyerror3')
3698 @XFail_deco()
3699 @Cvs2SvnTestFunction
3700 def add_cvsignore_to_branch_test():
3701 "check adding .cvsignore to an existing branch"
3703 # This a test for issue #122.
3705 conv = ensure_conversion('add-cvsignore-to-branch')
3706 wc_tree = conv.get_wc_tree()
3707 trunk_props = props_for_path(wc_tree, 'trunk/dir')
3708 if trunk_props['svn:ignore'] != '*.o\n\n':
3709 raise Failure()
3711 branch_props = props_for_path(wc_tree, 'branches/BRANCH/dir')
3712 if branch_props['svn:ignore'] != '*.o\n\n':
3713 raise Failure()
3716 @Cvs2SvnTestFunction
3717 def missing_deltatext():
3718 "a revision's deltatext is missing"
3720 # This is a type of RCS file corruption that has been observed.
3721 conv = ensure_conversion(
3722 'missing-deltatext',
3723 error_re=(
3724 r"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3729 @Cvs2SvnTestFunction
3730 def transform_unlabeled_branch_name():
3731 "transform name of unlabeled branch"
3733 conv = ensure_conversion(
3734 'unlabeled-branch',
3735 args=[
3736 '--symbol-transform=unlabeled-1.1.4:BRANCH2',
3739 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3740 raise Failure('Branch unlabeled-1.1.4 not excluded')
3741 if not conv.path_exists('branches', 'BRANCH2'):
3742 raise Failure('Branch BRANCH2 not found')
3745 @Cvs2SvnTestFunction
3746 def ignore_unlabeled_branch():
3747 "ignoring an unlabeled branch is not allowed"
3749 conv = ensure_conversion(
3750 'unlabeled-branch',
3751 options_file='cvs2svn-ignore.options',
3752 error_re=(
3753 r"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' "
3754 r"in \'.*\' contains commits"
3759 @Cvs2SvnTestFunction
3760 def exclude_unlabeled_branch():
3761 "exclude unlabeled branch"
3763 conv = ensure_conversion(
3764 'unlabeled-branch',
3765 args=['--exclude=unlabeled-.*'],
3767 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3768 raise Failure('Branch unlabeled-1.1.4 not excluded')
3771 @Cvs2SvnTestFunction
3772 def unlabeled_branch_name_collision():
3773 "transform unlabeled branch to same name as branch"
3775 conv = ensure_conversion(
3776 'unlabeled-branch',
3777 args=[
3778 '--symbol-transform=unlabeled-1.1.4:BRANCH',
3780 error_re=(
3781 r"ERROR\: Symbol name \'BRANCH\' is already used"
3786 @Cvs2SvnTestFunction
3787 def collision_with_unlabeled_branch_name():
3788 "transform branch to same name as unlabeled branch"
3790 conv = ensure_conversion(
3791 'unlabeled-branch',
3792 args=[
3793 '--symbol-transform=BRANCH:unlabeled-1.1.4',
3795 error_re=(
3796 r"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used"
3801 @Cvs2SvnTestFunction
3802 def many_deletes():
3803 "a repo with many removable dead revisions"
3805 conv = ensure_conversion('many-deletes')
3806 conv.logs[5].check('Add files on BRANCH', (
3807 ('/%(branches)s/BRANCH/proj/b.txt', 'A'),
3809 conv.logs[6].check('Add files on BRANCH2', (
3810 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3811 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3812 ('/%(branches)s/BRANCH2/proj/d.txt', 'A'),
3816 cvs_description = Cvs2SvnPropertiesTestCase(
3817 'main',
3818 doc='test handling of CVS file descriptions',
3819 props_to_test=['cvs:description'],
3820 expected_props=[
3821 ('trunk/proj/default', ['This is an example file description.']),
3822 ('trunk/proj/sub1/default', [None]),
3826 @Cvs2SvnTestFunction
3827 def include_empty_directories():
3828 "test --include-empty-directories option"
3830 conv = ensure_conversion(
3831 'empty-directories', args=['--include-empty-directories'],
3833 conv.logs[1].check('Standard project directories', (
3834 ('/%(trunk)s', 'A'),
3835 ('/%(branches)s', 'A'),
3836 ('/%(tags)s', 'A'),
3837 ('/%(trunk)s/root-empty-directory', 'A'),
3838 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3840 conv.logs[3].check('Add b.txt.', (
3841 ('/%(trunk)s/direct', 'A'),
3842 ('/%(trunk)s/direct/b.txt', 'A'),
3843 ('/%(trunk)s/direct/empty-directory', 'A'),
3844 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3846 conv.logs[4].check('Add c.txt.', (
3847 ('/%(trunk)s/indirect', 'A'),
3848 ('/%(trunk)s/indirect/subdirectory', 'A'),
3849 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3850 ('/%(trunk)s/indirect/empty-directory', 'A'),
3851 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3853 conv.logs[5].check('Remove b.txt', (
3854 ('/%(trunk)s/direct', 'D'),
3856 conv.logs[6].check('Remove c.txt', (
3857 ('/%(trunk)s/indirect', 'D'),
3859 conv.logs[7].check('Re-add b.txt.', (
3860 ('/%(trunk)s/direct', 'A'),
3861 ('/%(trunk)s/direct/b.txt', 'A'),
3862 ('/%(trunk)s/direct/empty-directory', 'A'),
3863 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3865 conv.logs[8].check('Re-add c.txt.', (
3866 ('/%(trunk)s/indirect', 'A'),
3867 ('/%(trunk)s/indirect/subdirectory', 'A'),
3868 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3869 ('/%(trunk)s/indirect/empty-directory', 'A'),
3870 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3872 conv.logs[9].check('This commit was manufactured', (
3873 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3875 conv.logs[10].check('This commit was manufactured', (
3876 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3878 conv.logs[11].check('Import d.txt.', (
3879 ('/%(branches)s/VENDORBRANCH', 'A'),
3880 ('/%(branches)s/VENDORBRANCH/import', 'A'),
3881 ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'),
3882 ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'),
3883 ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory',
3884 'A'),
3885 ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'),
3886 ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory',
3887 'A'),
3889 conv.logs[12].check('This commit was generated', (
3890 ('/%(trunk)s/import', 'A'),
3891 ('/%(trunk)s/import/d.txt '
3892 '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'),
3893 ('/%(trunk)s/import/empty-directory', 'A'),
3894 ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'),
3898 @Cvs2SvnTestFunction
3899 def include_empty_directories_no_prune():
3900 "test --include-empty-directories with --no-prune"
3902 conv = ensure_conversion(
3903 'empty-directories', args=['--include-empty-directories', '--no-prune'],
3905 conv.logs[1].check('Standard project directories', (
3906 ('/%(trunk)s', 'A'),
3907 ('/%(branches)s', 'A'),
3908 ('/%(tags)s', 'A'),
3909 ('/%(trunk)s/root-empty-directory', 'A'),
3910 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3912 conv.logs[3].check('Add b.txt.', (
3913 ('/%(trunk)s/direct', 'A'),
3914 ('/%(trunk)s/direct/b.txt', 'A'),
3915 ('/%(trunk)s/direct/empty-directory', 'A'),
3916 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3918 conv.logs[4].check('Add c.txt.', (
3919 ('/%(trunk)s/indirect', 'A'),
3920 ('/%(trunk)s/indirect/subdirectory', 'A'),
3921 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3922 ('/%(trunk)s/indirect/empty-directory', 'A'),
3923 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3925 conv.logs[5].check('Remove b.txt', (
3926 ('/%(trunk)s/direct/b.txt', 'D'),
3928 conv.logs[6].check('Remove c.txt', (
3929 ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'),
3931 conv.logs[7].check('Re-add b.txt.', (
3932 ('/%(trunk)s/direct/b.txt', 'A'),
3934 conv.logs[8].check('Re-add c.txt.', (
3935 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3937 conv.logs[9].check('This commit was manufactured', (
3938 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3940 conv.logs[10].check('This commit was manufactured', (
3941 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3945 @Cvs2SvnTestFunction
3946 def exclude_symbol_default():
3947 "test 'exclude' symbol default"
3949 conv = ensure_conversion(
3950 'symbol-mess', args=['--symbol-default=exclude'])
3951 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
3952 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
3953 raise Failure()
3954 if conv.path_exists('tags', 'MOSTLY_TAG') \
3955 or conv.path_exists('branches', 'MOSTLY_TAG'):
3956 raise Failure()
3959 @Cvs2SvnTestFunction
3960 def add_on_branch2():
3961 "another add-on-branch test case"
3963 conv = ensure_conversion('add-on-branch2')
3964 if len(conv.logs) != 2:
3965 raise Failure()
3966 conv.logs[2].check('add file on branch', (
3967 ('/%(branches)s/BRANCH', 'A'),
3968 ('/%(branches)s/BRANCH/file1', 'A'),
3972 @Cvs2SvnTestFunction
3973 def branch_from_vendor_branch():
3974 "branch from vendor branch"
3976 ensure_conversion(
3977 'branch-from-vendor-branch',
3978 symbol_hints_file='branch-from-vendor-branch-symbol-hints.txt',
3982 @Cvs2SvnTestFunction
3983 def strange_default_branch():
3984 "default branch too deep in the hierarchy"
3986 ensure_conversion(
3987 'strange-default-branch',
3988 error_re=(
3989 r'ERROR\: The default branch 1\.2\.4\.3\.2\.1\.2 '
3990 r'in file .* is not a top-level branch'
3995 @Cvs2SvnTestFunction
3996 def move_parent():
3997 "graft onto preferred parent that was itself moved"
3999 conv = ensure_conversion(
4000 'move-parent',
4002 conv.logs[2].check('first', (
4003 ('/%(trunk)s/file1', 'A'),
4004 ('/%(trunk)s/file2', 'A'),
4006 conv.logs[3].check('This commit was manufactured', (
4007 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
4009 conv.logs[4].check('second', (
4010 ('/%(branches)s/b2/file1', 'M'),
4012 conv.logs[5].check('This commit was manufactured', (
4013 ('/%(branches)s/b1 (from /%(branches)s/b2:4)', 'A'),
4016 # b2 and b1 are equally good parents for b3, so accept either one.
4017 # (Currently, cvs2svn chooses b1 as the preferred parent because it
4018 # comes earlier than b2 in alphabetical order.)
4019 try:
4020 conv.logs[6].check('This commit was manufactured', (
4021 ('/%(branches)s/b3 (from /%(branches)s/b1:5)', 'A'),
4023 except Failure:
4024 conv.logs[6].check('This commit was manufactured', (
4025 ('/%(branches)s/b3 (from /%(branches)s/b2:4)', 'A'),
4029 @Cvs2SvnTestFunction
4030 def log_message_eols():
4031 "nonstandard EOLs in log messages"
4033 conv = ensure_conversion(
4034 'log-message-eols',
4036 conv.logs[2].check('The CRLF at the end of this line\nshould', (
4037 ('/%(trunk)s/lottalogs', 'A'),
4039 conv.logs[3].check('The CR at the end of this line\nshould', (
4040 ('/%(trunk)s/lottalogs', 'M'),
4044 @Cvs2SvnTestFunction
4045 def missing_vendor_branch():
4046 "default branch not present in RCS file"
4048 conv = ensure_conversion(
4049 'missing-vendor-branch',
4050 verbosity='-qq',
4051 error_re=r'.*vendor branch \'1\.1\.1\' is not present in file and will be ignored',
4055 @Cvs2SvnTestFunction
4056 def newphrases():
4057 "newphrases in RCS files"
4059 ensure_conversion(
4060 'newphrases',
4064 @Cvs2SvnTestFunction
4065 def vendor_1_1_not_root():
4066 "supposed vendor 1.1 commit is not a root commit"
4068 ensure_conversion(
4069 'vendor-1-1-non-root',
4073 ########################################################################
4074 # Run the tests
4076 # list all tests here, starting with None:
4077 test_list = [
4078 None,
4079 # 1:
4080 show_usage,
4081 cvs2svn_manpage,
4082 cvs2git_manpage,
4083 cvs2hg_manpage,
4084 attr_exec,
4085 space_fname,
4086 two_quick,
4087 PruneWithCare(),
4088 PruneWithCare(variant=1, trunk='a', branches='b', tags='c'),
4089 # 10:
4090 PruneWithCare(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4091 PruneWithCare(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4092 interleaved_commits,
4093 simple_commits,
4094 SimpleTags(),
4095 SimpleTags(variant=1, trunk='a', branches='b', tags='c'),
4096 SimpleTags(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4097 SimpleTags(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4098 simple_branch_commits,
4099 mixed_time_tag,
4100 # 20:
4101 mixed_time_branch_with_added_file,
4102 mixed_commit,
4103 split_time_branch,
4104 bogus_tag,
4105 overlapping_branch,
4106 PhoenixBranch(),
4107 PhoenixBranch(variant=1, trunk='a/1', branches='b/1', tags='c/1'),
4108 ctrl_char_in_log,
4109 overdead,
4110 NoTrunkPrune(),
4111 # 30:
4112 NoTrunkPrune(variant=1, trunk='a', branches='b', tags='c'),
4113 NoTrunkPrune(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4114 NoTrunkPrune(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4115 double_delete,
4116 split_branch,
4117 resync_misgroups,
4118 TaggedBranchAndTrunk(),
4119 TaggedBranchAndTrunk(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4120 enroot_race,
4121 enroot_race_obo,
4122 # 40:
4123 BranchDeleteFirst(),
4124 BranchDeleteFirst(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4125 nonascii_cvsignore,
4126 nonascii_filenames,
4127 UnicodeAuthor(
4128 warning_expected=1),
4129 UnicodeAuthor(
4130 warning_expected=0,
4131 variant='encoding', args=['--encoding=utf_8']),
4132 UnicodeAuthor(
4133 warning_expected=0,
4134 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
4135 UnicodeLog(
4136 warning_expected=1),
4137 UnicodeLog(
4138 warning_expected=0,
4139 variant='encoding', args=['--encoding=utf_8']),
4140 UnicodeLog(
4141 warning_expected=0,
4142 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
4143 # 50:
4144 vendor_branch_sameness,
4145 vendor_branch_trunk_only,
4146 default_branches,
4147 default_branches_trunk_only,
4148 default_branch_and_1_2,
4149 compose_tag_three_sources,
4150 pass5_when_to_fill,
4151 PeerPathPruning(),
4152 PeerPathPruning(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4153 EmptyTrunk(),
4154 # 60:
4155 EmptyTrunk(variant=1, trunk='a', branches='b', tags='c'),
4156 EmptyTrunk(variant=2, trunk='a/1', branches='a/2', tags='a/3'),
4157 no_spurious_svn_commits,
4158 invalid_closings_on_trunk,
4159 individual_passes,
4160 resync_bug,
4161 branch_from_default_branch,
4162 file_in_attic_too,
4163 retain_file_in_attic_too,
4164 symbolic_name_filling_guide,
4165 # 70:
4166 eol_mime1,
4167 eol_mime2,
4168 eol_mime3,
4169 eol_mime4,
4170 cvs_revnums_off,
4171 cvs_revnums_on,
4172 keywords,
4173 ignore,
4174 requires_cvs,
4175 questionable_branch_names,
4176 # 80:
4177 questionable_tag_names,
4178 revision_reorder_bug,
4179 exclude,
4180 vendor_branch_delete_add,
4181 resync_pass2_pull_forward,
4182 native_eol,
4183 double_fill,
4184 double_fill2,
4185 resync_pass2_push_backward,
4186 double_add,
4187 # 90:
4188 bogus_branch_copy,
4189 nested_ttb_directories,
4190 auto_props_ignore_case,
4191 ctrl_char_in_filename,
4192 commit_dependencies,
4193 show_help_passes,
4194 multiple_tags,
4195 multiply_defined_symbols,
4196 multiply_defined_symbols_renamed,
4197 multiply_defined_symbols_ignored,
4198 # 100:
4199 repeatedly_defined_symbols,
4200 double_branch_delete,
4201 symbol_mismatches,
4202 overlook_symbol_mismatches,
4203 force_symbols,
4204 commit_blocks_tags,
4205 blocked_excludes,
4206 unblock_blocked_excludes,
4207 regexp_force_symbols,
4208 heuristic_symbol_default,
4209 # 110:
4210 branch_symbol_default,
4211 tag_symbol_default,
4212 symbol_transform,
4213 write_symbol_info,
4214 symbol_hints,
4215 parent_hints,
4216 parent_hints_invalid,
4217 parent_hints_wildcards,
4218 path_hints,
4219 issue_99,
4220 # 120:
4221 issue_100,
4222 issue_106,
4223 options_option,
4224 multiproject,
4225 crossproject,
4226 tag_with_no_revision,
4227 delete_cvsignore,
4228 repeated_deltatext,
4229 nasty_graphs,
4230 tagging_after_delete,
4231 # 130:
4232 crossed_branches,
4233 file_directory_conflict,
4234 attic_directory_conflict,
4235 use_rcs,
4236 internal_co_exclude,
4237 internal_co_trunk_only,
4238 internal_co_keywords,
4239 leftover_revs,
4240 requires_internal_co,
4241 timestamp_chaos,
4242 # 140:
4243 symlinks,
4244 empty_trunk_path,
4245 preferred_parent_cycle,
4246 branch_from_empty_dir,
4247 trunk_readd,
4248 branch_from_deleted_1_1,
4249 add_on_branch,
4250 main_git,
4251 main_git2,
4252 main_git_merged,
4253 # 150:
4254 main_git2_merged,
4255 git_options,
4256 main_hg,
4257 invalid_symbol,
4258 invalid_symbol_ignore,
4259 invalid_symbol_ignore2,
4260 EOLVariants('LF'),
4261 EOLVariants('CR'),
4262 EOLVariants('CRLF'),
4263 EOLVariants('native'),
4264 # 160:
4265 no_revs_file,
4266 mirror_keyerror_test,
4267 exclude_ntdb_test,
4268 mirror_keyerror2_test,
4269 mirror_keyerror3_test,
4270 add_cvsignore_to_branch_test,
4271 missing_deltatext,
4272 transform_unlabeled_branch_name,
4273 ignore_unlabeled_branch,
4274 exclude_unlabeled_branch,
4275 # 170:
4276 unlabeled_branch_name_collision,
4277 collision_with_unlabeled_branch_name,
4278 many_deletes,
4279 cvs_description,
4280 include_empty_directories,
4281 include_empty_directories_no_prune,
4282 exclude_symbol_default,
4283 add_on_branch2,
4284 branch_from_vendor_branch,
4285 strange_default_branch,
4286 # 180:
4287 move_parent,
4288 log_message_eols,
4289 missing_vendor_branch,
4290 newphrases,
4291 vendor_1_1_not_root,
4294 if __name__ == '__main__':
4296 # Configure the environment for reproducable output from svn, etc.
4297 os.environ["LC_ALL"] = "C"
4299 # Unfortunately, there is no way under Windows to make Subversion
4300 # think that the local time zone is UTC, so we just work in the
4301 # local time zone.
4303 # The Subversion test suite code assumes it's being invoked from
4304 # within a working copy of the Subversion sources, and tries to use
4305 # the binaries in that tree. Since the cvs2svn tree never contains
4306 # a Subversion build, we just use the system's installed binaries.
4307 svntest.main.svn_binary = svn_binary
4308 svntest.main.svnlook_binary = svnlook_binary
4309 svntest.main.svnadmin_binary = svnadmin_binary
4310 svntest.main.svnversion_binary = svnversion_binary
4312 svntest.main.run_tests(test_list)
4313 # NOTREACHED
4316 ### End of file.