cvs2git-example.options: Fix typo and formatting in comment.
[cvs2svn.git] / run-tests.py
bloba67aed405a4ef0cf4587f31119356756e71e2f8c
1 #!/usr/bin/env python
3 # run_tests.py: test suite for cvs2svn
5 # Usage: run_tests.py [-v | --verbose] [list | <num>]
7 # Options:
8 # -v, --verbose
9 # enable verbose output
11 # Arguments (at most one argument is allowed):
12 # list
13 # If the word "list" is passed as an argument, the list of
14 # available tests is printed (but no tests are run).
16 # <num>
17 # If a number is passed as an argument, then only the test
18 # with that number is run.
20 # If no argument is specified, then all tests are run.
22 # Subversion is a tool for revision control.
23 # See http://subversion.tigris.org for more information.
25 # ====================================================================
26 # Copyright (c) 2000-2009 CollabNet. All rights reserved.
28 # This software is licensed as described in the file COPYING, which
29 # you should have received as part of this distribution. The terms
30 # are also available at http://subversion.tigris.org/license-1.html.
31 # If newer versions of this license are posted there, you may use a
32 # newer version instead, at your option.
34 ######################################################################
36 # General modules
37 import sys
38 import shutil
39 import stat
40 import re
41 import os
42 import time
43 import os.path
44 import locale
45 import textwrap
46 import calendar
47 import types
48 try:
49 from hashlib import md5
50 except ImportError:
51 from md5 import md5
52 from difflib import Differ
54 # Make sure that a supported version of Python is being used:
55 if not (0x02040000 <= sys.hexversion < 0x03000000):
56 sys.stderr.write(
57 'error: Python 2, version 2.4 or higher required.\n'
59 sys.exit(1)
61 # This script needs to run in the correct directory. Make sure we're there.
62 if not (os.path.exists('cvs2svn') and os.path.exists('test-data')):
63 sys.stderr.write("error: I need to be run in the directory containing "
64 "'cvs2svn' and 'test-data'.\n")
65 sys.exit(1)
67 # Load the Subversion test framework.
68 import svntest
69 from svntest import Failure
70 from svntest.main import safe_rmtree
71 from svntest.testcase import TestCase
72 from svntest.testcase import XFail_deco
74 # Test if Mercurial >= 1.1 is available.
75 try:
76 from mercurial import context
77 context.memctx
78 have_hg = True
79 except (ImportError, AttributeError):
80 have_hg = False
82 cvs2svn = os.path.abspath('cvs2svn')
83 cvs2git = os.path.abspath('cvs2git')
84 cvs2hg = os.path.abspath('cvs2hg')
86 # We use the installed svn and svnlook binaries, instead of using
87 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
88 # behavior -- or even existence -- of local builds shouldn't affect
89 # the cvs2svn test suite.
90 svn_binary = 'svn'
91 svnlook_binary = 'svnlook'
92 svnadmin_binary = 'svnadmin'
93 svnversion_binary = 'svnversion'
95 test_data_dir = 'test-data'
96 tmp_dir = 'cvs2svn-tmp'
99 #----------------------------------------------------------------------
100 # Helpers.
101 #----------------------------------------------------------------------
104 # The value to expect for svn:keywords if it is set:
105 KEYWORDS = 'Author Date Id Revision'
108 class RunProgramException(Failure):
109 pass
112 class MissingErrorException(Failure):
113 def __init__(self, error_re):
114 Failure.__init__(
115 self, "Test failed because no error matched '%s'" % (error_re,)
119 def run_program(program, error_re, *varargs):
120 """Run PROGRAM with VARARGS, return stdout as a list of lines.
122 If there is any stderr and ERROR_RE is None, raise
123 RunProgramException, and log the stderr lines via
124 svntest.main.logger.info().
126 If ERROR_RE is not None, it is a string regular expression that must
127 match some line of stderr. If it fails to match, raise
128 MissingErrorExpection."""
130 # FIXME: exit_code is currently ignored.
131 exit_code, out, err = svntest.main.run_command(program, 1, 0, *varargs)
133 if error_re:
134 # Specified error expected on stderr.
135 if not err:
136 raise MissingErrorException(error_re)
137 else:
138 for line in err:
139 if re.match(error_re, line):
140 return out
141 raise MissingErrorException(error_re)
142 else:
143 # No stderr allowed.
144 if err:
145 log = svntest.main.logger.info
146 log('%s said:' % program)
147 for line in err:
148 log(' ' + line.rstrip())
149 raise RunProgramException()
151 return out
154 def run_script(script, error_re, *varargs):
155 """Run Python script SCRIPT with VARARGS, returning stdout as a list
156 of lines.
158 If there is any stderr and ERROR_RE is None, raise
159 RunProgramException, and log the stderr lines via
160 svntest.main.logger.info().
162 If ERROR_RE is not None, it is a string regular expression that must
163 match some line of stderr. If it fails to match, raise
164 MissingErrorException."""
166 # Use the same python that is running this script
167 return run_program(sys.executable, error_re, script, *varargs)
168 # On Windows, for an unknown reason, the cmd.exe process invoked by
169 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
170 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
171 # this. Therefore, the redirection of the output to the .s-revs file fails.
172 # We no longer use the problematic invocation on any system, but this
173 # comment remains to warn about this problem.
176 def run_svn(*varargs):
177 """Run svn with VARARGS; return stdout as a list of lines.
178 If there is any stderr, raise RunProgramException, and log the
179 stderr lines via svntest.main.logger.info()."""
180 return run_program(svn_binary, None, *varargs)
183 def repos_to_url(path_to_svn_repos):
184 """This does what you think it does."""
185 rpath = os.path.abspath(path_to_svn_repos)
186 if rpath[0] != '/':
187 rpath = '/' + rpath
188 return 'file://%s' % rpath.replace(os.sep, '/')
191 def svn_strptime(timestr):
192 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
195 class Log:
196 def __init__(self, revision, author, date, symbols):
197 self.revision = revision
198 self.author = author
200 # Internally, we represent the date as seconds since epoch (UTC).
201 # Since standard subversion log output shows dates in localtime
203 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
205 # and time.mktime() converts from localtime, it all works out very
206 # happily.
207 self.date = time.mktime(svn_strptime(date[0:19]))
209 # The following symbols are used for string interpolation when
210 # checking paths:
211 self.symbols = symbols
213 # The changed paths will be accumulated later, as log data is read.
214 # Keys here are paths such as '/trunk/foo/bar', values are letter
215 # codes such as 'M', 'A', and 'D'.
216 self.changed_paths = { }
218 # The msg will be accumulated later, as log data is read.
219 self.msg = ''
221 def absorb_changed_paths(self, out):
222 'Read changed paths from OUT into self, until no more.'
223 while 1:
224 line = out.readline()
225 if len(line) == 1: return
226 line = line[:-1]
227 op_portion = line[3:4]
228 path_portion = line[5:]
229 # If we're running on Windows we get backslashes instead of
230 # forward slashes.
231 path_portion = path_portion.replace('\\', '/')
232 # # We could parse out history information, but currently we
233 # # just leave it in the path portion because that's how some
234 # # tests expect it.
236 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
237 # if m:
238 # path_portion = m.group(1)
239 self.changed_paths[path_portion] = op_portion
241 def __cmp__(self, other):
242 return cmp(self.revision, other.revision) or \
243 cmp(self.author, other.author) or cmp(self.date, other.date) or \
244 cmp(self.changed_paths, other.changed_paths) or \
245 cmp(self.msg, other.msg)
247 def get_path_op(self, path):
248 """Return the operator for the change involving PATH.
250 PATH is allowed to include string interpolation directives (e.g.,
251 '%(trunk)s'), which are interpolated against self.symbols. Return
252 None if there is no record for PATH."""
253 return self.changed_paths.get(path % self.symbols)
255 def check_msg(self, msg):
256 """Verify that this Log's message starts with the specified MSG."""
257 if self.msg.find(msg) != 0:
258 raise Failure(
259 "Revision %d log message was:\n%s\n\n"
260 "It should have begun with:\n%s\n\n"
261 % (self.revision, self.msg, msg,)
264 def check_change(self, path, op):
265 """Verify that this Log includes a change for PATH with operator OP.
267 PATH is allowed to include string interpolation directives (e.g.,
268 '%(trunk)s'), which are interpolated against self.symbols."""
270 path = path % self.symbols
271 found_op = self.changed_paths.get(path, None)
272 if found_op is None:
273 raise Failure(
274 "Revision %d does not include change for path %s "
275 "(it should have been %s).\n"
276 % (self.revision, path, op,)
278 if found_op != op:
279 raise Failure(
280 "Revision %d path %s had op %s (it should have been %s)\n"
281 % (self.revision, path, found_op, op,)
284 def check_changes(self, changed_paths):
285 """Verify that this Log has precisely the CHANGED_PATHS specified.
287 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
288 strings are allowed to include string interpolation directives
289 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
291 cp = {}
292 for (path, op) in changed_paths:
293 cp[path % self.symbols] = op
295 if self.changed_paths != cp:
296 raise Failure(
297 "Revision %d changed paths list was:\n%s\n\n"
298 "It should have been:\n%s\n\n"
299 % (self.revision, self.changed_paths, cp,)
302 def check(self, msg, changed_paths):
303 """Verify that this Log has the MSG and CHANGED_PATHS specified.
305 Convenience function to check two things at once. MSG is passed
306 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
308 self.check_msg(msg)
309 self.check_changes(changed_paths)
312 def parse_log(svn_repos, symbols):
313 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
315 Initialize the Logs' symbols with SYMBOLS."""
317 class LineFeeder:
318 'Make a list of lines behave like an open file handle.'
319 def __init__(self, lines):
320 self.lines = lines
321 def readline(self):
322 if len(self.lines) > 0:
323 return self.lines.pop(0)
324 else:
325 return None
327 def absorb_message_body(out, num_lines, log):
328 """Read NUM_LINES of log message body from OUT into Log item LOG."""
330 for i in range(num_lines):
331 log.msg += out.readline()
333 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
334 '(?P<author>[^\|]+) \| '
335 '(?P<date>[^\|]+) '
336 '\| (?P<lines>[0-9]+) (line|lines)$')
338 log_separator = '-' * 72
340 logs = { }
342 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
344 while 1:
345 this_log = None
346 line = out.readline()
347 if not line: break
348 line = line[:-1]
350 if line.find(log_separator) == 0:
351 line = out.readline()
352 if not line: break
353 line = line[:-1]
354 m = log_start_re.match(line)
355 if m:
356 this_log = Log(
357 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
358 line = out.readline()
359 if not line.find('Changed paths:') == 0:
360 print 'unexpected log output (missing changed paths)'
361 print "Line: '%s'" % line
362 sys.exit(1)
363 this_log.absorb_changed_paths(out)
364 absorb_message_body(out, int(m.group('lines')), this_log)
365 logs[this_log.revision] = this_log
366 elif len(line) == 0:
367 break # We've reached the end of the log output.
368 else:
369 print 'unexpected log output (missing revision line)'
370 print "Line: '%s'" % line
371 sys.exit(1)
372 else:
373 print 'unexpected log output (missing log separator)'
374 print "Line: '%s'" % line
375 sys.exit(1)
377 return logs
380 def erase(path):
381 """Unconditionally remove PATH and its subtree, if any. PATH may be
382 non-existent, a file or symlink, or a directory."""
383 if os.path.isdir(path):
384 safe_rmtree(path)
385 elif os.path.exists(path):
386 os.remove(path)
389 log_msg_text_wrapper = textwrap.TextWrapper(width=76, break_long_words=False)
391 def sym_log_msg(symbolic_name, is_tag=None):
392 """Return the expected log message for a cvs2svn-synthesized revision
393 creating branch or tag SYMBOLIC_NAME."""
395 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
396 if is_tag:
397 type = 'tag'
398 else:
399 type = 'branch'
401 return log_msg_text_wrapper.fill(
402 "This commit was manufactured by cvs2svn to create %s '%s'."
403 % (type, symbolic_name)
407 def make_conversion_id(
408 name, args, passbypass, options_file=None, symbol_hints_file=None
410 """Create an identifying tag for a conversion.
412 The return value can also be used as part of a filesystem path.
414 NAME is the name of the CVS repository.
416 ARGS are the extra arguments to be passed to cvs2svn.
418 PASSBYPASS is a boolean indicating whether the conversion is to be
419 run one pass at a time.
421 If OPTIONS_FILE is specified, it is an options file that will be
422 used for the conversion.
424 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
425 will be used for the conversion.
427 The 1-to-1 mapping between cvs2svn command parameters and
428 conversion_ids allows us to avoid running the same conversion more
429 than once, when multiple tests use exactly the same conversion."""
431 conv_id = name
433 args = args[:]
435 if passbypass:
436 args.append('--passbypass')
438 if symbol_hints_file is not None:
439 args.append('--symbol-hints=%s' % (symbol_hints_file,))
441 # There are some characters that are forbidden in filenames, and
442 # there is a limit on the total length of a path to a file. So use
443 # a hash of the parameters rather than concatenating the parameters
444 # into a string.
445 if args:
446 conv_id += "-" + md5('\0'.join(args)).hexdigest()
448 # Some options-file based tests rely on knowing the paths to which
449 # the repository should be written, so we handle that option as a
450 # predictable string:
451 if options_file is not None:
452 conv_id += '--options=%s' % (options_file,)
454 return conv_id
457 class Conversion:
458 """A record of a cvs2svn conversion.
460 Fields:
462 conv_id -- the conversion id for this Conversion.
464 name -- a one-word name indicating the involved repositories.
466 dumpfile -- the name of the SVN dumpfile created by the conversion
467 (if the DUMPFILE constructor argument was used); otherwise,
468 None.
470 repos -- the path to the svn repository. Unset if DUMPFILE was
471 specified.
473 logs -- a dictionary of Log instances, as returned by parse_log().
474 Unset if DUMPFILE was specified.
476 symbols -- a dictionary of symbols used for string interpolation
477 in path names.
479 stdout -- a list of lines written by cvs2svn to stdout
481 _wc -- the basename of the svn working copy (within tmp_dir).
482 Unset if DUMPFILE was specified.
484 _wc_path -- the path to the svn working copy, if it has already
485 been created; otherwise, None. (The working copy is created
486 lazily when get_wc() is called.) Unset if DUMPFILE was
487 specified.
489 _wc_tree -- the tree built from the svn working copy, if it has
490 already been created; otherwise, None. The tree is created
491 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
492 specified.
494 _svnrepos -- the basename of the svn repository (within tmp_dir).
495 Unset if DUMPFILE was specified."""
497 # The number of the last cvs2svn pass (determined lazily by
498 # get_last_pass()).
499 last_pass = None
501 @classmethod
502 def get_last_pass(cls):
503 """Return the number of cvs2svn's last pass."""
505 if cls.last_pass is None:
506 out = run_script(cvs2svn, None, '--help-passes')
507 cls.last_pass = int(out[-1].split()[0])
508 return cls.last_pass
510 def __init__(
511 self, conv_id, name, error_re, passbypass, symbols, args,
512 options_file=None, symbol_hints_file=None, dumpfile=None,
514 self.conv_id = conv_id
515 self.name = name
516 self.symbols = symbols
517 if not os.path.isdir(tmp_dir):
518 os.mkdir(tmp_dir)
520 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
522 if dumpfile:
523 self.dumpfile = os.path.join(tmp_dir, dumpfile)
524 # Clean up from any previous invocations of this script.
525 erase(self.dumpfile)
526 else:
527 self.dumpfile = None
528 self.repos = os.path.join(tmp_dir, '%s-svnrepos' % self.conv_id)
529 self._wc = os.path.join(tmp_dir, '%s-wc' % self.conv_id)
530 self._wc_path = None
531 self._wc_tree = None
533 # Clean up from any previous invocations of this script.
534 erase(self.repos)
535 erase(self._wc)
537 args = list(args)
538 args.extend([
539 '--svnadmin=%s' % (svntest.main.svnadmin_binary,),
541 if options_file:
542 self.options_file = os.path.join(cvsrepos, options_file)
543 args.extend([
544 '--options=%s' % self.options_file,
546 assert not symbol_hints_file
547 else:
548 self.options_file = None
549 if tmp_dir != 'cvs2svn-tmp':
550 # Only include this argument if it differs from cvs2svn's default:
551 args.extend([
552 '--tmpdir=%s' % tmp_dir,
555 if symbol_hints_file:
556 self.symbol_hints_file = os.path.join(cvsrepos, symbol_hints_file)
557 args.extend([
558 '--symbol-hints=%s' % self.symbol_hints_file,
561 if self.dumpfile:
562 args.extend(['--dumpfile=%s' % (self.dumpfile,)])
563 else:
564 args.extend(['-s', self.repos])
565 args.extend([cvsrepos])
567 if passbypass:
568 self.stdout = []
569 for p in range(1, self.get_last_pass() + 1):
570 self.stdout += run_script(cvs2svn, error_re, '-p', str(p), *args)
571 else:
572 self.stdout = run_script(cvs2svn, error_re, *args)
574 if self.dumpfile:
575 if not os.path.isfile(self.dumpfile):
576 raise Failure(
577 "Dumpfile not created: '%s'"
578 % os.path.join(os.getcwd(), self.dumpfile)
580 else:
581 if os.path.isdir(self.repos):
582 self.logs = parse_log(self.repos, self.symbols)
583 elif error_re is None:
584 raise Failure(
585 "Repository not created: '%s'"
586 % os.path.join(os.getcwd(), self.repos)
589 def output_found(self, pattern):
590 """Return True if PATTERN matches any line in self.stdout.
592 PATTERN is a regular expression pattern as a string.
595 pattern_re = re.compile(pattern)
597 for line in self.stdout:
598 if pattern_re.match(line):
599 # We found the pattern that we were looking for.
600 return 1
601 else:
602 return 0
604 def find_tag_log(self, tagname):
605 """Search LOGS for a log message containing 'TAGNAME' and return the
606 log in which it was found."""
607 for i in xrange(len(self.logs), 0, -1):
608 if self.logs[i].msg.find("'"+tagname+"'") != -1:
609 return self.logs[i]
610 raise ValueError("Tag %s not found in logs" % tagname)
612 def get_wc(self, *args):
613 """Return the path to the svn working copy, or a path within the WC.
615 If a working copy has not been created yet, create it now.
617 If ARGS are specified, then they should be strings that form
618 fragments of a path within the WC. They are joined using
619 os.path.join() and appended to the WC path."""
621 if self._wc_path is None:
622 run_svn('co', repos_to_url(self.repos), self._wc)
623 self._wc_path = self._wc
624 return os.path.join(self._wc_path, *args)
626 def get_wc_tree(self):
627 if self._wc_tree is None:
628 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
629 return self._wc_tree
631 def path_exists(self, *args):
632 """Return True if the specified path exists within the repository.
634 (The strings in ARGS are first joined into a path using
635 os.path.join().)"""
637 return os.path.exists(self.get_wc(*args))
639 def check_props(self, keys, checks):
640 """Helper function for checking lots of properties. For a list of
641 files in the conversion, check that the values of the properties
642 listed in KEYS agree with those listed in CHECKS. CHECKS is a
643 list of tuples: [ (filename, [value, value, ...]), ...], where the
644 values are listed in the same order as the key names are listed in
645 KEYS."""
647 for (file, values) in checks:
648 assert len(values) == len(keys)
649 props = props_for_path(self.get_wc_tree(), file)
650 for i in range(len(keys)):
651 if props.get(keys[i]) != values[i]:
652 raise Failure(
653 "File %s has property %s set to \"%s\" "
654 "(it should have been \"%s\").\n"
655 % (file, keys[i], props.get(keys[i]), values[i],)
659 class GitConversion:
660 """A record of a cvs2svn conversion.
662 Fields:
664 name -- a one-word name indicating the CVS repository to be converted.
666 stdout -- a list of lines written by cvs2svn to stdout."""
668 def __init__(self, name, error_re, args, options_file=None):
669 self.name = name
670 if not os.path.isdir(tmp_dir):
671 os.mkdir(tmp_dir)
673 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
675 args = list(args)
676 if options_file:
677 self.options_file = os.path.join(cvsrepos, options_file)
678 args.extend([
679 '--options=%s' % self.options_file,
681 else:
682 self.options_file = None
684 self.stdout = run_script(cvs2git, error_re, *args)
687 # Cache of conversions that have already been done. Keys are conv_id;
688 # values are Conversion instances.
689 already_converted = { }
691 def ensure_conversion(
692 name, error_re=None, passbypass=None,
693 trunk=None, branches=None, tags=None,
694 args=None, options_file=None, symbol_hints_file=None, dumpfile=None,
696 """Convert CVS repository NAME to Subversion, but only if it has not
697 been converted before by this invocation of this script. If it has
698 been converted before, return the Conversion object from the
699 previous invocation.
701 If no error, return a Conversion instance.
703 If ERROR_RE is a string, it is a regular expression expected to
704 match some line of stderr printed by the conversion. If there is an
705 error and ERROR_RE is not set, then raise Failure.
707 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
708 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
710 NAME is just one word. For example, 'main' would mean to convert
711 './test-data/main-cvsrepos', and after the conversion, the resulting
712 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
713 a checked out head working copy in './cvs2svn-tmp/main-wc'.
715 Any other options to pass to cvs2svn should be in ARGS, each element
716 being one option, e.g., '--trunk-only'. If the option takes an
717 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
718 are passed to cvs2svn in the order that they appear in ARGS.
720 If OPTIONS_FILE is specified, then it should be the name of a file
721 within the main directory of the cvs repository associated with this
722 test. It is passed to cvs2svn using the --options option (which
723 suppresses some other options that are incompatible with --options).
725 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
726 file within the main directory of the cvs repository associated with
727 this test. It is passed to cvs2svn using the --symbol-hints option.
729 If DUMPFILE is specified, then it is the name of a dumpfile within
730 the temporary directory to which the conversion output should be
731 written."""
733 if args is None:
734 args = []
735 else:
736 args = list(args)
738 if trunk is None:
739 trunk = 'trunk'
740 else:
741 args.append('--trunk=%s' % (trunk,))
743 if branches is None:
744 branches = 'branches'
745 else:
746 args.append('--branches=%s' % (branches,))
748 if tags is None:
749 tags = 'tags'
750 else:
751 args.append('--tags=%s' % (tags,))
753 conv_id = make_conversion_id(
754 name, args, passbypass, options_file, symbol_hints_file
757 if conv_id not in already_converted:
758 try:
759 # Run the conversion and store the result for the rest of this
760 # session:
761 already_converted[conv_id] = Conversion(
762 conv_id, name, error_re, passbypass,
763 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
764 args, options_file, symbol_hints_file, dumpfile,
766 except Failure:
767 # Remember the failure so that a future attempt to run this conversion
768 # does not bother to retry, but fails immediately.
769 already_converted[conv_id] = None
770 raise
772 conv = already_converted[conv_id]
773 if conv is None:
774 raise Failure()
775 return conv
778 class Cvs2SvnTestFunction(TestCase):
779 """A TestCase based on a naked Python function object.
781 FUNC should be a function that returns None on success and throws an
782 svntest.Failure exception on failure. It should have a brief
783 docstring describing what it does (and fulfilling certain
784 conditions). FUNC must take no arguments.
786 This class is almost identical to svntest.testcase.FunctionTestCase,
787 except that the test function does not require a sandbox and does
788 not accept any parameter (not even sandbox=None).
790 This class can be used as an annotation on a Python function.
794 def __init__(self, func):
795 # it better be a function that accepts no parameters and has a
796 # docstring on it.
797 assert isinstance(func, types.FunctionType)
799 name = func.func_name
801 assert func.func_code.co_argcount == 0, \
802 '%s must not take any arguments' % name
804 doc = func.__doc__.strip()
805 assert doc, '%s must have a docstring' % name
807 # enforce stylistic guidelines for the function docstrings:
808 # - no longer than 50 characters
809 # - should not end in a period
810 # - should not be capitalized
811 assert len(doc) <= 50, \
812 "%s's docstring must be 50 characters or less" % name
813 assert doc[-1] != '.', \
814 "%s's docstring should not end in a period" % name
815 assert doc[0].lower() == doc[0], \
816 "%s's docstring should not be capitalized" % name
818 TestCase.__init__(self, doc=doc)
819 self.func = func
821 def get_function_name(self):
822 return self.func.func_name
824 def get_sandbox_name(self):
825 return None
827 def run(self, sandbox):
828 return self.func()
831 class Cvs2HgTestFunction(Cvs2SvnTestFunction):
832 """Same as Cvs2SvnTestFunction, but for test cases that should be
833 skipped if Mercurial is not available.
835 def run(self, sandbox):
836 if not have_hg:
837 raise svntest.Skip()
838 else:
839 return self.func()
842 class Cvs2SvnTestCase(TestCase):
843 def __init__(
844 self, name, doc=None, variant=None,
845 error_re=None, passbypass=None,
846 trunk=None, branches=None, tags=None,
847 args=None,
848 options_file=None, symbol_hints_file=None, dumpfile=None,
850 self.name = name
852 if doc is None:
853 # By default, use the first line of the class docstring as the
854 # doc:
855 doc = self.__doc__.splitlines()[0]
857 if variant is not None:
858 # Modify doc to show the variant. Trim doc first if necessary
859 # to stay within the 50-character limit.
860 suffix = '...variant %s' % (variant,)
861 doc = doc[:50 - len(suffix)] + suffix
863 TestCase.__init__(self, doc=doc)
865 self.error_re = error_re
866 self.passbypass = passbypass
867 self.trunk = trunk
868 self.branches = branches
869 self.tags = tags
870 self.args = args
871 self.options_file = options_file
872 self.symbol_hints_file = symbol_hints_file
873 self.dumpfile = dumpfile
875 def ensure_conversion(self):
876 return ensure_conversion(
877 self.name,
878 error_re=self.error_re, passbypass=self.passbypass,
879 trunk=self.trunk, branches=self.branches, tags=self.tags,
880 args=self.args,
881 options_file=self.options_file,
882 symbol_hints_file=self.symbol_hints_file,
883 dumpfile=self.dumpfile,
886 def get_sandbox_name(self):
887 return None
890 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase):
891 """Test properties resulting from a conversion."""
893 def __init__(self, name, props_to_test, expected_props, **kw):
894 """Initialize an instance of Cvs2SvnPropertiesTestCase.
896 NAME is the name of the test, passed to Cvs2SvnTestCase.
897 PROPS_TO_TEST is a list of the names of svn properties that should
898 be tested. EXPECTED_PROPS is a list of tuples [(filename,
899 [value,...])], where the second item in each tuple is a list of
900 values expected for the properties listed in PROPS_TO_TEST for the
901 specified filename. If a property must *not* be set, then its
902 value should be listed as None."""
904 Cvs2SvnTestCase.__init__(self, name, **kw)
905 self.props_to_test = props_to_test
906 self.expected_props = expected_props
908 def run(self, sbox):
909 conv = self.ensure_conversion()
910 conv.check_props(self.props_to_test, self.expected_props)
913 #----------------------------------------------------------------------
914 # Tests.
915 #----------------------------------------------------------------------
918 @Cvs2SvnTestFunction
919 def show_usage():
920 "cvs2svn with no arguments shows usage"
921 out = run_script(cvs2svn, None)
922 if (len(out) > 2 and out[0].find('ERROR:') == 0
923 and out[1].find('DBM module')):
924 print 'cvs2svn cannot execute due to lack of proper DBM module.'
925 print 'Exiting without running any further tests.'
926 sys.exit(1)
927 if out[0].find('Usage:') < 0:
928 raise Failure('Basic cvs2svn invocation failed.')
931 @Cvs2SvnTestFunction
932 def cvs2svn_manpage():
933 "generate a manpage for cvs2svn"
934 out = run_script(cvs2svn, None, '--man')
937 @Cvs2SvnTestFunction
938 def cvs2git_manpage():
939 "generate a manpage for cvs2git"
940 out = run_script(cvs2git, None, '--man')
943 @XFail_deco()
944 @Cvs2HgTestFunction
945 def cvs2hg_manpage():
946 "generate a manpage for cvs2hg"
947 out = run_script(cvs2hg, None, '--man')
950 @Cvs2SvnTestFunction
951 def show_help_passes():
952 "cvs2svn --help-passes shows pass information"
953 out = run_script(cvs2svn, None, '--help-passes')
954 if out[0].find('PASSES') < 0:
955 raise Failure('cvs2svn --help-passes failed.')
958 @Cvs2SvnTestFunction
959 def attr_exec():
960 "detection of the executable flag"
961 if sys.platform == 'win32':
962 raise svntest.Skip()
963 conv = ensure_conversion('main')
964 st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec'))
965 if not st.st_mode & stat.S_IXUSR:
966 raise Failure()
969 @Cvs2SvnTestFunction
970 def space_fname():
971 "conversion of filename with a space"
972 conv = ensure_conversion('main')
973 if not conv.path_exists('trunk', 'single-files', 'space fname'):
974 raise Failure()
977 @Cvs2SvnTestFunction
978 def two_quick():
979 "two commits in quick succession"
980 conv = ensure_conversion('main')
981 logs = parse_log(
982 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
983 if len(logs) != 2:
984 raise Failure()
987 class PruneWithCare(Cvs2SvnTestCase):
988 "prune, but never too much"
990 def __init__(self, **kw):
991 Cvs2SvnTestCase.__init__(self, 'main', **kw)
993 def run(self, sbox):
994 # Robert Pluim encountered this lovely one while converting the
995 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
996 # repository (see issue #1302). Step 4 is the doozy:
998 # revision 1: adds trunk/blah/, adds trunk/blah/first
999 # revision 2: adds trunk/blah/second
1000 # revision 3: deletes trunk/blah/first
1001 # revision 4: deletes blah [re-deleting trunk/blah/first pruned blah!]
1002 # revision 5: does nothing
1004 # After fixing cvs2svn, the sequence (correctly) looks like this:
1006 # revision 1: adds trunk/blah/, adds trunk/blah/first
1007 # revision 2: adds trunk/blah/second
1008 # revision 3: deletes trunk/blah/first
1009 # revision 4: does nothing [because trunk/blah/first already deleted]
1010 # revision 5: deletes blah
1012 # The difference is in 4 and 5. In revision 4, it's not correct
1013 # to prune blah/, because second is still in there, so revision 4
1014 # does nothing now. But when we delete second in 5, that should
1015 # bubble up and prune blah/ instead.
1017 # ### Note that empty revisions like 4 are probably going to become
1018 # ### at least optional, if not banished entirely from cvs2svn's
1019 # ### output. Hmmm, or they may stick around, with an extra
1020 # ### revision property explaining what happened. Need to think
1021 # ### about that. In some sense, it's a bug in Subversion itself,
1022 # ### that such revisions don't show up in 'svn log' output.
1024 conv = self.ensure_conversion()
1026 # Confirm that revision 4 removes '/trunk/full-prune/first',
1027 # and that revision 6 removes '/trunk/full-prune'.
1029 # Also confirm similar things about '/full-prune-reappear/...',
1030 # which is similar, except that later on it reappears, restored
1031 # from pruneland, because a file gets added to it.
1033 # And finally, a similar thing for '/partial-prune/...', except that
1034 # in its case, a permanent file on the top level prevents the
1035 # pruning from going farther than the subdirectory containing first
1036 # and second.
1038 for path in ('full-prune/first',
1039 'full-prune-reappear/sub/first',
1040 'partial-prune/sub/first'):
1041 conv.logs[5].check_change('/%(trunk)s/' + path, 'D')
1043 for path in ('full-prune',
1044 'full-prune-reappear',
1045 'partial-prune/sub'):
1046 conv.logs[7].check_change('/%(trunk)s/' + path, 'D')
1048 for path in ('full-prune-reappear',
1049 'full-prune-reappear/appears-later'):
1050 conv.logs[33].check_change('/%(trunk)s/' + path, 'A')
1053 @Cvs2SvnTestFunction
1054 def interleaved_commits():
1055 "two interleaved trunk commits, different log msgs"
1056 # See test-data/main-cvsrepos/proj/README.
1057 conv = ensure_conversion('main')
1059 # The initial import.
1060 rev = 26
1061 conv.logs[rev].check('Initial import.', (
1062 ('/%(trunk)s/interleaved', 'A'),
1063 ('/%(trunk)s/interleaved/1', 'A'),
1064 ('/%(trunk)s/interleaved/2', 'A'),
1065 ('/%(trunk)s/interleaved/3', 'A'),
1066 ('/%(trunk)s/interleaved/4', 'A'),
1067 ('/%(trunk)s/interleaved/5', 'A'),
1068 ('/%(trunk)s/interleaved/a', 'A'),
1069 ('/%(trunk)s/interleaved/b', 'A'),
1070 ('/%(trunk)s/interleaved/c', 'A'),
1071 ('/%(trunk)s/interleaved/d', 'A'),
1072 ('/%(trunk)s/interleaved/e', 'A'),
1075 def check_letters(rev):
1076 """Check if REV is the rev where only letters were committed."""
1078 conv.logs[rev].check('Committing letters only.', (
1079 ('/%(trunk)s/interleaved/a', 'M'),
1080 ('/%(trunk)s/interleaved/b', 'M'),
1081 ('/%(trunk)s/interleaved/c', 'M'),
1082 ('/%(trunk)s/interleaved/d', 'M'),
1083 ('/%(trunk)s/interleaved/e', 'M'),
1086 def check_numbers(rev):
1087 """Check if REV is the rev where only numbers were committed."""
1089 conv.logs[rev].check('Committing numbers only.', (
1090 ('/%(trunk)s/interleaved/1', 'M'),
1091 ('/%(trunk)s/interleaved/2', 'M'),
1092 ('/%(trunk)s/interleaved/3', 'M'),
1093 ('/%(trunk)s/interleaved/4', 'M'),
1094 ('/%(trunk)s/interleaved/5', 'M'),
1097 # One of the commits was letters only, the other was numbers only.
1098 # But they happened "simultaneously", so we don't assume anything
1099 # about which commit appeared first, so we just try both ways.
1100 rev += 1
1101 try:
1102 check_letters(rev)
1103 check_numbers(rev + 1)
1104 except Failure:
1105 check_numbers(rev)
1106 check_letters(rev + 1)
1109 @Cvs2SvnTestFunction
1110 def simple_commits():
1111 "simple trunk commits"
1112 # See test-data/main-cvsrepos/proj/README.
1113 conv = ensure_conversion('main')
1115 # The initial import.
1116 conv.logs[13].check('Initial import.', (
1117 ('/%(trunk)s/proj', 'A'),
1118 ('/%(trunk)s/proj/default', 'A'),
1119 ('/%(trunk)s/proj/sub1', 'A'),
1120 ('/%(trunk)s/proj/sub1/default', 'A'),
1121 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1122 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1123 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1124 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1125 ('/%(trunk)s/proj/sub2', 'A'),
1126 ('/%(trunk)s/proj/sub2/default', 'A'),
1127 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1128 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1129 ('/%(trunk)s/proj/sub3', 'A'),
1130 ('/%(trunk)s/proj/sub3/default', 'A'),
1133 # The first commit.
1134 conv.logs[18].check('First commit to proj, affecting two files.', (
1135 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1136 ('/%(trunk)s/proj/sub3/default', 'M'),
1139 # The second commit.
1140 conv.logs[19].check('Second commit to proj, affecting all 7 files.', (
1141 ('/%(trunk)s/proj/default', 'M'),
1142 ('/%(trunk)s/proj/sub1/default', 'M'),
1143 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1144 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1145 ('/%(trunk)s/proj/sub2/default', 'M'),
1146 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1147 ('/%(trunk)s/proj/sub3/default', 'M')
1151 class SimpleTags(Cvs2SvnTestCase):
1152 "simple tags and branches, no commits"
1154 def __init__(self, **kw):
1155 # See test-data/main-cvsrepos/proj/README.
1156 Cvs2SvnTestCase.__init__(self, 'main', **kw)
1158 def run(self, sbox):
1159 conv = self.ensure_conversion()
1161 # Verify the copy source for the tags we are about to check
1162 # No need to verify the copyfrom revision, as simple_commits did that
1163 conv.logs[13].check('Initial import.', (
1164 ('/%(trunk)s/proj', 'A'),
1165 ('/%(trunk)s/proj/default', 'A'),
1166 ('/%(trunk)s/proj/sub1', 'A'),
1167 ('/%(trunk)s/proj/sub1/default', 'A'),
1168 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1169 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1170 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1171 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1172 ('/%(trunk)s/proj/sub2', 'A'),
1173 ('/%(trunk)s/proj/sub2/default', 'A'),
1174 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1175 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1176 ('/%(trunk)s/proj/sub3', 'A'),
1177 ('/%(trunk)s/proj/sub3/default', 'A'),
1180 # Tag on rev 1.1.1.1 of all files in proj
1181 conv.logs[16].check(sym_log_msg('B_FROM_INITIALS'), (
1182 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1183 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1184 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1187 # The same, as a tag
1188 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
1189 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1190 ('/%(tags)s/T_ALL_INITIAL_FILES (from /%(trunk)s:13)', 'A'),
1191 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
1192 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
1195 # Tag on rev 1.1.1.1 of all files in proj, except one
1196 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1197 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1198 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE (from /%(trunk)s:13)', 'A'),
1199 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
1200 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
1201 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1204 # The same, as a branch
1205 conv.logs[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1206 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE (from /%(trunk)s:13)', 'A'),
1207 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1208 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
1209 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
1213 @Cvs2SvnTestFunction
1214 def simple_branch_commits():
1215 "simple branch commits"
1216 # See test-data/main-cvsrepos/proj/README.
1217 conv = ensure_conversion('main')
1219 conv.logs[23].check('Modify three files, on branch B_MIXED.', (
1220 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1221 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1222 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1226 @Cvs2SvnTestFunction
1227 def mixed_time_tag():
1228 "mixed-time tag"
1229 # See test-data/main-cvsrepos/proj/README.
1230 conv = ensure_conversion('main')
1232 log = conv.find_tag_log('T_MIXED')
1233 log.check_changes((
1234 ('/%(tags)s/T_MIXED (from /%(trunk)s:19)', 'A'),
1235 ('/%(tags)s/T_MIXED/single-files', 'D'),
1236 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
1237 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
1238 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1239 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1243 @Cvs2SvnTestFunction
1244 def mixed_time_branch_with_added_file():
1245 "mixed-time branch, and a file added to the branch"
1246 # See test-data/main-cvsrepos/proj/README.
1247 conv = ensure_conversion('main')
1249 # A branch from the same place as T_MIXED in the previous test,
1250 # plus a file added directly to the branch
1251 conv.logs[21].check(sym_log_msg('B_MIXED'), (
1252 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1253 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1254 ('/%(branches)s/B_MIXED/single-files', 'D'),
1255 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1256 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1257 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1260 conv.logs[22].check('Add a file on branch B_MIXED.', (
1261 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1265 @Cvs2SvnTestFunction
1266 def mixed_commit():
1267 "a commit affecting both trunk and a branch"
1268 # See test-data/main-cvsrepos/proj/README.
1269 conv = ensure_conversion('main')
1271 conv.logs[24].check(
1272 'A single commit affecting one file on branch B_MIXED '
1273 'and one on trunk.', (
1274 ('/%(trunk)s/proj/sub2/default', 'M'),
1275 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1279 @Cvs2SvnTestFunction
1280 def split_time_branch():
1281 "branch some trunk files, and later branch the rest"
1282 # See test-data/main-cvsrepos/proj/README.
1283 conv = ensure_conversion('main')
1285 # First change on the branch, creating it
1286 conv.logs[25].check(sym_log_msg('B_SPLIT'), (
1287 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1288 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1289 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1290 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1293 conv.logs[29].check('First change on branch B_SPLIT.', (
1294 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1295 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1296 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1297 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1298 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1301 # A trunk commit for the file which was not branched
1302 conv.logs[30].check('A trunk change to sub1/subsubB/default. '
1303 'This was committed about an', (
1304 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1307 # Add the file not already branched to the branch, with modification:w
1308 conv.logs[31].check(sym_log_msg('B_SPLIT'), (
1309 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1310 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1313 conv.logs[32].check('This change affects sub3/default and '
1314 'sub1/subsubB/default, on branch', (
1315 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1316 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1320 @Cvs2SvnTestFunction
1321 def multiple_tags():
1322 "multiple tags referring to same revision"
1323 conv = ensure_conversion('main')
1324 if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1325 raise Failure()
1326 if not conv.path_exists(
1327 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1328 raise Failure()
1331 @Cvs2SvnTestFunction
1332 def multiply_defined_symbols():
1333 "multiple definitions of symbol names"
1335 # We can only check one line of the error output at a time, so test
1336 # twice. (The conversion only have to be done once because the
1337 # results are cached.)
1338 conv = ensure_conversion(
1339 'multiply-defined-symbols',
1340 error_re=(
1341 r"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1342 r"1\.2\.4 1\.2\.2"
1345 conv = ensure_conversion(
1346 'multiply-defined-symbols',
1347 error_re=(
1348 r"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1349 r"1\.2 1\.1"
1354 @Cvs2SvnTestFunction
1355 def multiply_defined_symbols_renamed():
1356 "rename multiply defined symbols"
1358 conv = ensure_conversion(
1359 'multiply-defined-symbols',
1360 options_file='cvs2svn-rename.options',
1364 @Cvs2SvnTestFunction
1365 def multiply_defined_symbols_ignored():
1366 "ignore multiply defined symbols"
1368 conv = ensure_conversion(
1369 'multiply-defined-symbols',
1370 options_file='cvs2svn-ignore.options',
1374 @Cvs2SvnTestFunction
1375 def repeatedly_defined_symbols():
1376 "multiple identical definitions of symbol names"
1378 # If a symbol is defined multiple times but has the same value each
1379 # time, that should not be an error.
1381 conv = ensure_conversion('repeatedly-defined-symbols')
1384 @Cvs2SvnTestFunction
1385 def bogus_tag():
1386 "conversion of invalid symbolic names"
1387 conv = ensure_conversion('bogus-tag')
1390 @Cvs2SvnTestFunction
1391 def overlapping_branch():
1392 "ignore a file with a branch with two names"
1393 conv = ensure_conversion('overlapping-branch')
1395 if not conv.output_found('.*cannot also have name \'vendorB\''):
1396 raise Failure()
1398 conv.logs[2].check('imported', (
1399 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1400 ('/%(trunk)s/overlapping-branch', 'A'),
1403 if len(conv.logs) != 2:
1404 raise Failure()
1407 class PhoenixBranch(Cvs2SvnTestCase):
1408 "convert a branch file rooted in a 'dead' revision"
1410 def __init__(self, **kw):
1411 Cvs2SvnTestCase.__init__(self, 'phoenix', **kw)
1413 def run(self, sbox):
1414 conv = self.ensure_conversion()
1415 conv.logs[8].check('This file was supplied by Jack Moffitt', (
1416 ('/%(branches)s/volsung_20010721', 'A'),
1417 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1419 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1420 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1424 ###TODO: We check for 4 changed paths here to accomodate creating tags
1425 ###and branches in rev 1, but that will change, so this will
1426 ###eventually change back.
1427 @Cvs2SvnTestFunction
1428 def ctrl_char_in_log():
1429 "handle a control char in a log message"
1430 # This was issue #1106.
1431 rev = 2
1432 conv = ensure_conversion('ctrl-char-in-log')
1433 conv.logs[rev].check_changes((
1434 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1436 if conv.logs[rev].msg.find('\x04') < 0:
1437 raise Failure(
1438 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1441 @Cvs2SvnTestFunction
1442 def overdead():
1443 "handle tags rooted in a redeleted revision"
1444 conv = ensure_conversion('overdead')
1447 class NoTrunkPrune(Cvs2SvnTestCase):
1448 "ensure that trunk doesn't get pruned"
1450 def __init__(self, **kw):
1451 Cvs2SvnTestCase.__init__(self, 'overdead', **kw)
1453 def run(self, sbox):
1454 conv = self.ensure_conversion()
1455 for rev in conv.logs.keys():
1456 rev_logs = conv.logs[rev]
1457 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1458 raise Failure()
1461 @Cvs2SvnTestFunction
1462 def double_delete():
1463 "file deleted twice, in the root of the repository"
1464 # This really tests several things: how we handle a file that's
1465 # removed (state 'dead') in two successive revisions; how we
1466 # handle a file in the root of the repository (there were some
1467 # bugs in cvs2svn's svn path construction for top-level files); and
1468 # the --no-prune option.
1469 conv = ensure_conversion(
1470 'double-delete', args=['--trunk-only', '--no-prune'])
1472 path = '/%(trunk)s/twice-removed'
1473 rev = 2
1474 conv.logs[rev].check('Updated CVS', (
1475 (path, 'A'),
1477 conv.logs[rev + 1].check('Remove this file for the first time.', (
1478 (path, 'D'),
1480 conv.logs[rev + 2].check('Remove this file for the second time,', (
1484 @Cvs2SvnTestFunction
1485 def split_branch():
1486 "branch created from both trunk and another branch"
1487 # See test-data/split-branch-cvsrepos/README.
1489 # The conversion will fail if the bug is present, and
1490 # ensure_conversion will raise Failure.
1491 conv = ensure_conversion('split-branch')
1494 @Cvs2SvnTestFunction
1495 def resync_misgroups():
1496 "resyncing should not misorder commit groups"
1497 # See test-data/resync-misgroups-cvsrepos/README.
1499 # The conversion will fail if the bug is present, and
1500 # ensure_conversion will raise Failure.
1501 conv = ensure_conversion('resync-misgroups')
1504 class TaggedBranchAndTrunk(Cvs2SvnTestCase):
1505 "allow tags with mixed trunk and branch sources"
1507 def __init__(self, **kw):
1508 Cvs2SvnTestCase.__init__(self, 'tagged-branch-n-trunk', **kw)
1510 def run(self, sbox):
1511 conv = self.ensure_conversion()
1513 tags = conv.symbols.get('tags', 'tags')
1515 a_path = conv.get_wc(tags, 'some-tag', 'a.txt')
1516 b_path = conv.get_wc(tags, 'some-tag', 'b.txt')
1517 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1518 raise Failure()
1519 if (open(a_path, 'r').read().find('1.24') == -1) \
1520 or (open(b_path, 'r').read().find('1.5') == -1):
1521 raise Failure()
1524 @Cvs2SvnTestFunction
1525 def enroot_race():
1526 "never use the rev-in-progress as a copy source"
1528 # See issue #1427 and r8544.
1529 conv = ensure_conversion('enroot-race')
1530 rev = 6
1531 conv.logs[rev].check_changes((
1532 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1533 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1534 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1536 conv.logs[rev + 1].check_changes((
1537 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1538 ('/%(trunk)s/proj/a.txt', 'M'),
1539 ('/%(trunk)s/proj/b.txt', 'M'),
1543 @Cvs2SvnTestFunction
1544 def enroot_race_obo():
1545 "do use the last completed rev as a copy source"
1546 conv = ensure_conversion('enroot-race-obo')
1547 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1548 if not len(conv.logs) == 3:
1549 raise Failure()
1552 class BranchDeleteFirst(Cvs2SvnTestCase):
1553 "correctly handle deletion as initial branch action"
1555 def __init__(self, **kw):
1556 Cvs2SvnTestCase.__init__(self, 'branch-delete-first', **kw)
1558 def run(self, sbox):
1559 # See test-data/branch-delete-first-cvsrepos/README.
1561 # The conversion will fail if the bug is present, and
1562 # ensure_conversion would raise Failure.
1563 conv = self.ensure_conversion()
1565 branches = conv.symbols.get('branches', 'branches')
1567 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1568 if conv.path_exists(branches, 'branch-1', 'file'):
1569 raise Failure()
1570 if conv.path_exists(branches, 'branch-2', 'file'):
1571 raise Failure()
1572 if not conv.path_exists(branches, 'branch-3', 'file'):
1573 raise Failure()
1576 @Cvs2SvnTestFunction
1577 def nonascii_cvsignore():
1578 "non ascii files in .cvsignore"
1580 # The output seems to be in the C locale, where it looks like this
1581 # (at least on one test system):
1582 expected = (
1583 'Sp?\\195?\\164tzle\n'
1584 'Cr?\\195?\\168meBr?\\195?\\187l?\\195?\\169e\n'
1585 'Jam?\\195?\\179nIb?\\195?\\169rico\n'
1586 'Am?\\195?\\170ijoas?\\195?\\128Bulh?\\195?\\163oPato\n'
1589 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1590 props = props_for_path(conv.get_wc_tree(), 'trunk/single-files')
1592 if props['svn:ignore'] != expected:
1593 raise Failure()
1596 @Cvs2SvnTestFunction
1597 def nonascii_filenames():
1598 "non ascii files converted incorrectly"
1599 # see issue #1255
1601 # on a en_US.iso-8859-1 machine this test fails with
1602 # svn: Can't recode ...
1604 # as described in the issue
1606 # on a en_US.UTF-8 machine this test fails with
1607 # svn: Malformed XML ...
1609 # which means at least it fails. Unfortunately it won't fail
1610 # with the same error...
1612 # mangle current locale settings so we know we're not running
1613 # a UTF-8 locale (which does not exhibit this problem)
1614 current_locale = locale.getlocale()
1615 new_locale = 'en_US.ISO8859-1'
1616 locale_changed = None
1618 # From http://docs.python.org/lib/module-sys.html
1620 # getfilesystemencoding():
1622 # Return the name of the encoding used to convert Unicode filenames
1623 # into system file names, or None if the system default encoding is
1624 # used. The result value depends on the operating system:
1626 # - On Windows 9x, the encoding is ``mbcs''.
1627 # - On Mac OS X, the encoding is ``utf-8''.
1628 # - On Unix, the encoding is the user's preference according to the
1629 # result of nl_langinfo(CODESET), or None if the
1630 # nl_langinfo(CODESET) failed.
1631 # - On Windows NT+, file names are Unicode natively, so no conversion is
1632 # performed.
1634 # So we're going to skip this test on Mac OS X for now.
1635 if sys.platform == "darwin":
1636 raise svntest.Skip()
1638 try:
1639 # change locale to non-UTF-8 locale to generate latin1 names
1640 locale.setlocale(locale.LC_ALL, # this might be too broad?
1641 new_locale)
1642 locale_changed = 1
1643 except locale.Error:
1644 raise svntest.Skip()
1646 try:
1647 srcrepos_path = os.path.join(test_data_dir, 'non-ascii-cvsrepos')
1648 dstrepos_path = os.path.join(test_data_dir, 'non-ascii-copy-cvsrepos')
1649 if not os.path.exists(dstrepos_path):
1650 # create repos from existing main repos
1651 shutil.copytree(srcrepos_path, dstrepos_path)
1652 base_path = os.path.join(dstrepos_path, 'single-files')
1653 os.remove(os.path.join(base_path, '.cvsignore,v'))
1654 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1655 os.path.join(base_path, 'two\366uick,v'))
1656 new_path = os.path.join(dstrepos_path, 'single\366files')
1657 os.rename(base_path, new_path)
1659 conv = ensure_conversion('non-ascii-copy', args=['--encoding=latin1'])
1660 finally:
1661 if locale_changed:
1662 locale.setlocale(locale.LC_ALL, current_locale)
1663 safe_rmtree(dstrepos_path)
1666 class UnicodeTest(Cvs2SvnTestCase):
1667 "metadata contains Unicode"
1669 warning_pattern = r'ERROR\: There were warnings converting .* messages'
1671 def __init__(self, name, warning_expected, **kw):
1672 if warning_expected:
1673 error_re = self.warning_pattern
1674 else:
1675 error_re = None
1677 Cvs2SvnTestCase.__init__(self, name, error_re=error_re, **kw)
1678 self.warning_expected = warning_expected
1680 def run(self, sbox):
1681 try:
1682 # ensure the availability of the "utf_8" encoding:
1683 u'a'.encode('utf_8').decode('utf_8')
1684 except LookupError:
1685 raise svntest.Skip()
1687 self.ensure_conversion()
1690 class UnicodeAuthor(UnicodeTest):
1691 "author name contains Unicode"
1693 def __init__(self, warning_expected, **kw):
1694 UnicodeTest.__init__(self, 'unicode-author', warning_expected, **kw)
1697 class UnicodeLog(UnicodeTest):
1698 "log message contains Unicode"
1700 def __init__(self, warning_expected, **kw):
1701 UnicodeTest.__init__(self, 'unicode-log', warning_expected, **kw)
1704 @Cvs2SvnTestFunction
1705 def vendor_branch_sameness():
1706 "avoid spurious changes for initial revs"
1707 conv = ensure_conversion(
1708 'vendor-branch-sameness', args=['--keep-trivial-imports']
1711 # The following files are in this repository:
1713 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1714 # the same contents, the file's default branch is 1.1.1,
1715 # and both revisions are in state 'Exp'.
1717 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1718 # 1.1 (the addition of a line of text).
1720 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1722 # d.txt: This file was created by 'cvs add' instead of import, so
1723 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1724 # The timestamp on the add is exactly the same as for the
1725 # imports of the other files.
1727 # e.txt: Like a.txt, except that the log message for revision 1.1
1728 # is not the standard import log message.
1730 # (Aside from e.txt, the log messages for the same revisions are the
1731 # same in all files.)
1733 # We expect that only a.txt is recognized as an import whose 1.1
1734 # revision can be omitted. The other files should be added on trunk
1735 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1736 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1737 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1738 # and c.txt should be 'D'eleted.
1740 rev = 2
1741 conv.logs[rev].check('Initial revision', (
1742 ('/%(trunk)s/proj', 'A'),
1743 ('/%(trunk)s/proj/b.txt', 'A'),
1744 ('/%(trunk)s/proj/c.txt', 'A'),
1745 ('/%(trunk)s/proj/d.txt', 'A'),
1748 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1749 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1750 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1753 conv.logs[rev + 2].check('First vendor branch revision.', (
1754 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1755 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1756 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1759 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1760 'to compensate for changes in r4,', (
1761 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1762 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1763 ('/%(trunk)s/proj/c.txt', 'D'),
1766 rev = 7
1767 conv.logs[rev].check('This log message is not the standard', (
1768 ('/%(trunk)s/proj/e.txt', 'A'),
1771 conv.logs[rev + 2].check('First vendor branch revision', (
1772 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1775 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1776 'to compensate for changes in r9,', (
1777 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1781 @Cvs2SvnTestFunction
1782 def vendor_branch_trunk_only():
1783 "handle vendor branches with --trunk-only"
1784 conv = ensure_conversion('vendor-branch-sameness', args=['--trunk-only'])
1786 rev = 2
1787 conv.logs[rev].check('Initial revision', (
1788 ('/%(trunk)s/proj', 'A'),
1789 ('/%(trunk)s/proj/b.txt', 'A'),
1790 ('/%(trunk)s/proj/c.txt', 'A'),
1791 ('/%(trunk)s/proj/d.txt', 'A'),
1794 conv.logs[rev + 1].check('First vendor branch revision', (
1795 ('/%(trunk)s/proj/a.txt', 'A'),
1796 ('/%(trunk)s/proj/b.txt', 'M'),
1797 ('/%(trunk)s/proj/c.txt', 'D'),
1800 conv.logs[rev + 2].check('This log message is not the standard', (
1801 ('/%(trunk)s/proj/e.txt', 'A'),
1804 conv.logs[rev + 3].check('First vendor branch revision', (
1805 ('/%(trunk)s/proj/e.txt', 'M'),
1809 @Cvs2SvnTestFunction
1810 def default_branches():
1811 "handle default branches correctly"
1812 conv = ensure_conversion('default-branches')
1814 # There are seven files in the repository:
1816 # a.txt:
1817 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1818 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1819 # committed (thus losing the default branch "1.1.1"), then
1820 # 1.1.1.4 was imported. All vendor import release tags are
1821 # still present.
1823 # b.txt:
1824 # Like a.txt, but without rev 1.2.
1826 # c.txt:
1827 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1829 # d.txt:
1830 # Same as the previous two, but 1.1.1 branch is unlabeled.
1832 # e.txt:
1833 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1835 # deleted-on-vendor-branch.txt,v:
1836 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1838 # added-then-imported.txt,v:
1839 # Added with 'cvs add' to create 1.1, then imported with
1840 # completely different contents to create 1.1.1.1, therefore
1841 # never had a default branch.
1844 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1845 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1846 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1847 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1848 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1849 ('/%(branches)s/vbranchA', 'A'),
1850 ('/%(branches)s/vbranchA/proj', 'A'),
1851 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1852 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1853 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1854 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1857 conv.logs[3].check("This commit was generated by cvs2svn "
1858 "to compensate for changes in r2,", (
1859 ('/%(trunk)s/proj', 'A'),
1860 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1861 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1862 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1863 ('/%(trunk)s/proj/d.txt '
1864 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1865 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1866 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1867 ('/%(trunk)s/proj/e.txt '
1868 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1871 conv.logs[4].check(sym_log_msg('vtag-1',1), (
1872 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1873 ('/%(tags)s/vtag-1/proj/d.txt '
1874 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1877 conv.logs[5].check("Import (vbranchA, vtag-2).", (
1878 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1879 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1880 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1881 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1882 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1883 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1886 conv.logs[6].check("This commit was generated by cvs2svn "
1887 "to compensate for changes in r5,", (
1888 ('/%(trunk)s/proj/a.txt '
1889 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1890 ('/%(trunk)s/proj/b.txt '
1891 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1892 ('/%(trunk)s/proj/c.txt '
1893 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1894 ('/%(trunk)s/proj/d.txt '
1895 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1896 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1897 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1898 'R'),
1899 ('/%(trunk)s/proj/e.txt '
1900 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1903 conv.logs[7].check(sym_log_msg('vtag-2',1), (
1904 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1905 ('/%(tags)s/vtag-2/proj/d.txt '
1906 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1909 conv.logs[8].check("Import (vbranchA, vtag-3).", (
1910 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1911 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1912 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1913 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1914 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1915 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1918 conv.logs[9].check("This commit was generated by cvs2svn "
1919 "to compensate for changes in r8,", (
1920 ('/%(trunk)s/proj/a.txt '
1921 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1922 ('/%(trunk)s/proj/b.txt '
1923 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1924 ('/%(trunk)s/proj/c.txt '
1925 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1926 ('/%(trunk)s/proj/d.txt '
1927 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1928 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1929 ('/%(trunk)s/proj/e.txt '
1930 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1933 conv.logs[10].check(sym_log_msg('vtag-3',1), (
1934 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1935 ('/%(tags)s/vtag-3/proj/d.txt '
1936 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1937 ('/%(tags)s/vtag-3/proj/e.txt '
1938 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1941 conv.logs[11].check("First regular commit, to a.txt, on vtag-3.", (
1942 ('/%(trunk)s/proj/a.txt', 'M'),
1945 conv.logs[12].check("Add a file to the working copy.", (
1946 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1949 conv.logs[13].check(sym_log_msg('vbranchA'), (
1950 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1951 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1954 conv.logs[14].check("Import (vbranchA, vtag-4).", (
1955 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1956 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1957 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1958 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1959 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1960 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1961 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1964 conv.logs[15].check("This commit was generated by cvs2svn "
1965 "to compensate for changes in r14,", (
1966 ('/%(trunk)s/proj/b.txt '
1967 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1968 ('/%(trunk)s/proj/c.txt '
1969 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1970 ('/%(trunk)s/proj/d.txt '
1971 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1972 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1973 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1974 'A'),
1975 ('/%(trunk)s/proj/e.txt '
1976 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1979 conv.logs[16].check(sym_log_msg('vtag-4',1), (
1980 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1981 ('/%(tags)s/vtag-4/proj/d.txt '
1982 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
1986 @Cvs2SvnTestFunction
1987 def default_branches_trunk_only():
1988 "handle default branches with --trunk-only"
1990 conv = ensure_conversion('default-branches', args=['--trunk-only'])
1992 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1993 ('/%(trunk)s/proj', 'A'),
1994 ('/%(trunk)s/proj/a.txt', 'A'),
1995 ('/%(trunk)s/proj/b.txt', 'A'),
1996 ('/%(trunk)s/proj/c.txt', 'A'),
1997 ('/%(trunk)s/proj/d.txt', 'A'),
1998 ('/%(trunk)s/proj/e.txt', 'A'),
1999 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2002 conv.logs[3].check("Import (vbranchA, vtag-2).", (
2003 ('/%(trunk)s/proj/a.txt', 'M'),
2004 ('/%(trunk)s/proj/b.txt', 'M'),
2005 ('/%(trunk)s/proj/c.txt', 'M'),
2006 ('/%(trunk)s/proj/d.txt', 'M'),
2007 ('/%(trunk)s/proj/e.txt', 'M'),
2008 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
2011 conv.logs[4].check("Import (vbranchA, vtag-3).", (
2012 ('/%(trunk)s/proj/a.txt', 'M'),
2013 ('/%(trunk)s/proj/b.txt', 'M'),
2014 ('/%(trunk)s/proj/c.txt', 'M'),
2015 ('/%(trunk)s/proj/d.txt', 'M'),
2016 ('/%(trunk)s/proj/e.txt', 'M'),
2017 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
2020 conv.logs[5].check("First regular commit, to a.txt, on vtag-3.", (
2021 ('/%(trunk)s/proj/a.txt', 'M'),
2024 conv.logs[6].check("Add a file to the working copy.", (
2025 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
2028 conv.logs[7].check("Import (vbranchA, vtag-4).", (
2029 ('/%(trunk)s/proj/b.txt', 'M'),
2030 ('/%(trunk)s/proj/c.txt', 'M'),
2031 ('/%(trunk)s/proj/d.txt', 'M'),
2032 ('/%(trunk)s/proj/e.txt', 'M'),
2033 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2037 @Cvs2SvnTestFunction
2038 def default_branch_and_1_2():
2039 "do not allow 1.2 revision with default branch"
2041 conv = ensure_conversion(
2042 'default-branch-and-1-2',
2043 error_re=(
2044 r'.*File \'.*\' has default branch=1\.1\.1 but also a revision 1\.2'
2049 @Cvs2SvnTestFunction
2050 def compose_tag_three_sources():
2051 "compose a tag from three sources"
2052 conv = ensure_conversion('compose-tag-three-sources')
2054 conv.logs[2].check("Add on trunk", (
2055 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
2056 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
2057 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
2058 ('/%(trunk)s/tagged-on-b1', 'A'),
2059 ('/%(trunk)s/tagged-on-b2', 'A'),
2062 conv.logs[3].check(sym_log_msg('b1'), (
2063 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
2066 conv.logs[4].check(sym_log_msg('b2'), (
2067 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
2070 conv.logs[5].check("Commit on branch b1", (
2071 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
2072 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
2073 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
2074 ('/%(branches)s/b1/tagged-on-b1', 'M'),
2075 ('/%(branches)s/b1/tagged-on-b2', 'M'),
2078 conv.logs[6].check("Commit on branch b2", (
2079 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
2080 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
2081 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
2082 ('/%(branches)s/b2/tagged-on-b1', 'M'),
2083 ('/%(branches)s/b2/tagged-on-b2', 'M'),
2086 conv.logs[7].check("Commit again on trunk", (
2087 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
2088 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
2089 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
2090 ('/%(trunk)s/tagged-on-b1', 'M'),
2091 ('/%(trunk)s/tagged-on-b2', 'M'),
2094 conv.logs[8].check(sym_log_msg('T',1), (
2095 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
2096 ('/%(tags)s/T/tagged-on-trunk-1.1 '
2097 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
2098 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:5)', 'R'),
2099 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:6)', 'R'),
2103 @Cvs2SvnTestFunction
2104 def pass5_when_to_fill():
2105 "reserve a svn revnum for a fill only when required"
2106 # The conversion will fail if the bug is present, and
2107 # ensure_conversion would raise Failure.
2108 conv = ensure_conversion('pass5-when-to-fill')
2111 class EmptyTrunk(Cvs2SvnTestCase):
2112 "don't break when the trunk is empty"
2114 def __init__(self, **kw):
2115 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
2117 def run(self, sbox):
2118 # The conversion will fail if the bug is present, and
2119 # ensure_conversion would raise Failure.
2120 conv = self.ensure_conversion()
2123 @Cvs2SvnTestFunction
2124 def no_spurious_svn_commits():
2125 "ensure that we don't create any spurious commits"
2126 conv = ensure_conversion('phoenix')
2128 # Check spurious commit that could be created in
2129 # SVNCommitCreator._pre_commit()
2131 # (When you add a file on a branch, CVS creates a trunk revision
2132 # in state 'dead'. If the log message of that commit is equal to
2133 # the one that CVS generates, we do not ever create a 'fill'
2134 # SVNCommit for it.)
2136 # and spurious commit that could be created in
2137 # SVNCommitCreator._commit()
2139 # (When you add a file on a branch, CVS creates a trunk revision
2140 # in state 'dead'. If the log message of that commit is equal to
2141 # the one that CVS generates, we do not create a primary SVNCommit
2142 # for it.)
2143 conv.logs[17].check('File added on branch xiphophorus', (
2144 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
2147 # Check to make sure that a commit *is* generated:
2148 # (When you add a file on a branch, CVS creates a trunk revision
2149 # in state 'dead'. If the log message of that commit is NOT equal
2150 # to the one that CVS generates, we create a primary SVNCommit to
2151 # serve as a home for the log message in question.
2152 conv.logs[18].check('file added-on-branch2.txt was initially added on '
2153 + 'branch xiphophorus,\nand this log message was tweaked', ())
2155 # Check spurious commit that could be created in
2156 # SVNCommitCreator._commit_symbols().
2157 conv.logs[19].check('This file was also added on branch xiphophorus,', (
2158 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
2162 class PeerPathPruning(Cvs2SvnTestCase):
2163 "make sure that filling prunes paths correctly"
2165 def __init__(self, **kw):
2166 Cvs2SvnTestCase.__init__(self, 'peer-path-pruning', **kw)
2168 def run(self, sbox):
2169 conv = self.ensure_conversion()
2170 conv.logs[6].check(sym_log_msg('BRANCH'), (
2171 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A'),
2172 ('/%(branches)s/BRANCH/bar', 'D'),
2173 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:5)', 'R'),
2177 @Cvs2SvnTestFunction
2178 def invalid_closings_on_trunk():
2179 "verify correct revs are copied to default branches"
2180 # The conversion will fail if the bug is present, and
2181 # ensure_conversion would raise Failure.
2182 conv = ensure_conversion('invalid-closings-on-trunk')
2185 @Cvs2SvnTestFunction
2186 def individual_passes():
2187 "run each pass individually"
2188 conv = ensure_conversion('main')
2189 conv2 = ensure_conversion('main', passbypass=1)
2191 if conv.logs != conv2.logs:
2192 raise Failure()
2195 @Cvs2SvnTestFunction
2196 def resync_bug():
2197 "reveal a big bug in our resync algorithm"
2198 # This will fail if the bug is present
2199 conv = ensure_conversion('resync-bug')
2202 @Cvs2SvnTestFunction
2203 def branch_from_default_branch():
2204 "reveal a bug in our default branch detection code"
2205 conv = ensure_conversion('branch-from-default-branch')
2207 # This revision will be a default branch synchronization only
2208 # if cvs2svn is correctly determining default branch revisions.
2210 # The bug was that cvs2svn was treating revisions on branches off of
2211 # default branches as default branch revisions, resulting in
2212 # incorrectly regarding the branch off of the default branch as a
2213 # non-trunk default branch. Crystal clear? I thought so. See
2214 # issue #42 for more incoherent blathering.
2215 conv.logs[5].check("This commit was generated by cvs2svn", (
2216 ('/%(trunk)s/proj/file.txt '
2217 '(from /%(branches)s/upstream/proj/file.txt:4)', 'R'),
2221 @Cvs2SvnTestFunction
2222 def file_in_attic_too():
2223 "die if a file exists in and out of the attic"
2224 ensure_conversion(
2225 'file-in-attic-too',
2226 error_re=(
2227 r'.*A CVS repository cannot contain both '
2228 r'(.*)' + re.escape(os.sep) + r'(.*) '
2229 + r'and '
2230 r'\1' + re.escape(os.sep) + r'Attic' + re.escape(os.sep) + r'\2'
2235 @Cvs2SvnTestFunction
2236 def retain_file_in_attic_too():
2237 "test --retain-conflicting-attic-files option"
2238 conv = ensure_conversion(
2239 'file-in-attic-too', args=['--retain-conflicting-attic-files'])
2240 if not conv.path_exists('trunk', 'file.txt'):
2241 raise Failure()
2242 if not conv.path_exists('trunk', 'Attic', 'file.txt'):
2243 raise Failure()
2246 @Cvs2SvnTestFunction
2247 def symbolic_name_filling_guide():
2248 "reveal a big bug in our SymbolFillingGuide"
2249 # This will fail if the bug is present
2250 conv = ensure_conversion('symbolic-name-overfill')
2253 # Helpers for tests involving file contents and properties.
2255 class NodeTreeWalkException:
2256 "Exception class for node tree traversals."
2257 pass
2259 def node_for_path(node, path):
2260 "In the tree rooted under SVNTree NODE, return the node at PATH."
2261 if node.name != '__SVN_ROOT_NODE':
2262 raise NodeTreeWalkException()
2263 path = path.strip('/')
2264 components = path.split('/')
2265 for component in components:
2266 node = svntest.tree.get_child(node, component)
2267 return node
2269 # Helper for tests involving properties.
2270 def props_for_path(node, path):
2271 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2272 return node_for_path(node, path).props
2275 class EOLMime(Cvs2SvnPropertiesTestCase):
2276 """eol settings and mime types together
2278 The files are as follows:
2280 trunk/foo.txt: no -kb, mime file says nothing.
2281 trunk/foo.xml: no -kb, mime file says text.
2282 trunk/foo.zip: no -kb, mime file says non-text.
2283 trunk/foo.bin: has -kb, mime file says nothing.
2284 trunk/foo.csv: has -kb, mime file says text.
2285 trunk/foo.dbf: has -kb, mime file says non-text.
2288 def __init__(self, args, **kw):
2289 # TODO: It's a bit klugey to construct this path here. But so far
2290 # there's only one test with a mime.types file. If we have more,
2291 # we should abstract this into some helper, which would be located
2292 # near ensure_conversion(). Note that it is a convention of this
2293 # test suite for a mime.types file to be located in the top level
2294 # of the CVS repository to which it applies.
2295 self.mime_path = os.path.join(
2296 test_data_dir, 'eol-mime-cvsrepos', 'mime.types')
2298 Cvs2SvnPropertiesTestCase.__init__(
2299 self, 'eol-mime',
2300 props_to_test=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2301 args=['--mime-types=%s' % self.mime_path] + args,
2302 **kw)
2305 # We do four conversions. Each time, we pass --mime-types=FILE with
2306 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2307 # Thus there's one conversion with neither flag, one with just the
2308 # former, one with just the latter, and one with both.
2311 # Neither --no-default-eol nor --eol-from-mime-type:
2312 eol_mime1 = EOLMime(
2313 variant=1,
2314 args=[],
2315 expected_props=[
2316 ('trunk/foo.txt', [None, None, None]),
2317 ('trunk/foo.xml', [None, 'text/xml', None]),
2318 ('trunk/foo.zip', [None, 'application/zip', None]),
2319 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2320 ('trunk/foo.csv', [None, 'text/csv', None]),
2321 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2325 # Just --no-default-eol, not --eol-from-mime-type:
2326 eol_mime2 = EOLMime(
2327 variant=2,
2328 args=['--default-eol=native'],
2329 expected_props=[
2330 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2331 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2332 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS]),
2333 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2334 ('trunk/foo.csv', [None, 'text/csv', None]),
2335 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2339 # Just --eol-from-mime-type, not --no-default-eol:
2340 eol_mime3 = EOLMime(
2341 variant=3,
2342 args=['--eol-from-mime-type'],
2343 expected_props=[
2344 ('trunk/foo.txt', [None, None, None]),
2345 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2346 ('trunk/foo.zip', [None, 'application/zip', None]),
2347 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2348 ('trunk/foo.csv', [None, 'text/csv', None]),
2349 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2353 # Both --no-default-eol and --eol-from-mime-type:
2354 eol_mime4 = EOLMime(
2355 variant=4,
2356 args=['--eol-from-mime-type', '--default-eol=native'],
2357 expected_props=[
2358 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2359 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2360 ('trunk/foo.zip', [None, 'application/zip', None]),
2361 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2362 ('trunk/foo.csv', [None, 'text/csv', None]),
2363 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2367 cvs_revnums_off = Cvs2SvnPropertiesTestCase(
2368 'eol-mime',
2369 doc='test non-setting of cvs2svn:cvs-rev property',
2370 args=[],
2371 props_to_test=['cvs2svn:cvs-rev'],
2372 expected_props=[
2373 ('trunk/foo.txt', [None]),
2374 ('trunk/foo.xml', [None]),
2375 ('trunk/foo.zip', [None]),
2376 ('trunk/foo.bin', [None]),
2377 ('trunk/foo.csv', [None]),
2378 ('trunk/foo.dbf', [None]),
2382 cvs_revnums_on = Cvs2SvnPropertiesTestCase(
2383 'eol-mime',
2384 doc='test setting of cvs2svn:cvs-rev property',
2385 args=['--cvs-revnums'],
2386 props_to_test=['cvs2svn:cvs-rev'],
2387 expected_props=[
2388 ('trunk/foo.txt', ['1.2']),
2389 ('trunk/foo.xml', ['1.2']),
2390 ('trunk/foo.zip', ['1.2']),
2391 ('trunk/foo.bin', ['1.2']),
2392 ('trunk/foo.csv', ['1.2']),
2393 ('trunk/foo.dbf', ['1.2']),
2397 keywords = Cvs2SvnPropertiesTestCase(
2398 'keywords',
2399 doc='test setting of svn:keywords property among others',
2400 args=['--default-eol=native'],
2401 props_to_test=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2402 expected_props=[
2403 ('trunk/foo.default', [KEYWORDS, 'native', None]),
2404 ('trunk/foo.kkvl', [KEYWORDS, 'native', None]),
2405 ('trunk/foo.kkv', [KEYWORDS, 'native', None]),
2406 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2407 ('trunk/foo.kk', [None, 'native', None]),
2408 ('trunk/foo.ko', [None, 'native', None]),
2409 ('trunk/foo.kv', [None, 'native', None]),
2413 @Cvs2SvnTestFunction
2414 def ignore():
2415 "test setting of svn:ignore property"
2416 conv = ensure_conversion('cvsignore')
2417 wc_tree = conv.get_wc_tree()
2418 topdir_props = props_for_path(wc_tree, 'trunk/proj')
2419 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
2421 if topdir_props['svn:ignore'] != \
2422 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2423 raise Failure()
2425 if subdir_props['svn:ignore'] != \
2426 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2427 raise Failure()
2430 @Cvs2SvnTestFunction
2431 def requires_cvs():
2432 "test that CVS can still do what RCS can't"
2433 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2434 conv = ensure_conversion(
2435 'requires-cvs', args=['--use-cvs', '--default-eol=native'],
2438 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
2439 cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read()
2441 if atsign_contents[-1:] == "@":
2442 raise Failure()
2443 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
2444 raise Failure()
2446 if not (conv.logs[6].author == "William Lyon Phelps III" and
2447 conv.logs[5].author == "j random"):
2448 raise Failure()
2451 @Cvs2SvnTestFunction
2452 def questionable_branch_names():
2453 "test that we can handle weird branch names"
2454 conv = ensure_conversion('questionable-symbols')
2455 # If the conversion succeeds, then we're okay. We could check the
2456 # actual branch paths, too, but the main thing is to know that the
2457 # conversion doesn't fail.
2460 @Cvs2SvnTestFunction
2461 def questionable_tag_names():
2462 "test that we can handle weird tag names"
2463 conv = ensure_conversion('questionable-symbols')
2464 conv.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2465 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2467 conv.find_tag_log('TagWith/Backslash_E').check(
2468 sym_log_msg('TagWith/Backslash_E',1),
2470 ('/%(tags)s/TagWith', 'A'),
2471 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2474 conv.find_tag_log('TagWith/Slash_Z').check(
2475 sym_log_msg('TagWith/Slash_Z',1),
2477 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2482 @Cvs2SvnTestFunction
2483 def revision_reorder_bug():
2484 "reveal a bug that reorders file revisions"
2485 conv = ensure_conversion('revision-reorder-bug')
2486 # If the conversion succeeds, then we're okay. We could check the
2487 # actual revisions, too, but the main thing is to know that the
2488 # conversion doesn't fail.
2491 @Cvs2SvnTestFunction
2492 def exclude():
2493 "test that exclude really excludes everything"
2494 conv = ensure_conversion('main', args=['--exclude=.*'])
2495 for log in conv.logs.values():
2496 for item in log.changed_paths.keys():
2497 if item.startswith('/branches/') or item.startswith('/tags/'):
2498 raise Failure()
2501 @Cvs2SvnTestFunction
2502 def vendor_branch_delete_add():
2503 "add trunk file that was deleted on vendor branch"
2504 # This will error if the bug is present
2505 conv = ensure_conversion('vendor-branch-delete-add')
2508 @Cvs2SvnTestFunction
2509 def resync_pass2_pull_forward():
2510 "ensure pass2 doesn't pull rev too far forward"
2511 conv = ensure_conversion('resync-pass2-pull-forward')
2512 # If the conversion succeeds, then we're okay. We could check the
2513 # actual revisions, too, but the main thing is to know that the
2514 # conversion doesn't fail.
2517 @Cvs2SvnTestFunction
2518 def native_eol():
2519 "only LFs for svn:eol-style=native files"
2520 conv = ensure_conversion('native-eol', args=['--default-eol=native'])
2521 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
2522 conv.repos)
2523 # Verify that all files in the dump have LF EOLs. We're actually
2524 # testing the whole dump file, but the dump file itself only uses
2525 # LF EOLs, so we're safe.
2526 for line in lines:
2527 if line[-1] != '\n' or line[:-1].find('\r') != -1:
2528 raise Failure()
2531 @Cvs2SvnTestFunction
2532 def double_fill():
2533 "reveal a bug that created a branch twice"
2534 conv = ensure_conversion('double-fill')
2535 # If the conversion succeeds, then we're okay. We could check the
2536 # actual revisions, too, but the main thing is to know that the
2537 # conversion doesn't fail.
2540 @XFail_deco()
2541 @Cvs2SvnTestFunction
2542 def double_fill2():
2543 "reveal a second bug that created a branch twice"
2544 conv = ensure_conversion('double-fill2')
2545 conv.logs[6].check_msg(sym_log_msg('BRANCH1'))
2546 conv.logs[7].check_msg(sym_log_msg('BRANCH2'))
2547 try:
2548 # This check should fail:
2549 conv.logs[8].check_msg(sym_log_msg('BRANCH2'))
2550 except Failure:
2551 pass
2552 else:
2553 raise Failure('Symbol filled twice in a row')
2556 @Cvs2SvnTestFunction
2557 def resync_pass2_push_backward():
2558 "ensure pass2 doesn't push rev too far backward"
2559 conv = ensure_conversion('resync-pass2-push-backward')
2560 # If the conversion succeeds, then we're okay. We could check the
2561 # actual revisions, too, but the main thing is to know that the
2562 # conversion doesn't fail.
2565 @Cvs2SvnTestFunction
2566 def double_add():
2567 "reveal a bug that added a branch file twice"
2568 conv = ensure_conversion('double-add')
2569 # If the conversion succeeds, then we're okay. We could check the
2570 # actual revisions, too, but the main thing is to know that the
2571 # conversion doesn't fail.
2574 @Cvs2SvnTestFunction
2575 def bogus_branch_copy():
2576 "reveal a bug that copies a branch file wrongly"
2577 conv = ensure_conversion('bogus-branch-copy')
2578 # If the conversion succeeds, then we're okay. We could check the
2579 # actual revisions, too, but the main thing is to know that the
2580 # conversion doesn't fail.
2583 @Cvs2SvnTestFunction
2584 def nested_ttb_directories():
2585 "require error if ttb directories are not disjoint"
2586 opts_list = [
2587 {'trunk' : 'a', 'branches' : 'a',},
2588 {'trunk' : 'a', 'tags' : 'a',},
2589 {'branches' : 'a', 'tags' : 'a',},
2590 # This option conflicts with the default trunk path:
2591 {'branches' : 'trunk',},
2592 # Try some nested directories:
2593 {'trunk' : 'a', 'branches' : 'a/b',},
2594 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2595 {'branches' : 'a', 'tags' : 'a/b',},
2598 for opts in opts_list:
2599 ensure_conversion(
2600 'main', error_re=r'The following paths are not disjoint\:', **opts
2604 class AutoProps(Cvs2SvnPropertiesTestCase):
2605 """Test auto-props.
2607 The files are as follows:
2609 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2610 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2611 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2612 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2613 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2614 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2615 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2616 trunk/foo.UPCASE1: no -kb, no mime type.
2617 trunk/foo.UPCASE2: no -kb, no mime type.
2620 def __init__(self, args, **kw):
2621 ### TODO: It's a bit klugey to construct this path here. See also
2622 ### the comment in eol_mime().
2623 auto_props_path = os.path.join(
2624 test_data_dir, 'eol-mime-cvsrepos', 'auto-props')
2626 Cvs2SvnPropertiesTestCase.__init__(
2627 self, 'eol-mime',
2628 props_to_test=[
2629 'myprop',
2630 'svn:eol-style',
2631 'svn:mime-type',
2632 'svn:keywords',
2633 'svn:executable',
2635 args=[
2636 '--auto-props=%s' % auto_props_path,
2637 '--eol-from-mime-type'
2638 ] + args,
2639 **kw)
2642 auto_props_ignore_case = AutoProps(
2643 doc="test auto-props",
2644 args=['--default-eol=native'],
2645 expected_props=[
2646 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS, None]),
2647 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS, None]),
2648 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2649 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2650 ('trunk/foo.bin',
2651 ['bin', None, 'application/octet-stream', None, '']),
2652 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2653 ('trunk/foo.dbf',
2654 ['dbf', None, 'application/what-is-dbf', None, None]),
2655 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS, None]),
2656 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS, None]),
2660 @Cvs2SvnTestFunction
2661 def ctrl_char_in_filename():
2662 "do not allow control characters in filenames"
2664 try:
2665 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
2666 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
2667 if os.path.exists(dstrepos_path):
2668 safe_rmtree(dstrepos_path)
2670 # create repos from existing main repos
2671 shutil.copytree(srcrepos_path, dstrepos_path)
2672 base_path = os.path.join(dstrepos_path, 'single-files')
2673 try:
2674 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2675 os.path.join(base_path, 'two\rquick,v'))
2676 except:
2677 # Operating systems that don't allow control characters in
2678 # filenames will hopefully have thrown an exception; in that
2679 # case, just skip this test.
2680 raise svntest.Skip()
2682 conv = ensure_conversion(
2683 'ctrl-char-filename',
2684 error_re=(r'.*Subversion does not allow character .*.'),
2686 finally:
2687 safe_rmtree(dstrepos_path)
2690 @Cvs2SvnTestFunction
2691 def commit_dependencies():
2692 "interleaved and multi-branch commits to same files"
2693 conv = ensure_conversion("commit-dependencies")
2694 conv.logs[2].check('adding', (
2695 ('/%(trunk)s/interleaved', 'A'),
2696 ('/%(trunk)s/interleaved/file1', 'A'),
2697 ('/%(trunk)s/interleaved/file2', 'A'),
2699 conv.logs[3].check('big commit', (
2700 ('/%(trunk)s/interleaved/file1', 'M'),
2701 ('/%(trunk)s/interleaved/file2', 'M'),
2703 conv.logs[4].check('dependant small commit', (
2704 ('/%(trunk)s/interleaved/file1', 'M'),
2706 conv.logs[5].check('adding', (
2707 ('/%(trunk)s/multi-branch', 'A'),
2708 ('/%(trunk)s/multi-branch/file1', 'A'),
2709 ('/%(trunk)s/multi-branch/file2', 'A'),
2711 conv.logs[6].check(sym_log_msg("branch"), (
2712 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2713 ('/%(branches)s/branch/interleaved', 'D'),
2715 conv.logs[7].check('multi-branch-commit', (
2716 ('/%(trunk)s/multi-branch/file1', 'M'),
2717 ('/%(trunk)s/multi-branch/file2', 'M'),
2718 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2719 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2723 @Cvs2SvnTestFunction
2724 def double_branch_delete():
2725 "fill branches before modifying files on them"
2726 conv = ensure_conversion('double-branch-delete')
2728 # Test for issue #102. The file IMarshalledValue.java is branched,
2729 # deleted, readded on the branch, and then deleted again. If the
2730 # fill for the file on the branch is postponed until after the
2731 # modification, the file will end up live on the branch instead of
2732 # dead! Make sure it happens at the right time.
2734 conv.logs[6].check('JBAS-2436 - Adding LGPL Header2', (
2735 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2738 conv.logs[7].check('JBAS-3025 - Removing dependency', (
2739 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2743 @Cvs2SvnTestFunction
2744 def symbol_mismatches():
2745 "error for conflicting tag/branch"
2747 ensure_conversion(
2748 'symbol-mess',
2749 args=['--symbol-default=strict'],
2750 error_re=r'.*Problems determining how symbols should be converted',
2754 @Cvs2SvnTestFunction
2755 def overlook_symbol_mismatches():
2756 "overlook conflicting tag/branch when --trunk-only"
2758 # This is a test for issue #85.
2760 ensure_conversion('symbol-mess', args=['--trunk-only'])
2763 @Cvs2SvnTestFunction
2764 def force_symbols():
2765 "force symbols to be tags/branches"
2767 conv = ensure_conversion(
2768 'symbol-mess',
2769 args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2770 if conv.path_exists('tags', 'BRANCH') \
2771 or not conv.path_exists('branches', 'BRANCH'):
2772 raise Failure()
2773 if not conv.path_exists('tags', 'TAG') \
2774 or conv.path_exists('branches', 'TAG'):
2775 raise Failure()
2776 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2777 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2778 raise Failure()
2779 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2780 or conv.path_exists('branches', 'MOSTLY_TAG'):
2781 raise Failure()
2784 @Cvs2SvnTestFunction
2785 def commit_blocks_tags():
2786 "commit prevents forced tag"
2788 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2789 ensure_conversion(
2790 'symbol-mess',
2791 args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']),
2792 error_re=(
2793 r'.*The following branches cannot be forced to be tags '
2794 r'because they have commits'
2799 @Cvs2SvnTestFunction
2800 def blocked_excludes():
2801 "error for blocked excludes"
2803 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2804 for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']:
2805 try:
2806 ensure_conversion(
2807 'symbol-mess',
2808 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker]))
2809 raise MissingErrorException()
2810 except Failure:
2811 pass
2814 @Cvs2SvnTestFunction
2815 def unblock_blocked_excludes():
2816 "excluding blocker removes blockage"
2818 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2819 for blocker in ['BRANCH', 'COMMIT']:
2820 ensure_conversion(
2821 'symbol-mess',
2822 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker,
2823 '--exclude=BLOCKING_%s' % blocker]))
2826 @Cvs2SvnTestFunction
2827 def regexp_force_symbols():
2828 "force symbols via regular expressions"
2830 conv = ensure_conversion(
2831 'symbol-mess',
2832 args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2833 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2834 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2835 raise Failure()
2836 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2837 or conv.path_exists('branches', 'MOSTLY_TAG'):
2838 raise Failure()
2841 @Cvs2SvnTestFunction
2842 def heuristic_symbol_default():
2843 "test 'heuristic' symbol default"
2845 conv = ensure_conversion(
2846 'symbol-mess', args=['--symbol-default=heuristic'])
2847 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2848 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2849 raise Failure()
2850 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2851 or conv.path_exists('branches', 'MOSTLY_TAG'):
2852 raise Failure()
2855 @Cvs2SvnTestFunction
2856 def branch_symbol_default():
2857 "test 'branch' symbol default"
2859 conv = ensure_conversion(
2860 'symbol-mess', args=['--symbol-default=branch'])
2861 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2862 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2863 raise Failure()
2864 if conv.path_exists('tags', 'MOSTLY_TAG') \
2865 or not conv.path_exists('branches', 'MOSTLY_TAG'):
2866 raise Failure()
2869 @Cvs2SvnTestFunction
2870 def tag_symbol_default():
2871 "test 'tag' symbol default"
2873 conv = ensure_conversion(
2874 'symbol-mess', args=['--symbol-default=tag'])
2875 if not conv.path_exists('tags', 'MOSTLY_BRANCH') \
2876 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
2877 raise Failure()
2878 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2879 or conv.path_exists('branches', 'MOSTLY_TAG'):
2880 raise Failure()
2883 @Cvs2SvnTestFunction
2884 def symbol_transform():
2885 "test --symbol-transform"
2887 conv = ensure_conversion(
2888 'symbol-mess',
2889 args=[
2890 '--symbol-default=heuristic',
2891 '--symbol-transform=BRANCH:branch',
2892 '--symbol-transform=TAG:tag',
2893 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2895 if not conv.path_exists('branches', 'branch'):
2896 raise Failure()
2897 if not conv.path_exists('tags', 'tag'):
2898 raise Failure()
2899 if not conv.path_exists('branches', 'MOSTLY.BRANCH'):
2900 raise Failure()
2901 if not conv.path_exists('tags', 'MOSTLY.TAG'):
2902 raise Failure()
2905 @Cvs2SvnTestFunction
2906 def write_symbol_info():
2907 "test --write-symbol-info"
2909 expected_lines = [
2910 ['0', '.trunk.',
2911 'trunk', 'trunk', '.'],
2912 ['0', 'BLOCKED_BY_UNNAMED',
2913 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2914 ['0', 'BLOCKING_COMMIT',
2915 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2916 ['0', 'BLOCKED_BY_COMMIT',
2917 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2918 ['0', 'BLOCKING_BRANCH',
2919 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2920 ['0', 'BLOCKED_BY_BRANCH',
2921 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2922 ['0', 'MOSTLY_BRANCH',
2923 '.', '.', '.'],
2924 ['0', 'MOSTLY_TAG',
2925 '.', '.', '.'],
2926 ['0', 'BRANCH_WITH_COMMIT',
2927 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2928 ['0', 'BRANCH',
2929 'branch', 'branches/BRANCH', '.trunk.'],
2930 ['0', 'TAG',
2931 'tag', 'tags/TAG', '.trunk.'],
2932 ['0', 'unlabeled-1.1.12.1.2',
2933 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2935 expected_lines.sort()
2937 symbol_info_file = os.path.join(tmp_dir, 'symbol-mess-symbol-info.txt')
2938 try:
2939 ensure_conversion(
2940 'symbol-mess',
2941 args=[
2942 '--symbol-default=strict',
2943 '--write-symbol-info=%s' % (symbol_info_file,),
2944 '--passes=:CollateSymbolsPass',
2947 raise MissingErrorException()
2948 except Failure:
2949 pass
2950 lines = []
2951 comment_re = re.compile(r'^\s*\#')
2952 for l in open(symbol_info_file, 'r'):
2953 if comment_re.match(l):
2954 continue
2955 lines.append(l.strip().split())
2956 lines.sort()
2957 if lines != expected_lines:
2958 s = ['Symbol info incorrect\n']
2959 differ = Differ()
2960 for diffline in differ.compare(
2961 [' '.join(line) + '\n' for line in expected_lines],
2962 [' '.join(line) + '\n' for line in lines],
2964 s.append(diffline)
2965 raise Failure(''.join(s))
2968 @Cvs2SvnTestFunction
2969 def symbol_hints():
2970 "test --symbol-hints for setting branch/tag"
2972 conv = ensure_conversion(
2973 'symbol-mess', symbol_hints_file='symbol-mess-symbol-hints.txt',
2975 if not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2976 raise Failure()
2977 if not conv.path_exists('tags', 'MOSTLY_TAG'):
2978 raise Failure()
2979 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2980 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2982 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2983 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
2985 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2986 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
2990 @Cvs2SvnTestFunction
2991 def parent_hints():
2992 "test --symbol-hints for setting parent"
2994 conv = ensure_conversion(
2995 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints.txt',
2997 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2998 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3002 @Cvs2SvnTestFunction
3003 def parent_hints_invalid():
3004 "test --symbol-hints with an invalid parent"
3006 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3007 # this symbol hints file sets the preferred parent to BRANCH
3008 # instead:
3009 conv = ensure_conversion(
3010 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-invalid.txt',
3011 error_re=(
3012 r"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
3017 @Cvs2SvnTestFunction
3018 def parent_hints_wildcards():
3019 "test --symbol-hints wildcards"
3021 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3022 # this symbol hints file sets the preferred parent to BRANCH
3023 # instead:
3024 conv = ensure_conversion(
3025 'symbol-mess',
3026 symbol_hints_file='symbol-mess-parent-hints-wildcards.txt',
3028 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3029 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3033 @Cvs2SvnTestFunction
3034 def path_hints():
3035 "test --symbol-hints for setting svn paths"
3037 conv = ensure_conversion(
3038 'symbol-mess', symbol_hints_file='symbol-mess-path-hints.txt',
3040 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3041 ('/trunk', 'A'),
3042 ('/a', 'A'),
3043 ('/a/strange', 'A'),
3044 ('/a/strange/trunk', 'A'),
3045 ('/a/strange/trunk/path', 'A'),
3046 ('/branches', 'A'),
3047 ('/tags', 'A'),
3049 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3050 ('/special', 'A'),
3051 ('/special/tag', 'A'),
3052 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
3054 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3055 ('/special/other', 'A'),
3056 ('/special/other/branch', 'A'),
3057 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
3059 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3060 ('/special/branch', 'A'),
3061 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
3065 @Cvs2SvnTestFunction
3066 def issue_99():
3067 "test problem from issue 99"
3069 conv = ensure_conversion('issue-99')
3072 @Cvs2SvnTestFunction
3073 def issue_100():
3074 "test problem from issue 100"
3076 conv = ensure_conversion('issue-100')
3077 file1 = conv.get_wc('trunk', 'file1.txt')
3078 if file(file1).read() != 'file1.txt<1.2>\n':
3079 raise Failure()
3082 @Cvs2SvnTestFunction
3083 def issue_106():
3084 "test problem from issue 106"
3086 conv = ensure_conversion('issue-106')
3089 @Cvs2SvnTestFunction
3090 def options_option():
3091 "use of the --options option"
3093 conv = ensure_conversion('main', options_file='cvs2svn.options')
3096 @Cvs2SvnTestFunction
3097 def multiproject():
3098 "multiproject conversion"
3100 conv = ensure_conversion(
3101 'main', options_file='cvs2svn-multiproject.options'
3103 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3104 ('/partial-prune', 'A'),
3105 ('/partial-prune/trunk', 'A'),
3106 ('/partial-prune/branches', 'A'),
3107 ('/partial-prune/tags', 'A'),
3108 ('/partial-prune/releases', 'A'),
3112 @Cvs2SvnTestFunction
3113 def crossproject():
3114 "multiproject conversion with cross-project commits"
3116 conv = ensure_conversion(
3117 'main', options_file='cvs2svn-crossproject.options'
3121 @Cvs2SvnTestFunction
3122 def tag_with_no_revision():
3123 "tag defined but revision is deleted"
3125 conv = ensure_conversion('tag-with-no-revision')
3128 @Cvs2SvnTestFunction
3129 def delete_cvsignore():
3130 "svn:ignore should vanish when .cvsignore does"
3132 # This is issue #81.
3134 conv = ensure_conversion('delete-cvsignore')
3136 wc_tree = conv.get_wc_tree()
3137 props = props_for_path(wc_tree, 'trunk/proj')
3139 if props.has_key('svn:ignore'):
3140 raise Failure()
3143 @Cvs2SvnTestFunction
3144 def repeated_deltatext():
3145 "ignore repeated deltatext blocks with warning"
3147 conv = ensure_conversion('repeated-deltatext')
3148 warning_re = r'.*Deltatext block for revision 1.1 appeared twice'
3149 if not conv.output_found(warning_re):
3150 raise Failure()
3153 @Cvs2SvnTestFunction
3154 def nasty_graphs():
3155 "process some nasty dependency graphs"
3157 # It's not how well the bear can dance, but that the bear can dance
3158 # at all:
3159 conv = ensure_conversion('nasty-graphs')
3162 @XFail_deco()
3163 @Cvs2SvnTestFunction
3164 def tagging_after_delete():
3165 "optimal tag after deleting files"
3167 conv = ensure_conversion('tagging-after-delete')
3169 # tag should be 'clean', no deletes
3170 log = conv.find_tag_log('tag1')
3171 expected = (
3172 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
3174 log.check_changes(expected)
3177 @Cvs2SvnTestFunction
3178 def crossed_branches():
3179 "branches created in inconsistent orders"
3181 conv = ensure_conversion('crossed-branches')
3184 @Cvs2SvnTestFunction
3185 def file_directory_conflict():
3186 "error when filename conflicts with directory name"
3188 conv = ensure_conversion(
3189 'file-directory-conflict',
3190 error_re=r'.*Directory name conflicts with filename',
3194 @Cvs2SvnTestFunction
3195 def attic_directory_conflict():
3196 "error when attic filename conflicts with dirname"
3198 # This tests the problem reported in issue #105.
3200 conv = ensure_conversion(
3201 'attic-directory-conflict',
3202 error_re=r'.*Directory name conflicts with filename',
3206 @Cvs2SvnTestFunction
3207 def use_rcs():
3208 "verify that --use-rcs and --use-internal-co agree"
3210 rcs_conv = ensure_conversion(
3211 'main', args=['--use-rcs', '--default-eol=native'], dumpfile='use-rcs-rcs.dump',
3213 conv = ensure_conversion(
3214 'main', args=['--default-eol=native'], dumpfile='use-rcs-int.dump',
3216 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3217 raise Failure()
3218 rcs_lines = list(open(rcs_conv.dumpfile, 'rb'))
3219 lines = list(open(conv.dumpfile, 'rb'))
3220 # Compare all lines following the repository UUID:
3221 if lines[3:] != rcs_lines[3:]:
3222 raise Failure()
3225 @Cvs2SvnTestFunction
3226 def internal_co_exclude():
3227 "verify that --use-internal-co --exclude=... works"
3229 rcs_conv = ensure_conversion(
3230 'internal-co',
3231 args=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3232 dumpfile='internal-co-exclude-rcs.dump',
3234 conv = ensure_conversion(
3235 'internal-co',
3236 args=['--exclude=BRANCH', '--default-eol=native'],
3237 dumpfile='internal-co-exclude-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_trunk_only():
3250 "verify that --use-internal-co --trunk-only works"
3252 conv = ensure_conversion(
3253 'internal-co',
3254 args=['--trunk-only', '--default-eol=native'],
3256 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3257 raise Failure()
3260 @Cvs2SvnTestFunction
3261 def leftover_revs():
3262 "check for leftover checked-out revisions"
3264 conv = ensure_conversion(
3265 'leftover-revs',
3266 args=['--exclude=BRANCH', '--default-eol=native'],
3268 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3269 raise Failure()
3272 @Cvs2SvnTestFunction
3273 def requires_internal_co():
3274 "test that internal co can do more than RCS"
3275 # See issues 4, 11 for the bugs whose regression we're testing for.
3276 # Unlike in requires_cvs above, issue 29 is not covered.
3277 conv = ensure_conversion('requires-cvs')
3279 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
3281 if atsign_contents[-1:] == "@":
3282 raise Failure()
3284 if not (conv.logs[6].author == "William Lyon Phelps III" and
3285 conv.logs[5].author == "j random"):
3286 raise Failure()
3289 @Cvs2SvnTestFunction
3290 def internal_co_keywords():
3291 "test that internal co handles keywords correctly"
3292 conv_ic = ensure_conversion('internal-co-keywords',
3293 args=["--keywords-off"])
3294 conv_cvs = ensure_conversion('internal-co-keywords',
3295 args=["--use-cvs", "--keywords-off"])
3297 ko_ic = file(conv_ic.get_wc('trunk', 'dir', 'ko.txt')).read()
3298 ko_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'ko.txt')).read()
3299 kk_ic = file(conv_ic.get_wc('trunk', 'dir', 'kk.txt')).read()
3300 kk_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kk.txt')).read()
3301 kv_ic = file(conv_ic.get_wc('trunk', 'dir', 'kv.txt')).read()
3302 kv_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kv.txt')).read()
3303 # Ensure proper "/Attic" expansion of $Source$ keyword in files
3304 # which are in a deleted state in trunk
3305 del_ic = file(conv_ic.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3306 del_cvs = file(conv_cvs.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3309 if ko_ic != ko_cvs:
3310 raise Failure()
3311 if kk_ic != kk_cvs:
3312 raise Failure()
3313 if del_ic != del_cvs:
3314 raise Failure()
3316 # The date format changed between cvs and co ('/' instead of '-').
3317 # Accept either one:
3318 date_substitution_re = re.compile(r' ([0-9]*)-([0-9]*)-([0-9]*) ')
3319 if kv_ic != kv_cvs \
3320 and date_substitution_re.sub(r' \1/\2/\3 ', kv_ic) != kv_cvs:
3321 raise Failure()
3324 @Cvs2SvnTestFunction
3325 def timestamp_chaos():
3326 "test timestamp adjustments"
3328 conv = ensure_conversion('timestamp-chaos', args=["-v"])
3330 # The times are expressed here in UTC:
3331 times = [
3332 '2007-01-01 21:00:00', # Initial commit
3333 '2007-01-01 21:00:00', # revision 1.1 of both files
3334 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3335 '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards
3336 '2007-01-01 22:00:00', # revision 1.3 of both files
3339 # Convert the times to seconds since the epoch, in UTC:
3340 times = [calendar.timegm(svn_strptime(t)) for t in times]
3342 for i in range(len(times)):
3343 if abs(conv.logs[i + 1].date - times[i]) > 0.1:
3344 raise Failure()
3347 @Cvs2SvnTestFunction
3348 def symlinks():
3349 "convert a repository that contains symlinks"
3351 # This is a test for issue #97.
3353 proj = os.path.join(test_data_dir, 'symlinks-cvsrepos', 'proj')
3354 links = [
3356 os.path.join('..', 'file.txt,v'),
3357 os.path.join(proj, 'dir1', 'file.txt,v'),
3360 'dir1',
3361 os.path.join(proj, 'dir2'),
3365 try:
3366 os.symlink
3367 except AttributeError:
3368 # Apparently this OS doesn't support symlinks, so skip test.
3369 raise svntest.Skip()
3371 try:
3372 for (src,dst) in links:
3373 os.symlink(src, dst)
3375 conv = ensure_conversion('symlinks')
3376 conv.logs[2].check('', (
3377 ('/%(trunk)s/proj', 'A'),
3378 ('/%(trunk)s/proj/file.txt', 'A'),
3379 ('/%(trunk)s/proj/dir1', 'A'),
3380 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3381 ('/%(trunk)s/proj/dir2', 'A'),
3382 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3384 finally:
3385 for (src,dst) in links:
3386 os.remove(dst)
3389 @Cvs2SvnTestFunction
3390 def empty_trunk_path():
3391 "allow --trunk to be empty if --trunk-only"
3393 # This is a test for issue #53.
3395 conv = ensure_conversion(
3396 'main', args=['--trunk-only', '--trunk='],
3400 @Cvs2SvnTestFunction
3401 def preferred_parent_cycle():
3402 "handle a cycle in branch parent preferences"
3404 conv = ensure_conversion('preferred-parent-cycle')
3407 @Cvs2SvnTestFunction
3408 def branch_from_empty_dir():
3409 "branch from an empty directory"
3411 conv = ensure_conversion('branch-from-empty-dir')
3414 @Cvs2SvnTestFunction
3415 def trunk_readd():
3416 "add a file on a branch then on trunk"
3418 conv = ensure_conversion('trunk-readd')
3421 @Cvs2SvnTestFunction
3422 def branch_from_deleted_1_1():
3423 "branch from a 1.1 revision that will be deleted"
3425 conv = ensure_conversion('branch-from-deleted-1-1')
3426 conv.logs[5].check('Adding b.txt:1.1.2.1', (
3427 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3429 conv.logs[6].check('Adding b.txt:1.1.4.1', (
3430 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3432 conv.logs[7].check('Adding b.txt:1.2', (
3433 ('/%(trunk)s/proj/b.txt', 'A'),
3436 conv.logs[8].check('Adding c.txt:1.1.2.1', (
3437 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3439 conv.logs[9].check('Adding c.txt:1.1.4.1', (
3440 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3444 @Cvs2SvnTestFunction
3445 def add_on_branch():
3446 "add a file on a branch using newer CVS"
3448 conv = ensure_conversion('add-on-branch')
3449 conv.logs[6].check('Adding b.txt:1.1', (
3450 ('/%(trunk)s/proj/b.txt', 'A'),
3452 conv.logs[7].check('Adding b.txt:1.1.2.2', (
3453 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3455 conv.logs[8].check('Adding c.txt:1.1', (
3456 ('/%(trunk)s/proj/c.txt', 'A'),
3458 conv.logs[9].check('Removing c.txt:1.2', (
3459 ('/%(trunk)s/proj/c.txt', 'D'),
3461 conv.logs[10].check('Adding c.txt:1.2.2.2', (
3462 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3464 conv.logs[11].check('Adding d.txt:1.1', (
3465 ('/%(trunk)s/proj/d.txt', 'A'),
3467 conv.logs[12].check('Adding d.txt:1.1.2.2', (
3468 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3472 @Cvs2SvnTestFunction
3473 def main_git():
3474 "test output in git-fast-import format"
3476 # Note: To test importing into git, do
3478 # ./run-tests <this-test-number>
3479 # rm -rf cvs2svn-tmp/main.git
3480 # git init --bare cvs2svn-tmp/main.git
3481 # cd cvs2svn-tmp/main.git
3482 # cat ../git-{blob,dump}.dat | git fast-import
3484 # Or, to load the dumpfiles separately:
3486 # cat ../git-blob.dat | git fast-import --export-marks=../git-marks.dat
3487 # cat ../git-dump.dat | git fast-import --import-marks=../git-marks.dat
3489 # Then use "gitk --all", "git log", etc. to test the contents of the
3490 # repository or "git clone" to make a non-bare clone.
3492 # We don't have the infrastructure to check that the resulting git
3493 # repository is correct, so we just check that the conversion runs
3494 # to completion:
3495 conv = GitConversion('main', None, [
3496 '--blobfile=cvs2svn-tmp/git-blob.dat',
3497 '--dumpfile=cvs2svn-tmp/git-dump.dat',
3498 '--username=cvs2git',
3499 'test-data/main-cvsrepos',
3503 @Cvs2SvnTestFunction
3504 def main_git2():
3505 "test cvs2git --use-external-blob-generator option"
3507 # See comment in main_git() for more information.
3509 conv = GitConversion('main', None, [
3510 '--use-external-blob-generator',
3511 '--blobfile=cvs2svn-tmp/blobfile.out',
3512 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3513 '--username=cvs2git',
3514 'test-data/main-cvsrepos',
3518 @Cvs2SvnTestFunction
3519 def git_options():
3520 "test cvs2git using options file"
3522 conv = GitConversion('main', None, [], options_file='cvs2git.options')
3525 @Cvs2SvnTestFunction
3526 def main_hg():
3527 "output in git-fast-import format with inline data"
3529 # The output should be suitable for import by Mercurial.
3531 # We don't have the infrastructure to check that the resulting
3532 # Mercurial repository is correct, so we just check that the
3533 # conversion runs to completion:
3534 conv = GitConversion('main', None, [], options_file='cvs2hg.options')
3537 @Cvs2SvnTestFunction
3538 def invalid_symbol():
3539 "a symbol with the incorrect format"
3541 conv = ensure_conversion('invalid-symbol')
3542 if not conv.output_found(
3543 r".*branch 'SYMBOL' references invalid revision 1$"
3545 raise Failure()
3548 @Cvs2SvnTestFunction
3549 def invalid_symbol_ignore():
3550 "ignore a symbol using a SymbolMapper"
3552 conv = ensure_conversion(
3553 'invalid-symbol', options_file='cvs2svn-ignore.options'
3557 @Cvs2SvnTestFunction
3558 def invalid_symbol_ignore2():
3559 "ignore a symbol using an IgnoreSymbolTransform"
3561 conv = ensure_conversion(
3562 'invalid-symbol', options_file='cvs2svn-ignore2.options'
3566 class EOLVariants(Cvs2SvnTestCase):
3567 "handle various --eol-style options"
3569 eol_style_strings = {
3570 'LF' : '\n',
3571 'CR' : '\r',
3572 'CRLF' : '\r\n',
3573 'native' : '\n',
3576 def __init__(self, eol_style):
3577 self.eol_style = eol_style
3578 self.dumpfile = 'eol-variants-%s.dump' % (self.eol_style,)
3579 Cvs2SvnTestCase.__init__(
3580 self, 'eol-variants', variant=self.eol_style,
3581 dumpfile=self.dumpfile,
3582 args=[
3583 '--default-eol=%s' % (self.eol_style,),
3587 def run(self, sbox):
3588 conv = self.ensure_conversion()
3589 dump_contents = open(conv.dumpfile, 'rb').read()
3590 expected_text = self.eol_style_strings[self.eol_style].join(
3591 ['line 1', 'line 2', '\n\n']
3593 if not dump_contents.endswith(expected_text):
3594 raise Failure()
3597 @Cvs2SvnTestFunction
3598 def no_revs_file():
3599 "handle a file with no revisions (issue #80)"
3601 conv = ensure_conversion('no-revs-file')
3604 @Cvs2SvnTestFunction
3605 def mirror_keyerror_test():
3606 "a case that gave KeyError in SVNRepositoryMirror"
3608 conv = ensure_conversion('mirror-keyerror')
3611 @Cvs2SvnTestFunction
3612 def exclude_ntdb_test():
3613 "exclude a non-trunk default branch"
3615 symbol_info_file = os.path.join(tmp_dir, 'exclude-ntdb-symbol-info.txt')
3616 conv = ensure_conversion(
3617 'exclude-ntdb',
3618 args=[
3619 '--write-symbol-info=%s' % (symbol_info_file,),
3620 '--exclude=branch3',
3621 '--exclude=tag3',
3622 '--exclude=vendortag3',
3623 '--exclude=vendorbranch',
3628 @Cvs2SvnTestFunction
3629 def mirror_keyerror2_test():
3630 "a case that gave KeyError in RepositoryMirror"
3632 conv = ensure_conversion('mirror-keyerror2')
3635 @Cvs2SvnTestFunction
3636 def mirror_keyerror3_test():
3637 "a case that gave KeyError in RepositoryMirror"
3639 conv = ensure_conversion('mirror-keyerror3')
3642 @XFail_deco()
3643 @Cvs2SvnTestFunction
3644 def add_cvsignore_to_branch_test():
3645 "check adding .cvsignore to an existing branch"
3647 # This a test for issue #122.
3649 conv = ensure_conversion('add-cvsignore-to-branch')
3650 wc_tree = conv.get_wc_tree()
3651 trunk_props = props_for_path(wc_tree, 'trunk/dir')
3652 if trunk_props['svn:ignore'] != '*.o\n\n':
3653 raise Failure()
3655 branch_props = props_for_path(wc_tree, 'branches/BRANCH/dir')
3656 if branch_props['svn:ignore'] != '*.o\n\n':
3657 raise Failure()
3660 @Cvs2SvnTestFunction
3661 def missing_deltatext():
3662 "a revision's deltatext is missing"
3664 # This is a type of RCS file corruption that has been observed.
3665 conv = ensure_conversion(
3666 'missing-deltatext',
3667 error_re=(
3668 r"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3673 @Cvs2SvnTestFunction
3674 def transform_unlabeled_branch_name():
3675 "transform name of unlabeled branch"
3677 conv = ensure_conversion(
3678 'unlabeled-branch',
3679 args=[
3680 '--symbol-transform=unlabeled-1.1.4:BRANCH2',
3683 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3684 raise Failure('Branch unlabeled-1.1.4 not excluded')
3685 if not conv.path_exists('branches', 'BRANCH2'):
3686 raise Failure('Branch BRANCH2 not found')
3689 @Cvs2SvnTestFunction
3690 def ignore_unlabeled_branch():
3691 "ignoring an unlabeled branch is not allowed"
3693 conv = ensure_conversion(
3694 'unlabeled-branch',
3695 options_file='cvs2svn-ignore.options',
3696 error_re=(
3697 r"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' "
3698 r"in \'.*\' contains commits"
3703 @Cvs2SvnTestFunction
3704 def exclude_unlabeled_branch():
3705 "exclude unlabeled branch"
3707 conv = ensure_conversion(
3708 'unlabeled-branch',
3709 args=['--exclude=unlabeled-.*'],
3711 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3712 raise Failure('Branch unlabeled-1.1.4 not excluded')
3715 @Cvs2SvnTestFunction
3716 def unlabeled_branch_name_collision():
3717 "transform unlabeled branch to same name as branch"
3719 conv = ensure_conversion(
3720 'unlabeled-branch',
3721 args=[
3722 '--symbol-transform=unlabeled-1.1.4:BRANCH',
3724 error_re=(
3725 r"ERROR\: Symbol name \'BRANCH\' is already used"
3730 @Cvs2SvnTestFunction
3731 def collision_with_unlabeled_branch_name():
3732 "transform branch to same name as unlabeled branch"
3734 conv = ensure_conversion(
3735 'unlabeled-branch',
3736 args=[
3737 '--symbol-transform=BRANCH:unlabeled-1.1.4',
3739 error_re=(
3740 r"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used"
3745 @Cvs2SvnTestFunction
3746 def many_deletes():
3747 "a repo with many removable dead revisions"
3749 conv = ensure_conversion('many-deletes')
3750 conv.logs[5].check('Add files on BRANCH', (
3751 ('/%(branches)s/BRANCH/proj/b.txt', 'A'),
3753 conv.logs[6].check('Add files on BRANCH2', (
3754 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3755 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3756 ('/%(branches)s/BRANCH2/proj/d.txt', 'A'),
3760 cvs_description = Cvs2SvnPropertiesTestCase(
3761 'main',
3762 doc='test handling of CVS file descriptions',
3763 props_to_test=['cvs:description'],
3764 expected_props=[
3765 ('trunk/proj/default', ['This is an example file description.']),
3766 ('trunk/proj/sub1/default', [None]),
3770 @Cvs2SvnTestFunction
3771 def include_empty_directories():
3772 "test --include-empty-directories option"
3774 conv = ensure_conversion(
3775 'empty-directories', args=['--include-empty-directories'],
3777 conv.logs[1].check('Standard project directories', (
3778 ('/%(trunk)s', 'A'),
3779 ('/%(branches)s', 'A'),
3780 ('/%(tags)s', 'A'),
3781 ('/%(trunk)s/root-empty-directory', 'A'),
3782 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3784 conv.logs[3].check('Add b.txt.', (
3785 ('/%(trunk)s/direct', 'A'),
3786 ('/%(trunk)s/direct/b.txt', 'A'),
3787 ('/%(trunk)s/direct/empty-directory', 'A'),
3788 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3790 conv.logs[4].check('Add c.txt.', (
3791 ('/%(trunk)s/indirect', 'A'),
3792 ('/%(trunk)s/indirect/subdirectory', 'A'),
3793 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3794 ('/%(trunk)s/indirect/empty-directory', 'A'),
3795 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3797 conv.logs[5].check('Remove b.txt', (
3798 ('/%(trunk)s/direct', 'D'),
3800 conv.logs[6].check('Remove c.txt', (
3801 ('/%(trunk)s/indirect', 'D'),
3803 conv.logs[7].check('Re-add b.txt.', (
3804 ('/%(trunk)s/direct', 'A'),
3805 ('/%(trunk)s/direct/b.txt', 'A'),
3806 ('/%(trunk)s/direct/empty-directory', 'A'),
3807 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3809 conv.logs[8].check('Re-add c.txt.', (
3810 ('/%(trunk)s/indirect', 'A'),
3811 ('/%(trunk)s/indirect/subdirectory', 'A'),
3812 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3813 ('/%(trunk)s/indirect/empty-directory', 'A'),
3814 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3816 conv.logs[9].check('This commit was manufactured', (
3817 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3819 conv.logs[10].check('This commit was manufactured', (
3820 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3822 conv.logs[11].check('Import d.txt.', (
3823 ('/%(branches)s/VENDORBRANCH', 'A'),
3824 ('/%(branches)s/VENDORBRANCH/import', 'A'),
3825 ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'),
3826 ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'),
3827 ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory',
3828 'A'),
3829 ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'),
3830 ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory',
3831 'A'),
3833 conv.logs[12].check('This commit was generated', (
3834 ('/%(trunk)s/import', 'A'),
3835 ('/%(trunk)s/import/d.txt '
3836 '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'),
3837 ('/%(trunk)s/import/empty-directory', 'A'),
3838 ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'),
3842 @Cvs2SvnTestFunction
3843 def include_empty_directories_no_prune():
3844 "test --include-empty-directories with --no-prune"
3846 conv = ensure_conversion(
3847 'empty-directories', args=['--include-empty-directories', '--no-prune'],
3849 conv.logs[1].check('Standard project directories', (
3850 ('/%(trunk)s', 'A'),
3851 ('/%(branches)s', 'A'),
3852 ('/%(tags)s', 'A'),
3853 ('/%(trunk)s/root-empty-directory', 'A'),
3854 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3856 conv.logs[3].check('Add b.txt.', (
3857 ('/%(trunk)s/direct', 'A'),
3858 ('/%(trunk)s/direct/b.txt', 'A'),
3859 ('/%(trunk)s/direct/empty-directory', 'A'),
3860 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3862 conv.logs[4].check('Add c.txt.', (
3863 ('/%(trunk)s/indirect', 'A'),
3864 ('/%(trunk)s/indirect/subdirectory', 'A'),
3865 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3866 ('/%(trunk)s/indirect/empty-directory', 'A'),
3867 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3869 conv.logs[5].check('Remove b.txt', (
3870 ('/%(trunk)s/direct/b.txt', 'D'),
3872 conv.logs[6].check('Remove c.txt', (
3873 ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'),
3875 conv.logs[7].check('Re-add b.txt.', (
3876 ('/%(trunk)s/direct/b.txt', 'A'),
3878 conv.logs[8].check('Re-add c.txt.', (
3879 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3881 conv.logs[9].check('This commit was manufactured', (
3882 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3884 conv.logs[10].check('This commit was manufactured', (
3885 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3889 @Cvs2SvnTestFunction
3890 def exclude_symbol_default():
3891 "test 'exclude' symbol default"
3893 conv = ensure_conversion(
3894 'symbol-mess', args=['--symbol-default=exclude'])
3895 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
3896 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
3897 raise Failure()
3898 if conv.path_exists('tags', 'MOSTLY_TAG') \
3899 or conv.path_exists('branches', 'MOSTLY_TAG'):
3900 raise Failure()
3903 @Cvs2SvnTestFunction
3904 def add_on_branch2():
3905 "another add-on-branch test case"
3907 conv = ensure_conversion('add-on-branch2')
3908 if len(conv.logs) != 2:
3909 raise Failure()
3910 conv.logs[2].check('add file on branch', (
3911 ('/%(branches)s/BRANCH', 'A'),
3912 ('/%(branches)s/BRANCH/file1', 'A'),
3916 @Cvs2SvnTestFunction
3917 def branch_from_vendor_branch():
3918 "branch from vendor branch"
3920 ensure_conversion(
3921 'branch-from-vendor-branch',
3922 symbol_hints_file='branch-from-vendor-branch-symbol-hints.txt',
3926 @Cvs2SvnTestFunction
3927 def strange_default_branch():
3928 "default branch too deep in the hierarchy"
3930 ensure_conversion(
3931 'strange-default-branch',
3932 error_re=(
3933 r'ERROR\: The default branch 1\.2\.4\.3\.2\.1\.2 '
3934 r'in file .* is not a top-level branch'
3939 @Cvs2SvnTestFunction
3940 def move_parent():
3941 "graft onto preferred parent that was itself moved"
3943 conv = ensure_conversion(
3944 'move-parent',
3946 conv.logs[2].check('first', (
3947 ('/%(trunk)s/file1', 'A'),
3948 ('/%(trunk)s/file2', 'A'),
3950 conv.logs[3].check('This commit was manufactured', (
3951 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
3953 conv.logs[4].check('second', (
3954 ('/%(branches)s/b2/file1', 'M'),
3956 conv.logs[5].check('This commit was manufactured', (
3957 ('/%(branches)s/b1 (from /%(branches)s/b2:4)', 'A'),
3960 # b2 and b1 are equally good parents for b3, so accept either one.
3961 # (Currently, cvs2svn chooses b1 as the preferred parent because it
3962 # comes earlier than b2 in alphabetical order.)
3963 try:
3964 conv.logs[6].check('This commit was manufactured', (
3965 ('/%(branches)s/b3 (from /%(branches)s/b1:5)', 'A'),
3967 except Failure:
3968 conv.logs[6].check('This commit was manufactured', (
3969 ('/%(branches)s/b3 (from /%(branches)s/b2:4)', 'A'),
3973 @Cvs2SvnTestFunction
3974 def log_message_eols():
3975 "nonstandard EOLs in log messages"
3977 conv = ensure_conversion(
3978 'log-message-eols',
3980 conv.logs[2].check('The CRLF at the end of this line\nshould', (
3981 ('/%(trunk)s/lottalogs', 'A'),
3983 conv.logs[3].check('The CR at the end of this line\nshould', (
3984 ('/%(trunk)s/lottalogs', 'M'),
3988 @Cvs2SvnTestFunction
3989 def missing_vendor_branch():
3990 "default branch not present in RCS file"
3992 conv = ensure_conversion(
3993 'missing-vendor-branch',
3995 if not conv.output_found(
3996 r'.*vendor branch \'1\.1\.1\' is not present in file and will be ignored'
3998 raise Failure()
4001 @Cvs2SvnTestFunction
4002 def newphrases():
4003 "newphrases in RCS files"
4005 ensure_conversion(
4006 'newphrases',
4010 ########################################################################
4011 # Run the tests
4013 # list all tests here, starting with None:
4014 test_list = [
4015 None,
4016 # 1:
4017 show_usage,
4018 cvs2svn_manpage,
4019 cvs2git_manpage,
4020 cvs2hg_manpage,
4021 attr_exec,
4022 space_fname,
4023 two_quick,
4024 PruneWithCare(),
4025 PruneWithCare(variant=1, trunk='a', branches='b', tags='c'),
4026 # 10:
4027 PruneWithCare(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4028 PruneWithCare(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4029 interleaved_commits,
4030 simple_commits,
4031 SimpleTags(),
4032 SimpleTags(variant=1, trunk='a', branches='b', tags='c'),
4033 SimpleTags(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4034 SimpleTags(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4035 simple_branch_commits,
4036 mixed_time_tag,
4037 # 20:
4038 mixed_time_branch_with_added_file,
4039 mixed_commit,
4040 split_time_branch,
4041 bogus_tag,
4042 overlapping_branch,
4043 PhoenixBranch(),
4044 PhoenixBranch(variant=1, trunk='a/1', branches='b/1', tags='c/1'),
4045 ctrl_char_in_log,
4046 overdead,
4047 NoTrunkPrune(),
4048 # 30:
4049 NoTrunkPrune(variant=1, trunk='a', branches='b', tags='c'),
4050 NoTrunkPrune(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4051 NoTrunkPrune(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4052 double_delete,
4053 split_branch,
4054 resync_misgroups,
4055 TaggedBranchAndTrunk(),
4056 TaggedBranchAndTrunk(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4057 enroot_race,
4058 enroot_race_obo,
4059 # 40:
4060 BranchDeleteFirst(),
4061 BranchDeleteFirst(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4062 nonascii_cvsignore,
4063 nonascii_filenames,
4064 UnicodeAuthor(
4065 warning_expected=1),
4066 UnicodeAuthor(
4067 warning_expected=0,
4068 variant='encoding', args=['--encoding=utf_8']),
4069 UnicodeAuthor(
4070 warning_expected=0,
4071 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
4072 UnicodeLog(
4073 warning_expected=1),
4074 UnicodeLog(
4075 warning_expected=0,
4076 variant='encoding', args=['--encoding=utf_8']),
4077 UnicodeLog(
4078 warning_expected=0,
4079 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
4080 # 50:
4081 vendor_branch_sameness,
4082 vendor_branch_trunk_only,
4083 default_branches,
4084 default_branches_trunk_only,
4085 default_branch_and_1_2,
4086 compose_tag_three_sources,
4087 pass5_when_to_fill,
4088 PeerPathPruning(),
4089 PeerPathPruning(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4090 EmptyTrunk(),
4091 # 60:
4092 EmptyTrunk(variant=1, trunk='a', branches='b', tags='c'),
4093 EmptyTrunk(variant=2, trunk='a/1', branches='a/2', tags='a/3'),
4094 no_spurious_svn_commits,
4095 invalid_closings_on_trunk,
4096 individual_passes,
4097 resync_bug,
4098 branch_from_default_branch,
4099 file_in_attic_too,
4100 retain_file_in_attic_too,
4101 symbolic_name_filling_guide,
4102 # 70:
4103 eol_mime1,
4104 eol_mime2,
4105 eol_mime3,
4106 eol_mime4,
4107 cvs_revnums_off,
4108 cvs_revnums_on,
4109 keywords,
4110 ignore,
4111 requires_cvs,
4112 questionable_branch_names,
4113 # 80:
4114 questionable_tag_names,
4115 revision_reorder_bug,
4116 exclude,
4117 vendor_branch_delete_add,
4118 resync_pass2_pull_forward,
4119 native_eol,
4120 double_fill,
4121 double_fill2,
4122 resync_pass2_push_backward,
4123 double_add,
4124 # 90:
4125 bogus_branch_copy,
4126 nested_ttb_directories,
4127 auto_props_ignore_case,
4128 ctrl_char_in_filename,
4129 commit_dependencies,
4130 show_help_passes,
4131 multiple_tags,
4132 multiply_defined_symbols,
4133 multiply_defined_symbols_renamed,
4134 multiply_defined_symbols_ignored,
4135 # 100:
4136 repeatedly_defined_symbols,
4137 double_branch_delete,
4138 symbol_mismatches,
4139 overlook_symbol_mismatches,
4140 force_symbols,
4141 commit_blocks_tags,
4142 blocked_excludes,
4143 unblock_blocked_excludes,
4144 regexp_force_symbols,
4145 heuristic_symbol_default,
4146 # 110:
4147 branch_symbol_default,
4148 tag_symbol_default,
4149 symbol_transform,
4150 write_symbol_info,
4151 symbol_hints,
4152 parent_hints,
4153 parent_hints_invalid,
4154 parent_hints_wildcards,
4155 path_hints,
4156 issue_99,
4157 # 120:
4158 issue_100,
4159 issue_106,
4160 options_option,
4161 multiproject,
4162 crossproject,
4163 tag_with_no_revision,
4164 delete_cvsignore,
4165 repeated_deltatext,
4166 nasty_graphs,
4167 tagging_after_delete,
4168 # 130:
4169 crossed_branches,
4170 file_directory_conflict,
4171 attic_directory_conflict,
4172 use_rcs,
4173 internal_co_exclude,
4174 internal_co_trunk_only,
4175 internal_co_keywords,
4176 leftover_revs,
4177 requires_internal_co,
4178 timestamp_chaos,
4179 # 140:
4180 symlinks,
4181 empty_trunk_path,
4182 preferred_parent_cycle,
4183 branch_from_empty_dir,
4184 trunk_readd,
4185 branch_from_deleted_1_1,
4186 add_on_branch,
4187 main_git,
4188 main_git2,
4189 git_options,
4190 # 150:
4191 main_hg,
4192 invalid_symbol,
4193 invalid_symbol_ignore,
4194 invalid_symbol_ignore2,
4195 EOLVariants('LF'),
4196 EOLVariants('CR'),
4197 EOLVariants('CRLF'),
4198 EOLVariants('native'),
4199 no_revs_file,
4200 mirror_keyerror_test,
4201 # 160:
4202 exclude_ntdb_test,
4203 mirror_keyerror2_test,
4204 mirror_keyerror3_test,
4205 add_cvsignore_to_branch_test,
4206 missing_deltatext,
4207 transform_unlabeled_branch_name,
4208 ignore_unlabeled_branch,
4209 exclude_unlabeled_branch,
4210 unlabeled_branch_name_collision,
4211 collision_with_unlabeled_branch_name,
4212 # 170:
4213 many_deletes,
4214 cvs_description,
4215 include_empty_directories,
4216 include_empty_directories_no_prune,
4217 exclude_symbol_default,
4218 add_on_branch2,
4219 branch_from_vendor_branch,
4220 strange_default_branch,
4221 move_parent,
4222 log_message_eols,
4223 # 180:
4224 missing_vendor_branch,
4225 newphrases,
4228 if __name__ == '__main__':
4230 # Configure the environment for reproducable output from svn, etc.
4231 os.environ["LC_ALL"] = "C"
4233 # Unfortunately, there is no way under Windows to make Subversion
4234 # think that the local time zone is UTC, so we just work in the
4235 # local time zone.
4237 # The Subversion test suite code assumes it's being invoked from
4238 # within a working copy of the Subversion sources, and tries to use
4239 # the binaries in that tree. Since the cvs2svn tree never contains
4240 # a Subversion build, we just use the system's installed binaries.
4241 svntest.main.svn_binary = svn_binary
4242 svntest.main.svnlook_binary = svnlook_binary
4243 svntest.main.svnadmin_binary = svnadmin_binary
4244 svntest.main.svnversion_binary = svnversion_binary
4246 svntest.main.run_tests(test_list)
4247 # NOTREACHED
4250 ### End of file.