By default, store temporary files in the standard location.
[cvs2svn.git] / run-tests.py
blob89fbfed041179e6be246937fb79f8a9389b70de8
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 args.extend([
550 '--tmpdir=%s' % tmp_dir,
553 if symbol_hints_file:
554 self.symbol_hints_file = os.path.join(cvsrepos, symbol_hints_file)
555 args.extend([
556 '--symbol-hints=%s' % self.symbol_hints_file,
559 if self.dumpfile:
560 args.extend(['--dumpfile=%s' % (self.dumpfile,)])
561 else:
562 args.extend(['-s', self.repos])
563 args.extend([cvsrepos])
565 if passbypass:
566 self.stdout = []
567 for p in range(1, self.get_last_pass() + 1):
568 self.stdout += run_script(cvs2svn, error_re, '-p', str(p), *args)
569 else:
570 self.stdout = run_script(cvs2svn, error_re, *args)
572 if self.dumpfile:
573 if not os.path.isfile(self.dumpfile):
574 raise Failure(
575 "Dumpfile not created: '%s'"
576 % os.path.join(os.getcwd(), self.dumpfile)
578 else:
579 if os.path.isdir(self.repos):
580 self.logs = parse_log(self.repos, self.symbols)
581 elif error_re is None:
582 raise Failure(
583 "Repository not created: '%s'"
584 % os.path.join(os.getcwd(), self.repos)
587 def output_found(self, pattern):
588 """Return True if PATTERN matches any line in self.stdout.
590 PATTERN is a regular expression pattern as a string.
593 pattern_re = re.compile(pattern)
595 for line in self.stdout:
596 if pattern_re.match(line):
597 # We found the pattern that we were looking for.
598 return 1
599 else:
600 return 0
602 def find_tag_log(self, tagname):
603 """Search LOGS for a log message containing 'TAGNAME' and return the
604 log in which it was found."""
605 for i in xrange(len(self.logs), 0, -1):
606 if self.logs[i].msg.find("'"+tagname+"'") != -1:
607 return self.logs[i]
608 raise ValueError("Tag %s not found in logs" % tagname)
610 def get_wc(self, *args):
611 """Return the path to the svn working copy, or a path within the WC.
613 If a working copy has not been created yet, create it now.
615 If ARGS are specified, then they should be strings that form
616 fragments of a path within the WC. They are joined using
617 os.path.join() and appended to the WC path."""
619 if self._wc_path is None:
620 run_svn('co', repos_to_url(self.repos), self._wc)
621 self._wc_path = self._wc
622 return os.path.join(self._wc_path, *args)
624 def get_wc_tree(self):
625 if self._wc_tree is None:
626 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
627 return self._wc_tree
629 def path_exists(self, *args):
630 """Return True if the specified path exists within the repository.
632 (The strings in ARGS are first joined into a path using
633 os.path.join().)"""
635 return os.path.exists(self.get_wc(*args))
637 def check_props(self, keys, checks):
638 """Helper function for checking lots of properties. For a list of
639 files in the conversion, check that the values of the properties
640 listed in KEYS agree with those listed in CHECKS. CHECKS is a
641 list of tuples: [ (filename, [value, value, ...]), ...], where the
642 values are listed in the same order as the key names are listed in
643 KEYS."""
645 for (file, values) in checks:
646 assert len(values) == len(keys)
647 props = props_for_path(self.get_wc_tree(), file)
648 for i in range(len(keys)):
649 if props.get(keys[i]) != values[i]:
650 raise Failure(
651 "File %s has property %s set to \"%s\" "
652 "(it should have been \"%s\").\n"
653 % (file, keys[i], props.get(keys[i]), values[i],)
657 class GitConversion:
658 """A record of a cvs2svn conversion.
660 Fields:
662 name -- a one-word name indicating the CVS repository to be converted.
664 stdout -- a list of lines written by cvs2svn to stdout."""
666 def __init__(self, name, error_re, args, options_file=None):
667 self.name = name
668 if not os.path.isdir(tmp_dir):
669 os.mkdir(tmp_dir)
671 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
673 args = list(args)
674 if options_file:
675 self.options_file = os.path.join(cvsrepos, options_file)
676 args.extend([
677 '--options=%s' % self.options_file,
679 else:
680 self.options_file = None
682 self.stdout = run_script(cvs2git, error_re, *args)
685 # Cache of conversions that have already been done. Keys are conv_id;
686 # values are Conversion instances.
687 already_converted = { }
689 def ensure_conversion(
690 name, error_re=None, passbypass=None,
691 trunk=None, branches=None, tags=None,
692 args=None, options_file=None, symbol_hints_file=None, dumpfile=None,
694 """Convert CVS repository NAME to Subversion, but only if it has not
695 been converted before by this invocation of this script. If it has
696 been converted before, return the Conversion object from the
697 previous invocation.
699 If no error, return a Conversion instance.
701 If ERROR_RE is a string, it is a regular expression expected to
702 match some line of stderr printed by the conversion. If there is an
703 error and ERROR_RE is not set, then raise Failure.
705 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
706 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
708 NAME is just one word. For example, 'main' would mean to convert
709 './test-data/main-cvsrepos', and after the conversion, the resulting
710 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
711 a checked out head working copy in './cvs2svn-tmp/main-wc'.
713 Any other options to pass to cvs2svn should be in ARGS, each element
714 being one option, e.g., '--trunk-only'. If the option takes an
715 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
716 are passed to cvs2svn in the order that they appear in ARGS.
718 If OPTIONS_FILE is specified, then it should be the name of a file
719 within the main directory of the cvs repository associated with this
720 test. It is passed to cvs2svn using the --options option (which
721 suppresses some other options that are incompatible with --options).
723 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
724 file within the main directory of the cvs repository associated with
725 this test. It is passed to cvs2svn using the --symbol-hints option.
727 If DUMPFILE is specified, then it is the name of a dumpfile within
728 the temporary directory to which the conversion output should be
729 written."""
731 if args is None:
732 args = []
733 else:
734 args = list(args)
736 if trunk is None:
737 trunk = 'trunk'
738 else:
739 args.append('--trunk=%s' % (trunk,))
741 if branches is None:
742 branches = 'branches'
743 else:
744 args.append('--branches=%s' % (branches,))
746 if tags is None:
747 tags = 'tags'
748 else:
749 args.append('--tags=%s' % (tags,))
751 conv_id = make_conversion_id(
752 name, args, passbypass, options_file, symbol_hints_file
755 if conv_id not in already_converted:
756 try:
757 # Run the conversion and store the result for the rest of this
758 # session:
759 already_converted[conv_id] = Conversion(
760 conv_id, name, error_re, passbypass,
761 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
762 args, options_file, symbol_hints_file, dumpfile,
764 except Failure:
765 # Remember the failure so that a future attempt to run this conversion
766 # does not bother to retry, but fails immediately.
767 already_converted[conv_id] = None
768 raise
770 conv = already_converted[conv_id]
771 if conv is None:
772 raise Failure()
773 return conv
776 class Cvs2SvnTestFunction(TestCase):
777 """A TestCase based on a naked Python function object.
779 FUNC should be a function that returns None on success and throws an
780 svntest.Failure exception on failure. It should have a brief
781 docstring describing what it does (and fulfilling certain
782 conditions). FUNC must take no arguments.
784 This class is almost identical to svntest.testcase.FunctionTestCase,
785 except that the test function does not require a sandbox and does
786 not accept any parameter (not even sandbox=None).
788 This class can be used as an annotation on a Python function.
792 def __init__(self, func):
793 # it better be a function that accepts no parameters and has a
794 # docstring on it.
795 assert isinstance(func, types.FunctionType)
797 name = func.func_name
799 assert func.func_code.co_argcount == 0, \
800 '%s must not take any arguments' % name
802 doc = func.__doc__.strip()
803 assert doc, '%s must have a docstring' % name
805 # enforce stylistic guidelines for the function docstrings:
806 # - no longer than 50 characters
807 # - should not end in a period
808 # - should not be capitalized
809 assert len(doc) <= 50, \
810 "%s's docstring must be 50 characters or less" % name
811 assert doc[-1] != '.', \
812 "%s's docstring should not end in a period" % name
813 assert doc[0].lower() == doc[0], \
814 "%s's docstring should not be capitalized" % name
816 TestCase.__init__(self, doc=doc)
817 self.func = func
819 def get_function_name(self):
820 return self.func.func_name
822 def get_sandbox_name(self):
823 return None
825 def run(self, sandbox):
826 return self.func()
829 class Cvs2HgTestFunction(Cvs2SvnTestFunction):
830 """Same as Cvs2SvnTestFunction, but for test cases that should be
831 skipped if Mercurial is not available.
833 def run(self, sandbox):
834 if not have_hg:
835 raise svntest.Skip()
836 else:
837 return self.func()
840 class Cvs2SvnTestCase(TestCase):
841 def __init__(
842 self, name, doc=None, variant=None,
843 error_re=None, passbypass=None,
844 trunk=None, branches=None, tags=None,
845 args=None,
846 options_file=None, symbol_hints_file=None, dumpfile=None,
848 self.name = name
850 if doc is None:
851 # By default, use the first line of the class docstring as the
852 # doc:
853 doc = self.__doc__.splitlines()[0]
855 if variant is not None:
856 # Modify doc to show the variant. Trim doc first if necessary
857 # to stay within the 50-character limit.
858 suffix = '...variant %s' % (variant,)
859 doc = doc[:50 - len(suffix)] + suffix
861 TestCase.__init__(self, doc=doc)
863 self.error_re = error_re
864 self.passbypass = passbypass
865 self.trunk = trunk
866 self.branches = branches
867 self.tags = tags
868 self.args = args
869 self.options_file = options_file
870 self.symbol_hints_file = symbol_hints_file
871 self.dumpfile = dumpfile
873 def ensure_conversion(self):
874 return ensure_conversion(
875 self.name,
876 error_re=self.error_re, passbypass=self.passbypass,
877 trunk=self.trunk, branches=self.branches, tags=self.tags,
878 args=self.args,
879 options_file=self.options_file,
880 symbol_hints_file=self.symbol_hints_file,
881 dumpfile=self.dumpfile,
884 def get_sandbox_name(self):
885 return None
888 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase):
889 """Test properties resulting from a conversion."""
891 def __init__(self, name, props_to_test, expected_props, **kw):
892 """Initialize an instance of Cvs2SvnPropertiesTestCase.
894 NAME is the name of the test, passed to Cvs2SvnTestCase.
895 PROPS_TO_TEST is a list of the names of svn properties that should
896 be tested. EXPECTED_PROPS is a list of tuples [(filename,
897 [value,...])], where the second item in each tuple is a list of
898 values expected for the properties listed in PROPS_TO_TEST for the
899 specified filename. If a property must *not* be set, then its
900 value should be listed as None."""
902 Cvs2SvnTestCase.__init__(self, name, **kw)
903 self.props_to_test = props_to_test
904 self.expected_props = expected_props
906 def run(self, sbox):
907 conv = self.ensure_conversion()
908 conv.check_props(self.props_to_test, self.expected_props)
911 #----------------------------------------------------------------------
912 # Tests.
913 #----------------------------------------------------------------------
916 @Cvs2SvnTestFunction
917 def show_usage():
918 "cvs2svn with no arguments shows usage"
919 out = run_script(cvs2svn, None)
920 if (len(out) > 2 and out[0].find('ERROR:') == 0
921 and out[1].find('DBM module')):
922 print 'cvs2svn cannot execute due to lack of proper DBM module.'
923 print 'Exiting without running any further tests.'
924 sys.exit(1)
925 if out[0].find('Usage:') < 0:
926 raise Failure('Basic cvs2svn invocation failed.')
929 @Cvs2SvnTestFunction
930 def cvs2svn_manpage():
931 "generate a manpage for cvs2svn"
932 out = run_script(cvs2svn, None, '--man')
935 @Cvs2SvnTestFunction
936 def cvs2git_manpage():
937 "generate a manpage for cvs2git"
938 out = run_script(cvs2git, None, '--man')
941 @XFail_deco()
942 @Cvs2HgTestFunction
943 def cvs2hg_manpage():
944 "generate a manpage for cvs2hg"
945 out = run_script(cvs2hg, None, '--man')
948 @Cvs2SvnTestFunction
949 def show_help_passes():
950 "cvs2svn --help-passes shows pass information"
951 out = run_script(cvs2svn, None, '--help-passes')
952 if out[0].find('PASSES') < 0:
953 raise Failure('cvs2svn --help-passes failed.')
956 @Cvs2SvnTestFunction
957 def attr_exec():
958 "detection of the executable flag"
959 if sys.platform == 'win32':
960 raise svntest.Skip()
961 conv = ensure_conversion('main')
962 st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec'))
963 if not st.st_mode & stat.S_IXUSR:
964 raise Failure()
967 @Cvs2SvnTestFunction
968 def space_fname():
969 "conversion of filename with a space"
970 conv = ensure_conversion('main')
971 if not conv.path_exists('trunk', 'single-files', 'space fname'):
972 raise Failure()
975 @Cvs2SvnTestFunction
976 def two_quick():
977 "two commits in quick succession"
978 conv = ensure_conversion('main')
979 logs = parse_log(
980 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
981 if len(logs) != 2:
982 raise Failure()
985 class PruneWithCare(Cvs2SvnTestCase):
986 "prune, but never too much"
988 def __init__(self, **kw):
989 Cvs2SvnTestCase.__init__(self, 'main', **kw)
991 def run(self, sbox):
992 # Robert Pluim encountered this lovely one while converting the
993 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
994 # repository (see issue #1302). Step 4 is the doozy:
996 # revision 1: adds trunk/blah/, adds trunk/blah/first
997 # revision 2: adds trunk/blah/second
998 # revision 3: deletes trunk/blah/first
999 # revision 4: deletes blah [re-deleting trunk/blah/first pruned blah!]
1000 # revision 5: does nothing
1002 # After fixing cvs2svn, the sequence (correctly) looks like this:
1004 # revision 1: adds trunk/blah/, adds trunk/blah/first
1005 # revision 2: adds trunk/blah/second
1006 # revision 3: deletes trunk/blah/first
1007 # revision 4: does nothing [because trunk/blah/first already deleted]
1008 # revision 5: deletes blah
1010 # The difference is in 4 and 5. In revision 4, it's not correct
1011 # to prune blah/, because second is still in there, so revision 4
1012 # does nothing now. But when we delete second in 5, that should
1013 # bubble up and prune blah/ instead.
1015 # ### Note that empty revisions like 4 are probably going to become
1016 # ### at least optional, if not banished entirely from cvs2svn's
1017 # ### output. Hmmm, or they may stick around, with an extra
1018 # ### revision property explaining what happened. Need to think
1019 # ### about that. In some sense, it's a bug in Subversion itself,
1020 # ### that such revisions don't show up in 'svn log' output.
1022 conv = self.ensure_conversion()
1024 # Confirm that revision 4 removes '/trunk/full-prune/first',
1025 # and that revision 6 removes '/trunk/full-prune'.
1027 # Also confirm similar things about '/full-prune-reappear/...',
1028 # which is similar, except that later on it reappears, restored
1029 # from pruneland, because a file gets added to it.
1031 # And finally, a similar thing for '/partial-prune/...', except that
1032 # in its case, a permanent file on the top level prevents the
1033 # pruning from going farther than the subdirectory containing first
1034 # and second.
1036 for path in ('full-prune/first',
1037 'full-prune-reappear/sub/first',
1038 'partial-prune/sub/first'):
1039 conv.logs[5].check_change('/%(trunk)s/' + path, 'D')
1041 for path in ('full-prune',
1042 'full-prune-reappear',
1043 'partial-prune/sub'):
1044 conv.logs[7].check_change('/%(trunk)s/' + path, 'D')
1046 for path in ('full-prune-reappear',
1047 'full-prune-reappear/appears-later'):
1048 conv.logs[33].check_change('/%(trunk)s/' + path, 'A')
1051 @Cvs2SvnTestFunction
1052 def interleaved_commits():
1053 "two interleaved trunk commits, different log msgs"
1054 # See test-data/main-cvsrepos/proj/README.
1055 conv = ensure_conversion('main')
1057 # The initial import.
1058 rev = 26
1059 conv.logs[rev].check('Initial import.', (
1060 ('/%(trunk)s/interleaved', 'A'),
1061 ('/%(trunk)s/interleaved/1', 'A'),
1062 ('/%(trunk)s/interleaved/2', 'A'),
1063 ('/%(trunk)s/interleaved/3', 'A'),
1064 ('/%(trunk)s/interleaved/4', 'A'),
1065 ('/%(trunk)s/interleaved/5', 'A'),
1066 ('/%(trunk)s/interleaved/a', 'A'),
1067 ('/%(trunk)s/interleaved/b', 'A'),
1068 ('/%(trunk)s/interleaved/c', 'A'),
1069 ('/%(trunk)s/interleaved/d', 'A'),
1070 ('/%(trunk)s/interleaved/e', 'A'),
1073 def check_letters(rev):
1074 """Check if REV is the rev where only letters were committed."""
1076 conv.logs[rev].check('Committing letters only.', (
1077 ('/%(trunk)s/interleaved/a', 'M'),
1078 ('/%(trunk)s/interleaved/b', 'M'),
1079 ('/%(trunk)s/interleaved/c', 'M'),
1080 ('/%(trunk)s/interleaved/d', 'M'),
1081 ('/%(trunk)s/interleaved/e', 'M'),
1084 def check_numbers(rev):
1085 """Check if REV is the rev where only numbers were committed."""
1087 conv.logs[rev].check('Committing numbers only.', (
1088 ('/%(trunk)s/interleaved/1', 'M'),
1089 ('/%(trunk)s/interleaved/2', 'M'),
1090 ('/%(trunk)s/interleaved/3', 'M'),
1091 ('/%(trunk)s/interleaved/4', 'M'),
1092 ('/%(trunk)s/interleaved/5', 'M'),
1095 # One of the commits was letters only, the other was numbers only.
1096 # But they happened "simultaneously", so we don't assume anything
1097 # about which commit appeared first, so we just try both ways.
1098 rev += 1
1099 try:
1100 check_letters(rev)
1101 check_numbers(rev + 1)
1102 except Failure:
1103 check_numbers(rev)
1104 check_letters(rev + 1)
1107 @Cvs2SvnTestFunction
1108 def simple_commits():
1109 "simple trunk commits"
1110 # See test-data/main-cvsrepos/proj/README.
1111 conv = ensure_conversion('main')
1113 # The initial import.
1114 conv.logs[13].check('Initial import.', (
1115 ('/%(trunk)s/proj', 'A'),
1116 ('/%(trunk)s/proj/default', 'A'),
1117 ('/%(trunk)s/proj/sub1', 'A'),
1118 ('/%(trunk)s/proj/sub1/default', 'A'),
1119 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1120 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1121 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1122 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1123 ('/%(trunk)s/proj/sub2', 'A'),
1124 ('/%(trunk)s/proj/sub2/default', 'A'),
1125 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1126 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1127 ('/%(trunk)s/proj/sub3', 'A'),
1128 ('/%(trunk)s/proj/sub3/default', 'A'),
1131 # The first commit.
1132 conv.logs[18].check('First commit to proj, affecting two files.', (
1133 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1134 ('/%(trunk)s/proj/sub3/default', 'M'),
1137 # The second commit.
1138 conv.logs[19].check('Second commit to proj, affecting all 7 files.', (
1139 ('/%(trunk)s/proj/default', 'M'),
1140 ('/%(trunk)s/proj/sub1/default', 'M'),
1141 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1142 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1143 ('/%(trunk)s/proj/sub2/default', 'M'),
1144 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1145 ('/%(trunk)s/proj/sub3/default', 'M')
1149 class SimpleTags(Cvs2SvnTestCase):
1150 "simple tags and branches, no commits"
1152 def __init__(self, **kw):
1153 # See test-data/main-cvsrepos/proj/README.
1154 Cvs2SvnTestCase.__init__(self, 'main', **kw)
1156 def run(self, sbox):
1157 conv = self.ensure_conversion()
1159 # Verify the copy source for the tags we are about to check
1160 # No need to verify the copyfrom revision, as simple_commits did that
1161 conv.logs[13].check('Initial import.', (
1162 ('/%(trunk)s/proj', 'A'),
1163 ('/%(trunk)s/proj/default', 'A'),
1164 ('/%(trunk)s/proj/sub1', 'A'),
1165 ('/%(trunk)s/proj/sub1/default', 'A'),
1166 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1167 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1168 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1169 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1170 ('/%(trunk)s/proj/sub2', 'A'),
1171 ('/%(trunk)s/proj/sub2/default', 'A'),
1172 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1173 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1174 ('/%(trunk)s/proj/sub3', 'A'),
1175 ('/%(trunk)s/proj/sub3/default', 'A'),
1178 # Tag on rev 1.1.1.1 of all files in proj
1179 conv.logs[16].check(sym_log_msg('B_FROM_INITIALS'), (
1180 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1181 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1182 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1185 # The same, as a tag
1186 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
1187 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1188 ('/%(tags)s/T_ALL_INITIAL_FILES (from /%(trunk)s:13)', 'A'),
1189 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
1190 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
1193 # Tag on rev 1.1.1.1 of all files in proj, except one
1194 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1195 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1196 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE (from /%(trunk)s:13)', 'A'),
1197 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
1198 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
1199 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1202 # The same, as a branch
1203 conv.logs[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1204 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE (from /%(trunk)s:13)', 'A'),
1205 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1206 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
1207 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
1211 @Cvs2SvnTestFunction
1212 def simple_branch_commits():
1213 "simple branch commits"
1214 # See test-data/main-cvsrepos/proj/README.
1215 conv = ensure_conversion('main')
1217 conv.logs[23].check('Modify three files, on branch B_MIXED.', (
1218 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1219 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1220 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1224 @Cvs2SvnTestFunction
1225 def mixed_time_tag():
1226 "mixed-time tag"
1227 # See test-data/main-cvsrepos/proj/README.
1228 conv = ensure_conversion('main')
1230 log = conv.find_tag_log('T_MIXED')
1231 log.check_changes((
1232 ('/%(tags)s/T_MIXED (from /%(trunk)s:19)', 'A'),
1233 ('/%(tags)s/T_MIXED/single-files', 'D'),
1234 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
1235 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
1236 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1237 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1241 @Cvs2SvnTestFunction
1242 def mixed_time_branch_with_added_file():
1243 "mixed-time branch, and a file added to the branch"
1244 # See test-data/main-cvsrepos/proj/README.
1245 conv = ensure_conversion('main')
1247 # A branch from the same place as T_MIXED in the previous test,
1248 # plus a file added directly to the branch
1249 conv.logs[21].check(sym_log_msg('B_MIXED'), (
1250 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1251 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1252 ('/%(branches)s/B_MIXED/single-files', 'D'),
1253 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1254 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1255 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1258 conv.logs[22].check('Add a file on branch B_MIXED.', (
1259 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1263 @Cvs2SvnTestFunction
1264 def mixed_commit():
1265 "a commit affecting both trunk and a branch"
1266 # See test-data/main-cvsrepos/proj/README.
1267 conv = ensure_conversion('main')
1269 conv.logs[24].check(
1270 'A single commit affecting one file on branch B_MIXED '
1271 'and one on trunk.', (
1272 ('/%(trunk)s/proj/sub2/default', 'M'),
1273 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1277 @Cvs2SvnTestFunction
1278 def split_time_branch():
1279 "branch some trunk files, and later branch the rest"
1280 # See test-data/main-cvsrepos/proj/README.
1281 conv = ensure_conversion('main')
1283 # First change on the branch, creating it
1284 conv.logs[25].check(sym_log_msg('B_SPLIT'), (
1285 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1286 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1287 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1288 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1291 conv.logs[29].check('First change on branch B_SPLIT.', (
1292 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1293 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1294 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1295 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1296 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1299 # A trunk commit for the file which was not branched
1300 conv.logs[30].check('A trunk change to sub1/subsubB/default. '
1301 'This was committed about an', (
1302 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1305 # Add the file not already branched to the branch, with modification:w
1306 conv.logs[31].check(sym_log_msg('B_SPLIT'), (
1307 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1308 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1311 conv.logs[32].check('This change affects sub3/default and '
1312 'sub1/subsubB/default, on branch', (
1313 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1314 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1318 @Cvs2SvnTestFunction
1319 def multiple_tags():
1320 "multiple tags referring to same revision"
1321 conv = ensure_conversion('main')
1322 if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1323 raise Failure()
1324 if not conv.path_exists(
1325 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1326 raise Failure()
1329 @Cvs2SvnTestFunction
1330 def multiply_defined_symbols():
1331 "multiple definitions of symbol names"
1333 # We can only check one line of the error output at a time, so test
1334 # twice. (The conversion only have to be done once because the
1335 # results are cached.)
1336 conv = ensure_conversion(
1337 'multiply-defined-symbols',
1338 error_re=(
1339 r"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1340 r"1\.2\.4 1\.2\.2"
1343 conv = ensure_conversion(
1344 'multiply-defined-symbols',
1345 error_re=(
1346 r"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1347 r"1\.2 1\.1"
1352 @Cvs2SvnTestFunction
1353 def multiply_defined_symbols_renamed():
1354 "rename multiply defined symbols"
1356 conv = ensure_conversion(
1357 'multiply-defined-symbols',
1358 options_file='cvs2svn-rename.options',
1362 @Cvs2SvnTestFunction
1363 def multiply_defined_symbols_ignored():
1364 "ignore multiply defined symbols"
1366 conv = ensure_conversion(
1367 'multiply-defined-symbols',
1368 options_file='cvs2svn-ignore.options',
1372 @Cvs2SvnTestFunction
1373 def repeatedly_defined_symbols():
1374 "multiple identical definitions of symbol names"
1376 # If a symbol is defined multiple times but has the same value each
1377 # time, that should not be an error.
1379 conv = ensure_conversion('repeatedly-defined-symbols')
1382 @Cvs2SvnTestFunction
1383 def bogus_tag():
1384 "conversion of invalid symbolic names"
1385 conv = ensure_conversion('bogus-tag')
1388 @Cvs2SvnTestFunction
1389 def overlapping_branch():
1390 "ignore a file with a branch with two names"
1391 conv = ensure_conversion('overlapping-branch')
1393 if not conv.output_found('.*cannot also have name \'vendorB\''):
1394 raise Failure()
1396 conv.logs[2].check('imported', (
1397 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1398 ('/%(trunk)s/overlapping-branch', 'A'),
1401 if len(conv.logs) != 2:
1402 raise Failure()
1405 class PhoenixBranch(Cvs2SvnTestCase):
1406 "convert a branch file rooted in a 'dead' revision"
1408 def __init__(self, **kw):
1409 Cvs2SvnTestCase.__init__(self, 'phoenix', **kw)
1411 def run(self, sbox):
1412 conv = self.ensure_conversion()
1413 conv.logs[8].check('This file was supplied by Jack Moffitt', (
1414 ('/%(branches)s/volsung_20010721', 'A'),
1415 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1417 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1418 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1422 ###TODO: We check for 4 changed paths here to accomodate creating tags
1423 ###and branches in rev 1, but that will change, so this will
1424 ###eventually change back.
1425 @Cvs2SvnTestFunction
1426 def ctrl_char_in_log():
1427 "handle a control char in a log message"
1428 # This was issue #1106.
1429 rev = 2
1430 conv = ensure_conversion('ctrl-char-in-log')
1431 conv.logs[rev].check_changes((
1432 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1434 if conv.logs[rev].msg.find('\x04') < 0:
1435 raise Failure(
1436 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1439 @Cvs2SvnTestFunction
1440 def overdead():
1441 "handle tags rooted in a redeleted revision"
1442 conv = ensure_conversion('overdead')
1445 class NoTrunkPrune(Cvs2SvnTestCase):
1446 "ensure that trunk doesn't get pruned"
1448 def __init__(self, **kw):
1449 Cvs2SvnTestCase.__init__(self, 'overdead', **kw)
1451 def run(self, sbox):
1452 conv = self.ensure_conversion()
1453 for rev in conv.logs.keys():
1454 rev_logs = conv.logs[rev]
1455 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1456 raise Failure()
1459 @Cvs2SvnTestFunction
1460 def double_delete():
1461 "file deleted twice, in the root of the repository"
1462 # This really tests several things: how we handle a file that's
1463 # removed (state 'dead') in two successive revisions; how we
1464 # handle a file in the root of the repository (there were some
1465 # bugs in cvs2svn's svn path construction for top-level files); and
1466 # the --no-prune option.
1467 conv = ensure_conversion(
1468 'double-delete', args=['--trunk-only', '--no-prune'])
1470 path = '/%(trunk)s/twice-removed'
1471 rev = 2
1472 conv.logs[rev].check('Updated CVS', (
1473 (path, 'A'),
1475 conv.logs[rev + 1].check('Remove this file for the first time.', (
1476 (path, 'D'),
1478 conv.logs[rev + 2].check('Remove this file for the second time,', (
1482 @Cvs2SvnTestFunction
1483 def split_branch():
1484 "branch created from both trunk and another branch"
1485 # See test-data/split-branch-cvsrepos/README.
1487 # The conversion will fail if the bug is present, and
1488 # ensure_conversion will raise Failure.
1489 conv = ensure_conversion('split-branch')
1492 @Cvs2SvnTestFunction
1493 def resync_misgroups():
1494 "resyncing should not misorder commit groups"
1495 # See test-data/resync-misgroups-cvsrepos/README.
1497 # The conversion will fail if the bug is present, and
1498 # ensure_conversion will raise Failure.
1499 conv = ensure_conversion('resync-misgroups')
1502 class TaggedBranchAndTrunk(Cvs2SvnTestCase):
1503 "allow tags with mixed trunk and branch sources"
1505 def __init__(self, **kw):
1506 Cvs2SvnTestCase.__init__(self, 'tagged-branch-n-trunk', **kw)
1508 def run(self, sbox):
1509 conv = self.ensure_conversion()
1511 tags = conv.symbols.get('tags', 'tags')
1513 a_path = conv.get_wc(tags, 'some-tag', 'a.txt')
1514 b_path = conv.get_wc(tags, 'some-tag', 'b.txt')
1515 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1516 raise Failure()
1517 if (open(a_path, 'r').read().find('1.24') == -1) \
1518 or (open(b_path, 'r').read().find('1.5') == -1):
1519 raise Failure()
1522 @Cvs2SvnTestFunction
1523 def enroot_race():
1524 "never use the rev-in-progress as a copy source"
1526 # See issue #1427 and r8544.
1527 conv = ensure_conversion('enroot-race')
1528 rev = 6
1529 conv.logs[rev].check_changes((
1530 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1531 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1532 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1534 conv.logs[rev + 1].check_changes((
1535 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1536 ('/%(trunk)s/proj/a.txt', 'M'),
1537 ('/%(trunk)s/proj/b.txt', 'M'),
1541 @Cvs2SvnTestFunction
1542 def enroot_race_obo():
1543 "do use the last completed rev as a copy source"
1544 conv = ensure_conversion('enroot-race-obo')
1545 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1546 if not len(conv.logs) == 3:
1547 raise Failure()
1550 class BranchDeleteFirst(Cvs2SvnTestCase):
1551 "correctly handle deletion as initial branch action"
1553 def __init__(self, **kw):
1554 Cvs2SvnTestCase.__init__(self, 'branch-delete-first', **kw)
1556 def run(self, sbox):
1557 # See test-data/branch-delete-first-cvsrepos/README.
1559 # The conversion will fail if the bug is present, and
1560 # ensure_conversion would raise Failure.
1561 conv = self.ensure_conversion()
1563 branches = conv.symbols.get('branches', 'branches')
1565 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1566 if conv.path_exists(branches, 'branch-1', 'file'):
1567 raise Failure()
1568 if conv.path_exists(branches, 'branch-2', 'file'):
1569 raise Failure()
1570 if not conv.path_exists(branches, 'branch-3', 'file'):
1571 raise Failure()
1574 @Cvs2SvnTestFunction
1575 def nonascii_cvsignore():
1576 "non ascii files in .cvsignore"
1578 # The output seems to be in the C locale, where it looks like this
1579 # (at least on one test system):
1580 expected = (
1581 'Sp?\\195?\\164tzle\n'
1582 'Cr?\\195?\\168meBr?\\195?\\187l?\\195?\\169e\n'
1583 'Jam?\\195?\\179nIb?\\195?\\169rico\n'
1584 'Am?\\195?\\170ijoas?\\195?\\128Bulh?\\195?\\163oPato\n'
1587 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1588 props = props_for_path(conv.get_wc_tree(), 'trunk/single-files')
1590 if props['svn:ignore'] != expected:
1591 raise Failure()
1594 @Cvs2SvnTestFunction
1595 def nonascii_filenames():
1596 "non ascii files converted incorrectly"
1597 # see issue #1255
1599 # on a en_US.iso-8859-1 machine this test fails with
1600 # svn: Can't recode ...
1602 # as described in the issue
1604 # on a en_US.UTF-8 machine this test fails with
1605 # svn: Malformed XML ...
1607 # which means at least it fails. Unfortunately it won't fail
1608 # with the same error...
1610 # mangle current locale settings so we know we're not running
1611 # a UTF-8 locale (which does not exhibit this problem)
1612 current_locale = locale.getlocale()
1613 new_locale = 'en_US.ISO8859-1'
1614 locale_changed = None
1616 # From http://docs.python.org/lib/module-sys.html
1618 # getfilesystemencoding():
1620 # Return the name of the encoding used to convert Unicode filenames
1621 # into system file names, or None if the system default encoding is
1622 # used. The result value depends on the operating system:
1624 # - On Windows 9x, the encoding is ``mbcs''.
1625 # - On Mac OS X, the encoding is ``utf-8''.
1626 # - On Unix, the encoding is the user's preference according to the
1627 # result of nl_langinfo(CODESET), or None if the
1628 # nl_langinfo(CODESET) failed.
1629 # - On Windows NT+, file names are Unicode natively, so no conversion is
1630 # performed.
1632 # So we're going to skip this test on Mac OS X for now.
1633 if sys.platform == "darwin":
1634 raise svntest.Skip()
1636 try:
1637 # change locale to non-UTF-8 locale to generate latin1 names
1638 locale.setlocale(locale.LC_ALL, # this might be too broad?
1639 new_locale)
1640 locale_changed = 1
1641 except locale.Error:
1642 raise svntest.Skip()
1644 try:
1645 srcrepos_path = os.path.join(test_data_dir, 'non-ascii-cvsrepos')
1646 dstrepos_path = os.path.join(test_data_dir, 'non-ascii-copy-cvsrepos')
1647 if not os.path.exists(dstrepos_path):
1648 # create repos from existing main repos
1649 shutil.copytree(srcrepos_path, dstrepos_path)
1650 base_path = os.path.join(dstrepos_path, 'single-files')
1651 os.remove(os.path.join(base_path, '.cvsignore,v'))
1652 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1653 os.path.join(base_path, 'two\366uick,v'))
1654 new_path = os.path.join(dstrepos_path, 'single\366files')
1655 os.rename(base_path, new_path)
1657 conv = ensure_conversion('non-ascii-copy', args=['--encoding=latin1'])
1658 finally:
1659 if locale_changed:
1660 locale.setlocale(locale.LC_ALL, current_locale)
1661 safe_rmtree(dstrepos_path)
1664 class UnicodeTest(Cvs2SvnTestCase):
1665 "metadata contains Unicode"
1667 warning_pattern = r'ERROR\: There were warnings converting .* messages'
1669 def __init__(self, name, warning_expected, **kw):
1670 if warning_expected:
1671 error_re = self.warning_pattern
1672 else:
1673 error_re = None
1675 Cvs2SvnTestCase.__init__(self, name, error_re=error_re, **kw)
1676 self.warning_expected = warning_expected
1678 def run(self, sbox):
1679 try:
1680 # ensure the availability of the "utf_8" encoding:
1681 u'a'.encode('utf_8').decode('utf_8')
1682 except LookupError:
1683 raise svntest.Skip()
1685 self.ensure_conversion()
1688 class UnicodeAuthor(UnicodeTest):
1689 "author name contains Unicode"
1691 def __init__(self, warning_expected, **kw):
1692 UnicodeTest.__init__(self, 'unicode-author', warning_expected, **kw)
1695 class UnicodeLog(UnicodeTest):
1696 "log message contains Unicode"
1698 def __init__(self, warning_expected, **kw):
1699 UnicodeTest.__init__(self, 'unicode-log', warning_expected, **kw)
1702 @Cvs2SvnTestFunction
1703 def vendor_branch_sameness():
1704 "avoid spurious changes for initial revs"
1705 conv = ensure_conversion(
1706 'vendor-branch-sameness', args=['--keep-trivial-imports']
1709 # The following files are in this repository:
1711 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1712 # the same contents, the file's default branch is 1.1.1,
1713 # and both revisions are in state 'Exp'.
1715 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1716 # 1.1 (the addition of a line of text).
1718 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1720 # d.txt: This file was created by 'cvs add' instead of import, so
1721 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1722 # The timestamp on the add is exactly the same as for the
1723 # imports of the other files.
1725 # e.txt: Like a.txt, except that the log message for revision 1.1
1726 # is not the standard import log message.
1728 # (Aside from e.txt, the log messages for the same revisions are the
1729 # same in all files.)
1731 # We expect that only a.txt is recognized as an import whose 1.1
1732 # revision can be omitted. The other files should be added on trunk
1733 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1734 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1735 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1736 # and c.txt should be 'D'eleted.
1738 rev = 2
1739 conv.logs[rev].check('Initial revision', (
1740 ('/%(trunk)s/proj', 'A'),
1741 ('/%(trunk)s/proj/b.txt', 'A'),
1742 ('/%(trunk)s/proj/c.txt', 'A'),
1743 ('/%(trunk)s/proj/d.txt', 'A'),
1746 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1747 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1748 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1751 conv.logs[rev + 2].check('First vendor branch revision.', (
1752 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1753 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1754 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1757 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1758 'to compensate for changes in r4,', (
1759 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1760 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1761 ('/%(trunk)s/proj/c.txt', 'D'),
1764 rev = 7
1765 conv.logs[rev].check('This log message is not the standard', (
1766 ('/%(trunk)s/proj/e.txt', 'A'),
1769 conv.logs[rev + 2].check('First vendor branch revision', (
1770 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1773 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1774 'to compensate for changes in r9,', (
1775 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1779 @Cvs2SvnTestFunction
1780 def vendor_branch_trunk_only():
1781 "handle vendor branches with --trunk-only"
1782 conv = ensure_conversion('vendor-branch-sameness', args=['--trunk-only'])
1784 rev = 2
1785 conv.logs[rev].check('Initial revision', (
1786 ('/%(trunk)s/proj', 'A'),
1787 ('/%(trunk)s/proj/b.txt', 'A'),
1788 ('/%(trunk)s/proj/c.txt', 'A'),
1789 ('/%(trunk)s/proj/d.txt', 'A'),
1792 conv.logs[rev + 1].check('First vendor branch revision', (
1793 ('/%(trunk)s/proj/a.txt', 'A'),
1794 ('/%(trunk)s/proj/b.txt', 'M'),
1795 ('/%(trunk)s/proj/c.txt', 'D'),
1798 conv.logs[rev + 2].check('This log message is not the standard', (
1799 ('/%(trunk)s/proj/e.txt', 'A'),
1802 conv.logs[rev + 3].check('First vendor branch revision', (
1803 ('/%(trunk)s/proj/e.txt', 'M'),
1807 @Cvs2SvnTestFunction
1808 def default_branches():
1809 "handle default branches correctly"
1810 conv = ensure_conversion('default-branches')
1812 # There are seven files in the repository:
1814 # a.txt:
1815 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1816 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1817 # committed (thus losing the default branch "1.1.1"), then
1818 # 1.1.1.4 was imported. All vendor import release tags are
1819 # still present.
1821 # b.txt:
1822 # Like a.txt, but without rev 1.2.
1824 # c.txt:
1825 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1827 # d.txt:
1828 # Same as the previous two, but 1.1.1 branch is unlabeled.
1830 # e.txt:
1831 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1833 # deleted-on-vendor-branch.txt,v:
1834 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1836 # added-then-imported.txt,v:
1837 # Added with 'cvs add' to create 1.1, then imported with
1838 # completely different contents to create 1.1.1.1, therefore
1839 # never had a default branch.
1842 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1843 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1844 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1845 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1846 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1847 ('/%(branches)s/vbranchA', 'A'),
1848 ('/%(branches)s/vbranchA/proj', 'A'),
1849 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1850 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1851 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1852 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1855 conv.logs[3].check("This commit was generated by cvs2svn "
1856 "to compensate for changes in r2,", (
1857 ('/%(trunk)s/proj', 'A'),
1858 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1859 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1860 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1861 ('/%(trunk)s/proj/d.txt '
1862 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1863 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1864 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1865 ('/%(trunk)s/proj/e.txt '
1866 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1869 conv.logs[4].check(sym_log_msg('vtag-1',1), (
1870 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1871 ('/%(tags)s/vtag-1/proj/d.txt '
1872 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1875 conv.logs[5].check("Import (vbranchA, vtag-2).", (
1876 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1877 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1878 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1879 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1880 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1881 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1884 conv.logs[6].check("This commit was generated by cvs2svn "
1885 "to compensate for changes in r5,", (
1886 ('/%(trunk)s/proj/a.txt '
1887 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1888 ('/%(trunk)s/proj/b.txt '
1889 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1890 ('/%(trunk)s/proj/c.txt '
1891 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1892 ('/%(trunk)s/proj/d.txt '
1893 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1894 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1895 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1896 'R'),
1897 ('/%(trunk)s/proj/e.txt '
1898 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1901 conv.logs[7].check(sym_log_msg('vtag-2',1), (
1902 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1903 ('/%(tags)s/vtag-2/proj/d.txt '
1904 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1907 conv.logs[8].check("Import (vbranchA, vtag-3).", (
1908 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1909 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1910 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1911 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1912 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1913 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1916 conv.logs[9].check("This commit was generated by cvs2svn "
1917 "to compensate for changes in r8,", (
1918 ('/%(trunk)s/proj/a.txt '
1919 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1920 ('/%(trunk)s/proj/b.txt '
1921 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1922 ('/%(trunk)s/proj/c.txt '
1923 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1924 ('/%(trunk)s/proj/d.txt '
1925 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1926 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1927 ('/%(trunk)s/proj/e.txt '
1928 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1931 conv.logs[10].check(sym_log_msg('vtag-3',1), (
1932 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1933 ('/%(tags)s/vtag-3/proj/d.txt '
1934 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1935 ('/%(tags)s/vtag-3/proj/e.txt '
1936 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1939 conv.logs[11].check("First regular commit, to a.txt, on vtag-3.", (
1940 ('/%(trunk)s/proj/a.txt', 'M'),
1943 conv.logs[12].check("Add a file to the working copy.", (
1944 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1947 conv.logs[13].check(sym_log_msg('vbranchA'), (
1948 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1949 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1952 conv.logs[14].check("Import (vbranchA, vtag-4).", (
1953 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1954 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1955 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1956 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1957 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1958 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1959 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1962 conv.logs[15].check("This commit was generated by cvs2svn "
1963 "to compensate for changes in r14,", (
1964 ('/%(trunk)s/proj/b.txt '
1965 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1966 ('/%(trunk)s/proj/c.txt '
1967 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1968 ('/%(trunk)s/proj/d.txt '
1969 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1970 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1971 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1972 'A'),
1973 ('/%(trunk)s/proj/e.txt '
1974 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1977 conv.logs[16].check(sym_log_msg('vtag-4',1), (
1978 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1979 ('/%(tags)s/vtag-4/proj/d.txt '
1980 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
1984 @Cvs2SvnTestFunction
1985 def default_branches_trunk_only():
1986 "handle default branches with --trunk-only"
1988 conv = ensure_conversion('default-branches', args=['--trunk-only'])
1990 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1991 ('/%(trunk)s/proj', 'A'),
1992 ('/%(trunk)s/proj/a.txt', 'A'),
1993 ('/%(trunk)s/proj/b.txt', 'A'),
1994 ('/%(trunk)s/proj/c.txt', 'A'),
1995 ('/%(trunk)s/proj/d.txt', 'A'),
1996 ('/%(trunk)s/proj/e.txt', 'A'),
1997 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2000 conv.logs[3].check("Import (vbranchA, vtag-2).", (
2001 ('/%(trunk)s/proj/a.txt', 'M'),
2002 ('/%(trunk)s/proj/b.txt', 'M'),
2003 ('/%(trunk)s/proj/c.txt', 'M'),
2004 ('/%(trunk)s/proj/d.txt', 'M'),
2005 ('/%(trunk)s/proj/e.txt', 'M'),
2006 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
2009 conv.logs[4].check("Import (vbranchA, vtag-3).", (
2010 ('/%(trunk)s/proj/a.txt', 'M'),
2011 ('/%(trunk)s/proj/b.txt', 'M'),
2012 ('/%(trunk)s/proj/c.txt', 'M'),
2013 ('/%(trunk)s/proj/d.txt', 'M'),
2014 ('/%(trunk)s/proj/e.txt', 'M'),
2015 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
2018 conv.logs[5].check("First regular commit, to a.txt, on vtag-3.", (
2019 ('/%(trunk)s/proj/a.txt', 'M'),
2022 conv.logs[6].check("Add a file to the working copy.", (
2023 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
2026 conv.logs[7].check("Import (vbranchA, vtag-4).", (
2027 ('/%(trunk)s/proj/b.txt', 'M'),
2028 ('/%(trunk)s/proj/c.txt', 'M'),
2029 ('/%(trunk)s/proj/d.txt', 'M'),
2030 ('/%(trunk)s/proj/e.txt', 'M'),
2031 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2035 @Cvs2SvnTestFunction
2036 def default_branch_and_1_2():
2037 "do not allow 1.2 revision with default branch"
2039 conv = ensure_conversion(
2040 'default-branch-and-1-2',
2041 error_re=(
2042 r'.*File \'.*\' has default branch=1\.1\.1 but also a revision 1\.2'
2047 @Cvs2SvnTestFunction
2048 def compose_tag_three_sources():
2049 "compose a tag from three sources"
2050 conv = ensure_conversion('compose-tag-three-sources')
2052 conv.logs[2].check("Add on trunk", (
2053 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
2054 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
2055 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
2056 ('/%(trunk)s/tagged-on-b1', 'A'),
2057 ('/%(trunk)s/tagged-on-b2', 'A'),
2060 conv.logs[3].check(sym_log_msg('b1'), (
2061 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
2064 conv.logs[4].check(sym_log_msg('b2'), (
2065 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
2068 conv.logs[5].check("Commit on branch b1", (
2069 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
2070 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
2071 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
2072 ('/%(branches)s/b1/tagged-on-b1', 'M'),
2073 ('/%(branches)s/b1/tagged-on-b2', 'M'),
2076 conv.logs[6].check("Commit on branch b2", (
2077 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
2078 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
2079 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
2080 ('/%(branches)s/b2/tagged-on-b1', 'M'),
2081 ('/%(branches)s/b2/tagged-on-b2', 'M'),
2084 conv.logs[7].check("Commit again on trunk", (
2085 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
2086 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
2087 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
2088 ('/%(trunk)s/tagged-on-b1', 'M'),
2089 ('/%(trunk)s/tagged-on-b2', 'M'),
2092 conv.logs[8].check(sym_log_msg('T',1), (
2093 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
2094 ('/%(tags)s/T/tagged-on-trunk-1.1 '
2095 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
2096 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:5)', 'R'),
2097 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:6)', 'R'),
2101 @Cvs2SvnTestFunction
2102 def pass5_when_to_fill():
2103 "reserve a svn revnum for a fill only when required"
2104 # The conversion will fail if the bug is present, and
2105 # ensure_conversion would raise Failure.
2106 conv = ensure_conversion('pass5-when-to-fill')
2109 class EmptyTrunk(Cvs2SvnTestCase):
2110 "don't break when the trunk is empty"
2112 def __init__(self, **kw):
2113 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
2115 def run(self, sbox):
2116 # The conversion will fail if the bug is present, and
2117 # ensure_conversion would raise Failure.
2118 conv = self.ensure_conversion()
2121 @Cvs2SvnTestFunction
2122 def no_spurious_svn_commits():
2123 "ensure that we don't create any spurious commits"
2124 conv = ensure_conversion('phoenix')
2126 # Check spurious commit that could be created in
2127 # SVNCommitCreator._pre_commit()
2129 # (When you add a file on a branch, CVS creates a trunk revision
2130 # in state 'dead'. If the log message of that commit is equal to
2131 # the one that CVS generates, we do not ever create a 'fill'
2132 # SVNCommit for it.)
2134 # and spurious commit that could be created in
2135 # SVNCommitCreator._commit()
2137 # (When you add a file on a branch, CVS creates a trunk revision
2138 # in state 'dead'. If the log message of that commit is equal to
2139 # the one that CVS generates, we do not create a primary SVNCommit
2140 # for it.)
2141 conv.logs[17].check('File added on branch xiphophorus', (
2142 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
2145 # Check to make sure that a commit *is* generated:
2146 # (When you add a file on a branch, CVS creates a trunk revision
2147 # in state 'dead'. If the log message of that commit is NOT equal
2148 # to the one that CVS generates, we create a primary SVNCommit to
2149 # serve as a home for the log message in question.
2150 conv.logs[18].check('file added-on-branch2.txt was initially added on '
2151 + 'branch xiphophorus,\nand this log message was tweaked', ())
2153 # Check spurious commit that could be created in
2154 # SVNCommitCreator._commit_symbols().
2155 conv.logs[19].check('This file was also added on branch xiphophorus,', (
2156 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
2160 class PeerPathPruning(Cvs2SvnTestCase):
2161 "make sure that filling prunes paths correctly"
2163 def __init__(self, **kw):
2164 Cvs2SvnTestCase.__init__(self, 'peer-path-pruning', **kw)
2166 def run(self, sbox):
2167 conv = self.ensure_conversion()
2168 conv.logs[6].check(sym_log_msg('BRANCH'), (
2169 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A'),
2170 ('/%(branches)s/BRANCH/bar', 'D'),
2171 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:5)', 'R'),
2175 @Cvs2SvnTestFunction
2176 def invalid_closings_on_trunk():
2177 "verify correct revs are copied to default branches"
2178 # The conversion will fail if the bug is present, and
2179 # ensure_conversion would raise Failure.
2180 conv = ensure_conversion('invalid-closings-on-trunk')
2183 @Cvs2SvnTestFunction
2184 def individual_passes():
2185 "run each pass individually"
2186 conv = ensure_conversion('main')
2187 conv2 = ensure_conversion('main', passbypass=1)
2189 if conv.logs != conv2.logs:
2190 raise Failure()
2193 @Cvs2SvnTestFunction
2194 def resync_bug():
2195 "reveal a big bug in our resync algorithm"
2196 # This will fail if the bug is present
2197 conv = ensure_conversion('resync-bug')
2200 @Cvs2SvnTestFunction
2201 def branch_from_default_branch():
2202 "reveal a bug in our default branch detection code"
2203 conv = ensure_conversion('branch-from-default-branch')
2205 # This revision will be a default branch synchronization only
2206 # if cvs2svn is correctly determining default branch revisions.
2208 # The bug was that cvs2svn was treating revisions on branches off of
2209 # default branches as default branch revisions, resulting in
2210 # incorrectly regarding the branch off of the default branch as a
2211 # non-trunk default branch. Crystal clear? I thought so. See
2212 # issue #42 for more incoherent blathering.
2213 conv.logs[5].check("This commit was generated by cvs2svn", (
2214 ('/%(trunk)s/proj/file.txt '
2215 '(from /%(branches)s/upstream/proj/file.txt:4)', 'R'),
2219 @Cvs2SvnTestFunction
2220 def file_in_attic_too():
2221 "die if a file exists in and out of the attic"
2222 ensure_conversion(
2223 'file-in-attic-too',
2224 error_re=(
2225 r'.*A CVS repository cannot contain both '
2226 r'(.*)' + re.escape(os.sep) + r'(.*) '
2227 + r'and '
2228 r'\1' + re.escape(os.sep) + r'Attic' + re.escape(os.sep) + r'\2'
2233 @Cvs2SvnTestFunction
2234 def retain_file_in_attic_too():
2235 "test --retain-conflicting-attic-files option"
2236 conv = ensure_conversion(
2237 'file-in-attic-too', args=['--retain-conflicting-attic-files'])
2238 if not conv.path_exists('trunk', 'file.txt'):
2239 raise Failure()
2240 if not conv.path_exists('trunk', 'Attic', 'file.txt'):
2241 raise Failure()
2244 @Cvs2SvnTestFunction
2245 def symbolic_name_filling_guide():
2246 "reveal a big bug in our SymbolFillingGuide"
2247 # This will fail if the bug is present
2248 conv = ensure_conversion('symbolic-name-overfill')
2251 # Helpers for tests involving file contents and properties.
2253 class NodeTreeWalkException:
2254 "Exception class for node tree traversals."
2255 pass
2257 def node_for_path(node, path):
2258 "In the tree rooted under SVNTree NODE, return the node at PATH."
2259 if node.name != '__SVN_ROOT_NODE':
2260 raise NodeTreeWalkException()
2261 path = path.strip('/')
2262 components = path.split('/')
2263 for component in components:
2264 node = svntest.tree.get_child(node, component)
2265 return node
2267 # Helper for tests involving properties.
2268 def props_for_path(node, path):
2269 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2270 return node_for_path(node, path).props
2273 class EOLMime(Cvs2SvnPropertiesTestCase):
2274 """eol settings and mime types together
2276 The files are as follows:
2278 trunk/foo.txt: no -kb, mime file says nothing.
2279 trunk/foo.xml: no -kb, mime file says text.
2280 trunk/foo.zip: no -kb, mime file says non-text.
2281 trunk/foo.bin: has -kb, mime file says nothing.
2282 trunk/foo.csv: has -kb, mime file says text.
2283 trunk/foo.dbf: has -kb, mime file says non-text.
2286 def __init__(self, args, **kw):
2287 # TODO: It's a bit klugey to construct this path here. But so far
2288 # there's only one test with a mime.types file. If we have more,
2289 # we should abstract this into some helper, which would be located
2290 # near ensure_conversion(). Note that it is a convention of this
2291 # test suite for a mime.types file to be located in the top level
2292 # of the CVS repository to which it applies.
2293 self.mime_path = os.path.join(
2294 test_data_dir, 'eol-mime-cvsrepos', 'mime.types')
2296 Cvs2SvnPropertiesTestCase.__init__(
2297 self, 'eol-mime',
2298 props_to_test=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2299 args=['--mime-types=%s' % self.mime_path] + args,
2300 **kw)
2303 # We do four conversions. Each time, we pass --mime-types=FILE with
2304 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2305 # Thus there's one conversion with neither flag, one with just the
2306 # former, one with just the latter, and one with both.
2309 # Neither --no-default-eol nor --eol-from-mime-type:
2310 eol_mime1 = EOLMime(
2311 variant=1,
2312 args=[],
2313 expected_props=[
2314 ('trunk/foo.txt', [None, None, None]),
2315 ('trunk/foo.xml', [None, 'text/xml', None]),
2316 ('trunk/foo.zip', [None, 'application/zip', None]),
2317 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2318 ('trunk/foo.csv', [None, 'text/csv', None]),
2319 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2323 # Just --no-default-eol, not --eol-from-mime-type:
2324 eol_mime2 = EOLMime(
2325 variant=2,
2326 args=['--default-eol=native'],
2327 expected_props=[
2328 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2329 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2330 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS]),
2331 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2332 ('trunk/foo.csv', [None, 'text/csv', None]),
2333 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2337 # Just --eol-from-mime-type, not --no-default-eol:
2338 eol_mime3 = EOLMime(
2339 variant=3,
2340 args=['--eol-from-mime-type'],
2341 expected_props=[
2342 ('trunk/foo.txt', [None, None, None]),
2343 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2344 ('trunk/foo.zip', [None, 'application/zip', None]),
2345 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2346 ('trunk/foo.csv', [None, 'text/csv', None]),
2347 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2351 # Both --no-default-eol and --eol-from-mime-type:
2352 eol_mime4 = EOLMime(
2353 variant=4,
2354 args=['--eol-from-mime-type', '--default-eol=native'],
2355 expected_props=[
2356 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2357 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2358 ('trunk/foo.zip', [None, 'application/zip', None]),
2359 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2360 ('trunk/foo.csv', [None, 'text/csv', None]),
2361 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2365 cvs_revnums_off = Cvs2SvnPropertiesTestCase(
2366 'eol-mime',
2367 doc='test non-setting of cvs2svn:cvs-rev property',
2368 args=[],
2369 props_to_test=['cvs2svn:cvs-rev'],
2370 expected_props=[
2371 ('trunk/foo.txt', [None]),
2372 ('trunk/foo.xml', [None]),
2373 ('trunk/foo.zip', [None]),
2374 ('trunk/foo.bin', [None]),
2375 ('trunk/foo.csv', [None]),
2376 ('trunk/foo.dbf', [None]),
2380 cvs_revnums_on = Cvs2SvnPropertiesTestCase(
2381 'eol-mime',
2382 doc='test setting of cvs2svn:cvs-rev property',
2383 args=['--cvs-revnums'],
2384 props_to_test=['cvs2svn:cvs-rev'],
2385 expected_props=[
2386 ('trunk/foo.txt', ['1.2']),
2387 ('trunk/foo.xml', ['1.2']),
2388 ('trunk/foo.zip', ['1.2']),
2389 ('trunk/foo.bin', ['1.2']),
2390 ('trunk/foo.csv', ['1.2']),
2391 ('trunk/foo.dbf', ['1.2']),
2395 keywords = Cvs2SvnPropertiesTestCase(
2396 'keywords',
2397 doc='test setting of svn:keywords property among others',
2398 args=['--default-eol=native'],
2399 props_to_test=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2400 expected_props=[
2401 ('trunk/foo.default', [KEYWORDS, 'native', None]),
2402 ('trunk/foo.kkvl', [KEYWORDS, 'native', None]),
2403 ('trunk/foo.kkv', [KEYWORDS, 'native', None]),
2404 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2405 ('trunk/foo.kk', [None, 'native', None]),
2406 ('trunk/foo.ko', [None, 'native', None]),
2407 ('trunk/foo.kv', [None, 'native', None]),
2411 @Cvs2SvnTestFunction
2412 def ignore():
2413 "test setting of svn:ignore property"
2414 conv = ensure_conversion('cvsignore')
2415 wc_tree = conv.get_wc_tree()
2416 topdir_props = props_for_path(wc_tree, 'trunk/proj')
2417 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
2419 if topdir_props['svn:ignore'] != \
2420 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2421 raise Failure()
2423 if subdir_props['svn:ignore'] != \
2424 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2425 raise Failure()
2428 @Cvs2SvnTestFunction
2429 def requires_cvs():
2430 "test that CVS can still do what RCS can't"
2431 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2432 conv = ensure_conversion(
2433 'requires-cvs', args=['--use-cvs', '--default-eol=native'],
2436 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
2437 cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read()
2439 if atsign_contents[-1:] == "@":
2440 raise Failure()
2441 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
2442 raise Failure()
2444 if not (conv.logs[6].author == "William Lyon Phelps III" and
2445 conv.logs[5].author == "j random"):
2446 raise Failure()
2449 @Cvs2SvnTestFunction
2450 def questionable_branch_names():
2451 "test that we can handle weird branch names"
2452 conv = ensure_conversion('questionable-symbols')
2453 # If the conversion succeeds, then we're okay. We could check the
2454 # actual branch paths, too, but the main thing is to know that the
2455 # conversion doesn't fail.
2458 @Cvs2SvnTestFunction
2459 def questionable_tag_names():
2460 "test that we can handle weird tag names"
2461 conv = ensure_conversion('questionable-symbols')
2462 conv.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2463 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2465 conv.find_tag_log('TagWith/Backslash_E').check(
2466 sym_log_msg('TagWith/Backslash_E',1),
2468 ('/%(tags)s/TagWith', 'A'),
2469 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2472 conv.find_tag_log('TagWith/Slash_Z').check(
2473 sym_log_msg('TagWith/Slash_Z',1),
2475 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2480 @Cvs2SvnTestFunction
2481 def revision_reorder_bug():
2482 "reveal a bug that reorders file revisions"
2483 conv = ensure_conversion('revision-reorder-bug')
2484 # If the conversion succeeds, then we're okay. We could check the
2485 # actual revisions, too, but the main thing is to know that the
2486 # conversion doesn't fail.
2489 @Cvs2SvnTestFunction
2490 def exclude():
2491 "test that exclude really excludes everything"
2492 conv = ensure_conversion('main', args=['--exclude=.*'])
2493 for log in conv.logs.values():
2494 for item in log.changed_paths.keys():
2495 if item.startswith('/branches/') or item.startswith('/tags/'):
2496 raise Failure()
2499 @Cvs2SvnTestFunction
2500 def vendor_branch_delete_add():
2501 "add trunk file that was deleted on vendor branch"
2502 # This will error if the bug is present
2503 conv = ensure_conversion('vendor-branch-delete-add')
2506 @Cvs2SvnTestFunction
2507 def resync_pass2_pull_forward():
2508 "ensure pass2 doesn't pull rev too far forward"
2509 conv = ensure_conversion('resync-pass2-pull-forward')
2510 # If the conversion succeeds, then we're okay. We could check the
2511 # actual revisions, too, but the main thing is to know that the
2512 # conversion doesn't fail.
2515 @Cvs2SvnTestFunction
2516 def native_eol():
2517 "only LFs for svn:eol-style=native files"
2518 conv = ensure_conversion('native-eol', args=['--default-eol=native'])
2519 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
2520 conv.repos)
2521 # Verify that all files in the dump have LF EOLs. We're actually
2522 # testing the whole dump file, but the dump file itself only uses
2523 # LF EOLs, so we're safe.
2524 for line in lines:
2525 if line[-1] != '\n' or line[:-1].find('\r') != -1:
2526 raise Failure()
2529 @Cvs2SvnTestFunction
2530 def double_fill():
2531 "reveal a bug that created a branch twice"
2532 conv = ensure_conversion('double-fill')
2533 # If the conversion succeeds, then we're okay. We could check the
2534 # actual revisions, too, but the main thing is to know that the
2535 # conversion doesn't fail.
2538 @XFail_deco()
2539 @Cvs2SvnTestFunction
2540 def double_fill2():
2541 "reveal a second bug that created a branch twice"
2542 conv = ensure_conversion('double-fill2')
2543 conv.logs[6].check_msg(sym_log_msg('BRANCH1'))
2544 conv.logs[7].check_msg(sym_log_msg('BRANCH2'))
2545 try:
2546 # This check should fail:
2547 conv.logs[8].check_msg(sym_log_msg('BRANCH2'))
2548 except Failure:
2549 pass
2550 else:
2551 raise Failure('Symbol filled twice in a row')
2554 @Cvs2SvnTestFunction
2555 def resync_pass2_push_backward():
2556 "ensure pass2 doesn't push rev too far backward"
2557 conv = ensure_conversion('resync-pass2-push-backward')
2558 # If the conversion succeeds, then we're okay. We could check the
2559 # actual revisions, too, but the main thing is to know that the
2560 # conversion doesn't fail.
2563 @Cvs2SvnTestFunction
2564 def double_add():
2565 "reveal a bug that added a branch file twice"
2566 conv = ensure_conversion('double-add')
2567 # If the conversion succeeds, then we're okay. We could check the
2568 # actual revisions, too, but the main thing is to know that the
2569 # conversion doesn't fail.
2572 @Cvs2SvnTestFunction
2573 def bogus_branch_copy():
2574 "reveal a bug that copies a branch file wrongly"
2575 conv = ensure_conversion('bogus-branch-copy')
2576 # If the conversion succeeds, then we're okay. We could check the
2577 # actual revisions, too, but the main thing is to know that the
2578 # conversion doesn't fail.
2581 @Cvs2SvnTestFunction
2582 def nested_ttb_directories():
2583 "require error if ttb directories are not disjoint"
2584 opts_list = [
2585 {'trunk' : 'a', 'branches' : 'a',},
2586 {'trunk' : 'a', 'tags' : 'a',},
2587 {'branches' : 'a', 'tags' : 'a',},
2588 # This option conflicts with the default trunk path:
2589 {'branches' : 'trunk',},
2590 # Try some nested directories:
2591 {'trunk' : 'a', 'branches' : 'a/b',},
2592 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2593 {'branches' : 'a', 'tags' : 'a/b',},
2596 for opts in opts_list:
2597 ensure_conversion(
2598 'main', error_re=r'The following paths are not disjoint\:', **opts
2602 class AutoProps(Cvs2SvnPropertiesTestCase):
2603 """Test auto-props.
2605 The files are as follows:
2607 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2608 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2609 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2610 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2611 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2612 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2613 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2614 trunk/foo.UPCASE1: no -kb, no mime type.
2615 trunk/foo.UPCASE2: no -kb, no mime type.
2618 def __init__(self, args, **kw):
2619 ### TODO: It's a bit klugey to construct this path here. See also
2620 ### the comment in eol_mime().
2621 auto_props_path = os.path.join(
2622 test_data_dir, 'eol-mime-cvsrepos', 'auto-props')
2624 Cvs2SvnPropertiesTestCase.__init__(
2625 self, 'eol-mime',
2626 props_to_test=[
2627 'myprop',
2628 'svn:eol-style',
2629 'svn:mime-type',
2630 'svn:keywords',
2631 'svn:executable',
2633 args=[
2634 '--auto-props=%s' % auto_props_path,
2635 '--eol-from-mime-type'
2636 ] + args,
2637 **kw)
2640 auto_props_ignore_case = AutoProps(
2641 doc="test auto-props",
2642 args=['--default-eol=native'],
2643 expected_props=[
2644 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS, None]),
2645 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS, None]),
2646 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2647 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2648 ('trunk/foo.bin',
2649 ['bin', None, 'application/octet-stream', None, '']),
2650 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2651 ('trunk/foo.dbf',
2652 ['dbf', None, 'application/what-is-dbf', None, None]),
2653 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS, None]),
2654 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS, None]),
2658 @Cvs2SvnTestFunction
2659 def ctrl_char_in_filename():
2660 "do not allow control characters in filenames"
2662 try:
2663 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
2664 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
2665 if os.path.exists(dstrepos_path):
2666 safe_rmtree(dstrepos_path)
2668 # create repos from existing main repos
2669 shutil.copytree(srcrepos_path, dstrepos_path)
2670 base_path = os.path.join(dstrepos_path, 'single-files')
2671 try:
2672 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2673 os.path.join(base_path, 'two\rquick,v'))
2674 except:
2675 # Operating systems that don't allow control characters in
2676 # filenames will hopefully have thrown an exception; in that
2677 # case, just skip this test.
2678 raise svntest.Skip()
2680 conv = ensure_conversion(
2681 'ctrl-char-filename',
2682 error_re=(r'.*Subversion does not allow character .*.'),
2684 finally:
2685 safe_rmtree(dstrepos_path)
2688 @Cvs2SvnTestFunction
2689 def commit_dependencies():
2690 "interleaved and multi-branch commits to same files"
2691 conv = ensure_conversion("commit-dependencies")
2692 conv.logs[2].check('adding', (
2693 ('/%(trunk)s/interleaved', 'A'),
2694 ('/%(trunk)s/interleaved/file1', 'A'),
2695 ('/%(trunk)s/interleaved/file2', 'A'),
2697 conv.logs[3].check('big commit', (
2698 ('/%(trunk)s/interleaved/file1', 'M'),
2699 ('/%(trunk)s/interleaved/file2', 'M'),
2701 conv.logs[4].check('dependant small commit', (
2702 ('/%(trunk)s/interleaved/file1', 'M'),
2704 conv.logs[5].check('adding', (
2705 ('/%(trunk)s/multi-branch', 'A'),
2706 ('/%(trunk)s/multi-branch/file1', 'A'),
2707 ('/%(trunk)s/multi-branch/file2', 'A'),
2709 conv.logs[6].check(sym_log_msg("branch"), (
2710 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2711 ('/%(branches)s/branch/interleaved', 'D'),
2713 conv.logs[7].check('multi-branch-commit', (
2714 ('/%(trunk)s/multi-branch/file1', 'M'),
2715 ('/%(trunk)s/multi-branch/file2', 'M'),
2716 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2717 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2721 @Cvs2SvnTestFunction
2722 def double_branch_delete():
2723 "fill branches before modifying files on them"
2724 conv = ensure_conversion('double-branch-delete')
2726 # Test for issue #102. The file IMarshalledValue.java is branched,
2727 # deleted, readded on the branch, and then deleted again. If the
2728 # fill for the file on the branch is postponed until after the
2729 # modification, the file will end up live on the branch instead of
2730 # dead! Make sure it happens at the right time.
2732 conv.logs[6].check('JBAS-2436 - Adding LGPL Header2', (
2733 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2736 conv.logs[7].check('JBAS-3025 - Removing dependency', (
2737 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2741 @Cvs2SvnTestFunction
2742 def symbol_mismatches():
2743 "error for conflicting tag/branch"
2745 ensure_conversion(
2746 'symbol-mess',
2747 args=['--symbol-default=strict'],
2748 error_re=r'.*Problems determining how symbols should be converted',
2752 @Cvs2SvnTestFunction
2753 def overlook_symbol_mismatches():
2754 "overlook conflicting tag/branch when --trunk-only"
2756 # This is a test for issue #85.
2758 ensure_conversion('symbol-mess', args=['--trunk-only'])
2761 @Cvs2SvnTestFunction
2762 def force_symbols():
2763 "force symbols to be tags/branches"
2765 conv = ensure_conversion(
2766 'symbol-mess',
2767 args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2768 if conv.path_exists('tags', 'BRANCH') \
2769 or not conv.path_exists('branches', 'BRANCH'):
2770 raise Failure()
2771 if not conv.path_exists('tags', 'TAG') \
2772 or conv.path_exists('branches', 'TAG'):
2773 raise Failure()
2774 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2775 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2776 raise Failure()
2777 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2778 or conv.path_exists('branches', 'MOSTLY_TAG'):
2779 raise Failure()
2782 @Cvs2SvnTestFunction
2783 def commit_blocks_tags():
2784 "commit prevents forced tag"
2786 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2787 ensure_conversion(
2788 'symbol-mess',
2789 args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']),
2790 error_re=(
2791 r'.*The following branches cannot be forced to be tags '
2792 r'because they have commits'
2797 @Cvs2SvnTestFunction
2798 def blocked_excludes():
2799 "error for blocked excludes"
2801 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2802 for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']:
2803 try:
2804 ensure_conversion(
2805 'symbol-mess',
2806 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker]))
2807 raise MissingErrorException()
2808 except Failure:
2809 pass
2812 @Cvs2SvnTestFunction
2813 def unblock_blocked_excludes():
2814 "excluding blocker removes blockage"
2816 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2817 for blocker in ['BRANCH', 'COMMIT']:
2818 ensure_conversion(
2819 'symbol-mess',
2820 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker,
2821 '--exclude=BLOCKING_%s' % blocker]))
2824 @Cvs2SvnTestFunction
2825 def regexp_force_symbols():
2826 "force symbols via regular expressions"
2828 conv = ensure_conversion(
2829 'symbol-mess',
2830 args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2831 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2832 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2833 raise Failure()
2834 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2835 or conv.path_exists('branches', 'MOSTLY_TAG'):
2836 raise Failure()
2839 @Cvs2SvnTestFunction
2840 def heuristic_symbol_default():
2841 "test 'heuristic' symbol default"
2843 conv = ensure_conversion(
2844 'symbol-mess', args=['--symbol-default=heuristic'])
2845 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2846 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2847 raise Failure()
2848 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2849 or conv.path_exists('branches', 'MOSTLY_TAG'):
2850 raise Failure()
2853 @Cvs2SvnTestFunction
2854 def branch_symbol_default():
2855 "test 'branch' symbol default"
2857 conv = ensure_conversion(
2858 'symbol-mess', args=['--symbol-default=branch'])
2859 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2860 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2861 raise Failure()
2862 if conv.path_exists('tags', 'MOSTLY_TAG') \
2863 or not conv.path_exists('branches', 'MOSTLY_TAG'):
2864 raise Failure()
2867 @Cvs2SvnTestFunction
2868 def tag_symbol_default():
2869 "test 'tag' symbol default"
2871 conv = ensure_conversion(
2872 'symbol-mess', args=['--symbol-default=tag'])
2873 if not conv.path_exists('tags', 'MOSTLY_BRANCH') \
2874 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
2875 raise Failure()
2876 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2877 or conv.path_exists('branches', 'MOSTLY_TAG'):
2878 raise Failure()
2881 @Cvs2SvnTestFunction
2882 def symbol_transform():
2883 "test --symbol-transform"
2885 conv = ensure_conversion(
2886 'symbol-mess',
2887 args=[
2888 '--symbol-default=heuristic',
2889 '--symbol-transform=BRANCH:branch',
2890 '--symbol-transform=TAG:tag',
2891 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2893 if not conv.path_exists('branches', 'branch'):
2894 raise Failure()
2895 if not conv.path_exists('tags', 'tag'):
2896 raise Failure()
2897 if not conv.path_exists('branches', 'MOSTLY.BRANCH'):
2898 raise Failure()
2899 if not conv.path_exists('tags', 'MOSTLY.TAG'):
2900 raise Failure()
2903 @Cvs2SvnTestFunction
2904 def write_symbol_info():
2905 "test --write-symbol-info"
2907 expected_lines = [
2908 ['0', '.trunk.',
2909 'trunk', 'trunk', '.'],
2910 ['0', 'BLOCKED_BY_UNNAMED',
2911 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2912 ['0', 'BLOCKING_COMMIT',
2913 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2914 ['0', 'BLOCKED_BY_COMMIT',
2915 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2916 ['0', 'BLOCKING_BRANCH',
2917 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2918 ['0', 'BLOCKED_BY_BRANCH',
2919 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2920 ['0', 'MOSTLY_BRANCH',
2921 '.', '.', '.'],
2922 ['0', 'MOSTLY_TAG',
2923 '.', '.', '.'],
2924 ['0', 'BRANCH_WITH_COMMIT',
2925 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2926 ['0', 'BRANCH',
2927 'branch', 'branches/BRANCH', '.trunk.'],
2928 ['0', 'TAG',
2929 'tag', 'tags/TAG', '.trunk.'],
2930 ['0', 'unlabeled-1.1.12.1.2',
2931 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2933 expected_lines.sort()
2935 symbol_info_file = os.path.join(tmp_dir, 'symbol-mess-symbol-info.txt')
2936 try:
2937 ensure_conversion(
2938 'symbol-mess',
2939 args=[
2940 '--symbol-default=strict',
2941 '--write-symbol-info=%s' % (symbol_info_file,),
2942 '--passes=:CollateSymbolsPass',
2945 raise MissingErrorException()
2946 except Failure:
2947 pass
2948 lines = []
2949 comment_re = re.compile(r'^\s*\#')
2950 for l in open(symbol_info_file, 'r'):
2951 if comment_re.match(l):
2952 continue
2953 lines.append(l.strip().split())
2954 lines.sort()
2955 if lines != expected_lines:
2956 s = ['Symbol info incorrect\n']
2957 differ = Differ()
2958 for diffline in differ.compare(
2959 [' '.join(line) + '\n' for line in expected_lines],
2960 [' '.join(line) + '\n' for line in lines],
2962 s.append(diffline)
2963 raise Failure(''.join(s))
2966 @Cvs2SvnTestFunction
2967 def symbol_hints():
2968 "test --symbol-hints for setting branch/tag"
2970 conv = ensure_conversion(
2971 'symbol-mess', symbol_hints_file='symbol-mess-symbol-hints.txt',
2973 if not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2974 raise Failure()
2975 if not conv.path_exists('tags', 'MOSTLY_TAG'):
2976 raise Failure()
2977 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2978 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2980 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2981 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
2983 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2984 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
2988 @Cvs2SvnTestFunction
2989 def parent_hints():
2990 "test --symbol-hints for setting parent"
2992 conv = ensure_conversion(
2993 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints.txt',
2995 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2996 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3000 @Cvs2SvnTestFunction
3001 def parent_hints_invalid():
3002 "test --symbol-hints with an invalid parent"
3004 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3005 # this symbol hints file sets the preferred parent to BRANCH
3006 # instead:
3007 conv = ensure_conversion(
3008 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-invalid.txt',
3009 error_re=(
3010 r"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
3015 @Cvs2SvnTestFunction
3016 def parent_hints_wildcards():
3017 "test --symbol-hints wildcards"
3019 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
3020 # this symbol hints file sets the preferred parent to BRANCH
3021 # instead:
3022 conv = ensure_conversion(
3023 'symbol-mess',
3024 symbol_hints_file='symbol-mess-parent-hints-wildcards.txt',
3026 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3027 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3031 @Cvs2SvnTestFunction
3032 def path_hints():
3033 "test --symbol-hints for setting svn paths"
3035 conv = ensure_conversion(
3036 'symbol-mess', symbol_hints_file='symbol-mess-path-hints.txt',
3038 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3039 ('/trunk', 'A'),
3040 ('/a', 'A'),
3041 ('/a/strange', 'A'),
3042 ('/a/strange/trunk', 'A'),
3043 ('/a/strange/trunk/path', 'A'),
3044 ('/branches', 'A'),
3045 ('/tags', 'A'),
3047 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3048 ('/special', 'A'),
3049 ('/special/tag', 'A'),
3050 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
3052 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3053 ('/special/other', 'A'),
3054 ('/special/other/branch', 'A'),
3055 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
3057 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3058 ('/special/branch', 'A'),
3059 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
3063 @Cvs2SvnTestFunction
3064 def issue_99():
3065 "test problem from issue 99"
3067 conv = ensure_conversion('issue-99')
3070 @Cvs2SvnTestFunction
3071 def issue_100():
3072 "test problem from issue 100"
3074 conv = ensure_conversion('issue-100')
3075 file1 = conv.get_wc('trunk', 'file1.txt')
3076 if file(file1).read() != 'file1.txt<1.2>\n':
3077 raise Failure()
3080 @Cvs2SvnTestFunction
3081 def issue_106():
3082 "test problem from issue 106"
3084 conv = ensure_conversion('issue-106')
3087 @Cvs2SvnTestFunction
3088 def options_option():
3089 "use of the --options option"
3091 conv = ensure_conversion('main', options_file='cvs2svn.options')
3094 @Cvs2SvnTestFunction
3095 def multiproject():
3096 "multiproject conversion"
3098 conv = ensure_conversion(
3099 'main', options_file='cvs2svn-multiproject.options'
3101 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3102 ('/partial-prune', 'A'),
3103 ('/partial-prune/trunk', 'A'),
3104 ('/partial-prune/branches', 'A'),
3105 ('/partial-prune/tags', 'A'),
3106 ('/partial-prune/releases', 'A'),
3110 @Cvs2SvnTestFunction
3111 def crossproject():
3112 "multiproject conversion with cross-project commits"
3114 conv = ensure_conversion(
3115 'main', options_file='cvs2svn-crossproject.options'
3119 @Cvs2SvnTestFunction
3120 def tag_with_no_revision():
3121 "tag defined but revision is deleted"
3123 conv = ensure_conversion('tag-with-no-revision')
3126 @Cvs2SvnTestFunction
3127 def delete_cvsignore():
3128 "svn:ignore should vanish when .cvsignore does"
3130 # This is issue #81.
3132 conv = ensure_conversion('delete-cvsignore')
3134 wc_tree = conv.get_wc_tree()
3135 props = props_for_path(wc_tree, 'trunk/proj')
3137 if props.has_key('svn:ignore'):
3138 raise Failure()
3141 @Cvs2SvnTestFunction
3142 def repeated_deltatext():
3143 "ignore repeated deltatext blocks with warning"
3145 conv = ensure_conversion('repeated-deltatext')
3146 warning_re = r'.*Deltatext block for revision 1.1 appeared twice'
3147 if not conv.output_found(warning_re):
3148 raise Failure()
3151 @Cvs2SvnTestFunction
3152 def nasty_graphs():
3153 "process some nasty dependency graphs"
3155 # It's not how well the bear can dance, but that the bear can dance
3156 # at all:
3157 conv = ensure_conversion('nasty-graphs')
3160 @XFail_deco()
3161 @Cvs2SvnTestFunction
3162 def tagging_after_delete():
3163 "optimal tag after deleting files"
3165 conv = ensure_conversion('tagging-after-delete')
3167 # tag should be 'clean', no deletes
3168 log = conv.find_tag_log('tag1')
3169 expected = (
3170 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
3172 log.check_changes(expected)
3175 @Cvs2SvnTestFunction
3176 def crossed_branches():
3177 "branches created in inconsistent orders"
3179 conv = ensure_conversion('crossed-branches')
3182 @Cvs2SvnTestFunction
3183 def file_directory_conflict():
3184 "error when filename conflicts with directory name"
3186 conv = ensure_conversion(
3187 'file-directory-conflict',
3188 error_re=r'.*Directory name conflicts with filename',
3192 @Cvs2SvnTestFunction
3193 def attic_directory_conflict():
3194 "error when attic filename conflicts with dirname"
3196 # This tests the problem reported in issue #105.
3198 conv = ensure_conversion(
3199 'attic-directory-conflict',
3200 error_re=r'.*Directory name conflicts with filename',
3204 @Cvs2SvnTestFunction
3205 def use_rcs():
3206 "verify that --use-rcs and --use-internal-co agree"
3208 rcs_conv = ensure_conversion(
3209 'main', args=['--use-rcs', '--default-eol=native'], dumpfile='use-rcs-rcs.dump',
3211 conv = ensure_conversion(
3212 'main', args=['--default-eol=native'], dumpfile='use-rcs-int.dump',
3214 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3215 raise Failure()
3216 rcs_lines = list(open(rcs_conv.dumpfile, 'rb'))
3217 lines = list(open(conv.dumpfile, 'rb'))
3218 # Compare all lines following the repository UUID:
3219 if lines[3:] != rcs_lines[3:]:
3220 raise Failure()
3223 @Cvs2SvnTestFunction
3224 def internal_co_exclude():
3225 "verify that --use-internal-co --exclude=... works"
3227 rcs_conv = ensure_conversion(
3228 'internal-co',
3229 args=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3230 dumpfile='internal-co-exclude-rcs.dump',
3232 conv = ensure_conversion(
3233 'internal-co',
3234 args=['--exclude=BRANCH', '--default-eol=native'],
3235 dumpfile='internal-co-exclude-int.dump',
3237 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3238 raise Failure()
3239 rcs_lines = list(open(rcs_conv.dumpfile, 'rb'))
3240 lines = list(open(conv.dumpfile, 'rb'))
3241 # Compare all lines following the repository UUID:
3242 if lines[3:] != rcs_lines[3:]:
3243 raise Failure()
3246 @Cvs2SvnTestFunction
3247 def internal_co_trunk_only():
3248 "verify that --use-internal-co --trunk-only works"
3250 conv = ensure_conversion(
3251 'internal-co',
3252 args=['--trunk-only', '--default-eol=native'],
3254 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3255 raise Failure()
3258 @Cvs2SvnTestFunction
3259 def leftover_revs():
3260 "check for leftover checked-out revisions"
3262 conv = ensure_conversion(
3263 'leftover-revs',
3264 args=['--exclude=BRANCH', '--default-eol=native'],
3266 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3267 raise Failure()
3270 @Cvs2SvnTestFunction
3271 def requires_internal_co():
3272 "test that internal co can do more than RCS"
3273 # See issues 4, 11 for the bugs whose regression we're testing for.
3274 # Unlike in requires_cvs above, issue 29 is not covered.
3275 conv = ensure_conversion('requires-cvs')
3277 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
3279 if atsign_contents[-1:] == "@":
3280 raise Failure()
3282 if not (conv.logs[6].author == "William Lyon Phelps III" and
3283 conv.logs[5].author == "j random"):
3284 raise Failure()
3287 @Cvs2SvnTestFunction
3288 def internal_co_keywords():
3289 "test that internal co handles keywords correctly"
3290 conv_ic = ensure_conversion('internal-co-keywords',
3291 args=["--keywords-off"])
3292 conv_cvs = ensure_conversion('internal-co-keywords',
3293 args=["--use-cvs", "--keywords-off"])
3295 ko_ic = file(conv_ic.get_wc('trunk', 'dir', 'ko.txt')).read()
3296 ko_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'ko.txt')).read()
3297 kk_ic = file(conv_ic.get_wc('trunk', 'dir', 'kk.txt')).read()
3298 kk_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kk.txt')).read()
3299 kv_ic = file(conv_ic.get_wc('trunk', 'dir', 'kv.txt')).read()
3300 kv_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kv.txt')).read()
3301 # Ensure proper "/Attic" expansion of $Source$ keyword in files
3302 # which are in a deleted state in trunk
3303 del_ic = file(conv_ic.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3304 del_cvs = file(conv_cvs.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read()
3307 if ko_ic != ko_cvs:
3308 raise Failure()
3309 if kk_ic != kk_cvs:
3310 raise Failure()
3311 if del_ic != del_cvs:
3312 raise Failure()
3314 # The date format changed between cvs and co ('/' instead of '-').
3315 # Accept either one:
3316 date_substitution_re = re.compile(r' ([0-9]*)-([0-9]*)-([0-9]*) ')
3317 if kv_ic != kv_cvs \
3318 and date_substitution_re.sub(r' \1/\2/\3 ', kv_ic) != kv_cvs:
3319 raise Failure()
3322 @Cvs2SvnTestFunction
3323 def timestamp_chaos():
3324 "test timestamp adjustments"
3326 conv = ensure_conversion('timestamp-chaos', args=["-v"])
3328 # The times are expressed here in UTC:
3329 times = [
3330 '2007-01-01 21:00:00', # Initial commit
3331 '2007-01-01 21:00:00', # revision 1.1 of both files
3332 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3333 '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards
3334 '2007-01-01 22:00:00', # revision 1.3 of both files
3337 # Convert the times to seconds since the epoch, in UTC:
3338 times = [calendar.timegm(svn_strptime(t)) for t in times]
3340 for i in range(len(times)):
3341 if abs(conv.logs[i + 1].date - times[i]) > 0.1:
3342 raise Failure()
3345 @Cvs2SvnTestFunction
3346 def symlinks():
3347 "convert a repository that contains symlinks"
3349 # This is a test for issue #97.
3351 proj = os.path.join(test_data_dir, 'symlinks-cvsrepos', 'proj')
3352 links = [
3354 os.path.join('..', 'file.txt,v'),
3355 os.path.join(proj, 'dir1', 'file.txt,v'),
3358 'dir1',
3359 os.path.join(proj, 'dir2'),
3363 try:
3364 os.symlink
3365 except AttributeError:
3366 # Apparently this OS doesn't support symlinks, so skip test.
3367 raise svntest.Skip()
3369 try:
3370 for (src,dst) in links:
3371 os.symlink(src, dst)
3373 conv = ensure_conversion('symlinks')
3374 conv.logs[2].check('', (
3375 ('/%(trunk)s/proj', 'A'),
3376 ('/%(trunk)s/proj/file.txt', 'A'),
3377 ('/%(trunk)s/proj/dir1', 'A'),
3378 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3379 ('/%(trunk)s/proj/dir2', 'A'),
3380 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3382 finally:
3383 for (src,dst) in links:
3384 os.remove(dst)
3387 @Cvs2SvnTestFunction
3388 def empty_trunk_path():
3389 "allow --trunk to be empty if --trunk-only"
3391 # This is a test for issue #53.
3393 conv = ensure_conversion(
3394 'main', args=['--trunk-only', '--trunk='],
3398 @Cvs2SvnTestFunction
3399 def preferred_parent_cycle():
3400 "handle a cycle in branch parent preferences"
3402 conv = ensure_conversion('preferred-parent-cycle')
3405 @Cvs2SvnTestFunction
3406 def branch_from_empty_dir():
3407 "branch from an empty directory"
3409 conv = ensure_conversion('branch-from-empty-dir')
3412 @Cvs2SvnTestFunction
3413 def trunk_readd():
3414 "add a file on a branch then on trunk"
3416 conv = ensure_conversion('trunk-readd')
3419 @Cvs2SvnTestFunction
3420 def branch_from_deleted_1_1():
3421 "branch from a 1.1 revision that will be deleted"
3423 conv = ensure_conversion('branch-from-deleted-1-1')
3424 conv.logs[5].check('Adding b.txt:1.1.2.1', (
3425 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3427 conv.logs[6].check('Adding b.txt:1.1.4.1', (
3428 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3430 conv.logs[7].check('Adding b.txt:1.2', (
3431 ('/%(trunk)s/proj/b.txt', 'A'),
3434 conv.logs[8].check('Adding c.txt:1.1.2.1', (
3435 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3437 conv.logs[9].check('Adding c.txt:1.1.4.1', (
3438 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3442 @Cvs2SvnTestFunction
3443 def add_on_branch():
3444 "add a file on a branch using newer CVS"
3446 conv = ensure_conversion('add-on-branch')
3447 conv.logs[6].check('Adding b.txt:1.1', (
3448 ('/%(trunk)s/proj/b.txt', 'A'),
3450 conv.logs[7].check('Adding b.txt:1.1.2.2', (
3451 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3453 conv.logs[8].check('Adding c.txt:1.1', (
3454 ('/%(trunk)s/proj/c.txt', 'A'),
3456 conv.logs[9].check('Removing c.txt:1.2', (
3457 ('/%(trunk)s/proj/c.txt', 'D'),
3459 conv.logs[10].check('Adding c.txt:1.2.2.2', (
3460 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3462 conv.logs[11].check('Adding d.txt:1.1', (
3463 ('/%(trunk)s/proj/d.txt', 'A'),
3465 conv.logs[12].check('Adding d.txt:1.1.2.2', (
3466 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3470 @Cvs2SvnTestFunction
3471 def main_git():
3472 "test output in git-fast-import format"
3474 # Note: To test importing into git, do
3476 # ./run-tests <this-test-number>
3477 # rm -rf cvs2svn-tmp/main.git
3478 # git init --bare cvs2svn-tmp/main.git
3479 # cd cvs2svn-tmp/main.git
3480 # cat ../git-{blob,dump}.dat | git fast-import
3482 # Or, to load the dumpfiles separately:
3484 # cat ../git-blob.dat | git fast-import --export-marks=../git-marks.dat
3485 # cat ../git-dump.dat | git fast-import --import-marks=../git-marks.dat
3487 # Then use "gitk --all", "git log", etc. to test the contents of the
3488 # repository or "git clone" to make a non-bare clone.
3490 # We don't have the infrastructure to check that the resulting git
3491 # repository is correct, so we just check that the conversion runs
3492 # to completion:
3493 conv = GitConversion('main', None, [
3494 '--blobfile=cvs2svn-tmp/git-blob.dat',
3495 '--dumpfile=cvs2svn-tmp/git-dump.dat',
3496 '--username=cvs2git',
3497 'test-data/main-cvsrepos',
3501 @Cvs2SvnTestFunction
3502 def main_git2():
3503 "test cvs2git --use-external-blob-generator option"
3505 # See comment in main_git() for more information.
3507 conv = GitConversion('main', None, [
3508 '--use-external-blob-generator',
3509 '--blobfile=cvs2svn-tmp/blobfile.out',
3510 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3511 '--username=cvs2git',
3512 'test-data/main-cvsrepos',
3516 @Cvs2SvnTestFunction
3517 def git_options():
3518 "test cvs2git using options file"
3520 conv = GitConversion('main', None, [], options_file='cvs2git.options')
3523 @Cvs2SvnTestFunction
3524 def main_hg():
3525 "output in git-fast-import format with inline data"
3527 # The output should be suitable for import by Mercurial.
3529 # We don't have the infrastructure to check that the resulting
3530 # Mercurial repository is correct, so we just check that the
3531 # conversion runs to completion:
3532 conv = GitConversion('main', None, [], options_file='cvs2hg.options')
3535 @Cvs2SvnTestFunction
3536 def invalid_symbol():
3537 "a symbol with the incorrect format"
3539 conv = ensure_conversion('invalid-symbol')
3540 if not conv.output_found(
3541 r".*branch 'SYMBOL' references invalid revision 1$"
3543 raise Failure()
3546 @Cvs2SvnTestFunction
3547 def invalid_symbol_ignore():
3548 "ignore a symbol using a SymbolMapper"
3550 conv = ensure_conversion(
3551 'invalid-symbol', options_file='cvs2svn-ignore.options'
3555 @Cvs2SvnTestFunction
3556 def invalid_symbol_ignore2():
3557 "ignore a symbol using an IgnoreSymbolTransform"
3559 conv = ensure_conversion(
3560 'invalid-symbol', options_file='cvs2svn-ignore2.options'
3564 class EOLVariants(Cvs2SvnTestCase):
3565 "handle various --eol-style options"
3567 eol_style_strings = {
3568 'LF' : '\n',
3569 'CR' : '\r',
3570 'CRLF' : '\r\n',
3571 'native' : '\n',
3574 def __init__(self, eol_style):
3575 self.eol_style = eol_style
3576 self.dumpfile = 'eol-variants-%s.dump' % (self.eol_style,)
3577 Cvs2SvnTestCase.__init__(
3578 self, 'eol-variants', variant=self.eol_style,
3579 dumpfile=self.dumpfile,
3580 args=[
3581 '--default-eol=%s' % (self.eol_style,),
3585 def run(self, sbox):
3586 conv = self.ensure_conversion()
3587 dump_contents = open(conv.dumpfile, 'rb').read()
3588 expected_text = self.eol_style_strings[self.eol_style].join(
3589 ['line 1', 'line 2', '\n\n']
3591 if not dump_contents.endswith(expected_text):
3592 raise Failure()
3595 @Cvs2SvnTestFunction
3596 def no_revs_file():
3597 "handle a file with no revisions (issue #80)"
3599 conv = ensure_conversion('no-revs-file')
3602 @Cvs2SvnTestFunction
3603 def mirror_keyerror_test():
3604 "a case that gave KeyError in SVNRepositoryMirror"
3606 conv = ensure_conversion('mirror-keyerror')
3609 @Cvs2SvnTestFunction
3610 def exclude_ntdb_test():
3611 "exclude a non-trunk default branch"
3613 symbol_info_file = os.path.join(tmp_dir, 'exclude-ntdb-symbol-info.txt')
3614 conv = ensure_conversion(
3615 'exclude-ntdb',
3616 args=[
3617 '--write-symbol-info=%s' % (symbol_info_file,),
3618 '--exclude=branch3',
3619 '--exclude=tag3',
3620 '--exclude=vendortag3',
3621 '--exclude=vendorbranch',
3626 @Cvs2SvnTestFunction
3627 def mirror_keyerror2_test():
3628 "a case that gave KeyError in RepositoryMirror"
3630 conv = ensure_conversion('mirror-keyerror2')
3633 @Cvs2SvnTestFunction
3634 def mirror_keyerror3_test():
3635 "a case that gave KeyError in RepositoryMirror"
3637 conv = ensure_conversion('mirror-keyerror3')
3640 @XFail_deco()
3641 @Cvs2SvnTestFunction
3642 def add_cvsignore_to_branch_test():
3643 "check adding .cvsignore to an existing branch"
3645 # This a test for issue #122.
3647 conv = ensure_conversion('add-cvsignore-to-branch')
3648 wc_tree = conv.get_wc_tree()
3649 trunk_props = props_for_path(wc_tree, 'trunk/dir')
3650 if trunk_props['svn:ignore'] != '*.o\n\n':
3651 raise Failure()
3653 branch_props = props_for_path(wc_tree, 'branches/BRANCH/dir')
3654 if branch_props['svn:ignore'] != '*.o\n\n':
3655 raise Failure()
3658 @Cvs2SvnTestFunction
3659 def missing_deltatext():
3660 "a revision's deltatext is missing"
3662 # This is a type of RCS file corruption that has been observed.
3663 conv = ensure_conversion(
3664 'missing-deltatext',
3665 error_re=(
3666 r"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3671 @Cvs2SvnTestFunction
3672 def transform_unlabeled_branch_name():
3673 "transform name of unlabeled branch"
3675 conv = ensure_conversion(
3676 'unlabeled-branch',
3677 args=[
3678 '--symbol-transform=unlabeled-1.1.4:BRANCH2',
3681 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3682 raise Failure('Branch unlabeled-1.1.4 not excluded')
3683 if not conv.path_exists('branches', 'BRANCH2'):
3684 raise Failure('Branch BRANCH2 not found')
3687 @Cvs2SvnTestFunction
3688 def ignore_unlabeled_branch():
3689 "ignoring an unlabeled branch is not allowed"
3691 conv = ensure_conversion(
3692 'unlabeled-branch',
3693 options_file='cvs2svn-ignore.options',
3694 error_re=(
3695 r"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' "
3696 r"in \'.*\' contains commits"
3701 @Cvs2SvnTestFunction
3702 def exclude_unlabeled_branch():
3703 "exclude unlabeled branch"
3705 conv = ensure_conversion(
3706 'unlabeled-branch',
3707 args=['--exclude=unlabeled-.*'],
3709 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3710 raise Failure('Branch unlabeled-1.1.4 not excluded')
3713 @Cvs2SvnTestFunction
3714 def unlabeled_branch_name_collision():
3715 "transform unlabeled branch to same name as branch"
3717 conv = ensure_conversion(
3718 'unlabeled-branch',
3719 args=[
3720 '--symbol-transform=unlabeled-1.1.4:BRANCH',
3722 error_re=(
3723 r"ERROR\: Symbol name \'BRANCH\' is already used"
3728 @Cvs2SvnTestFunction
3729 def collision_with_unlabeled_branch_name():
3730 "transform branch to same name as unlabeled branch"
3732 conv = ensure_conversion(
3733 'unlabeled-branch',
3734 args=[
3735 '--symbol-transform=BRANCH:unlabeled-1.1.4',
3737 error_re=(
3738 r"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used"
3743 @Cvs2SvnTestFunction
3744 def many_deletes():
3745 "a repo with many removable dead revisions"
3747 conv = ensure_conversion('many-deletes')
3748 conv.logs[5].check('Add files on BRANCH', (
3749 ('/%(branches)s/BRANCH/proj/b.txt', 'A'),
3751 conv.logs[6].check('Add files on BRANCH2', (
3752 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3753 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3754 ('/%(branches)s/BRANCH2/proj/d.txt', 'A'),
3758 cvs_description = Cvs2SvnPropertiesTestCase(
3759 'main',
3760 doc='test handling of CVS file descriptions',
3761 props_to_test=['cvs:description'],
3762 expected_props=[
3763 ('trunk/proj/default', ['This is an example file description.']),
3764 ('trunk/proj/sub1/default', [None]),
3768 @Cvs2SvnTestFunction
3769 def include_empty_directories():
3770 "test --include-empty-directories option"
3772 conv = ensure_conversion(
3773 'empty-directories', args=['--include-empty-directories'],
3775 conv.logs[1].check('Standard project directories', (
3776 ('/%(trunk)s', 'A'),
3777 ('/%(branches)s', 'A'),
3778 ('/%(tags)s', 'A'),
3779 ('/%(trunk)s/root-empty-directory', 'A'),
3780 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3782 conv.logs[3].check('Add b.txt.', (
3783 ('/%(trunk)s/direct', 'A'),
3784 ('/%(trunk)s/direct/b.txt', 'A'),
3785 ('/%(trunk)s/direct/empty-directory', 'A'),
3786 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3788 conv.logs[4].check('Add c.txt.', (
3789 ('/%(trunk)s/indirect', 'A'),
3790 ('/%(trunk)s/indirect/subdirectory', 'A'),
3791 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3792 ('/%(trunk)s/indirect/empty-directory', 'A'),
3793 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3795 conv.logs[5].check('Remove b.txt', (
3796 ('/%(trunk)s/direct', 'D'),
3798 conv.logs[6].check('Remove c.txt', (
3799 ('/%(trunk)s/indirect', 'D'),
3801 conv.logs[7].check('Re-add b.txt.', (
3802 ('/%(trunk)s/direct', 'A'),
3803 ('/%(trunk)s/direct/b.txt', 'A'),
3804 ('/%(trunk)s/direct/empty-directory', 'A'),
3805 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3807 conv.logs[8].check('Re-add c.txt.', (
3808 ('/%(trunk)s/indirect', 'A'),
3809 ('/%(trunk)s/indirect/subdirectory', 'A'),
3810 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3811 ('/%(trunk)s/indirect/empty-directory', 'A'),
3812 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3814 conv.logs[9].check('This commit was manufactured', (
3815 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3817 conv.logs[10].check('This commit was manufactured', (
3818 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3820 conv.logs[11].check('Import d.txt.', (
3821 ('/%(branches)s/VENDORBRANCH', 'A'),
3822 ('/%(branches)s/VENDORBRANCH/import', 'A'),
3823 ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'),
3824 ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'),
3825 ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory',
3826 'A'),
3827 ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'),
3828 ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory',
3829 'A'),
3831 conv.logs[12].check('This commit was generated', (
3832 ('/%(trunk)s/import', 'A'),
3833 ('/%(trunk)s/import/d.txt '
3834 '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'),
3835 ('/%(trunk)s/import/empty-directory', 'A'),
3836 ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'),
3840 @Cvs2SvnTestFunction
3841 def include_empty_directories_no_prune():
3842 "test --include-empty-directories with --no-prune"
3844 conv = ensure_conversion(
3845 'empty-directories', args=['--include-empty-directories', '--no-prune'],
3847 conv.logs[1].check('Standard project directories', (
3848 ('/%(trunk)s', 'A'),
3849 ('/%(branches)s', 'A'),
3850 ('/%(tags)s', 'A'),
3851 ('/%(trunk)s/root-empty-directory', 'A'),
3852 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3854 conv.logs[3].check('Add b.txt.', (
3855 ('/%(trunk)s/direct', 'A'),
3856 ('/%(trunk)s/direct/b.txt', 'A'),
3857 ('/%(trunk)s/direct/empty-directory', 'A'),
3858 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3860 conv.logs[4].check('Add c.txt.', (
3861 ('/%(trunk)s/indirect', 'A'),
3862 ('/%(trunk)s/indirect/subdirectory', 'A'),
3863 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3864 ('/%(trunk)s/indirect/empty-directory', 'A'),
3865 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3867 conv.logs[5].check('Remove b.txt', (
3868 ('/%(trunk)s/direct/b.txt', 'D'),
3870 conv.logs[6].check('Remove c.txt', (
3871 ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'),
3873 conv.logs[7].check('Re-add b.txt.', (
3874 ('/%(trunk)s/direct/b.txt', 'A'),
3876 conv.logs[8].check('Re-add c.txt.', (
3877 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3879 conv.logs[9].check('This commit was manufactured', (
3880 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3882 conv.logs[10].check('This commit was manufactured', (
3883 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3887 @Cvs2SvnTestFunction
3888 def exclude_symbol_default():
3889 "test 'exclude' symbol default"
3891 conv = ensure_conversion(
3892 'symbol-mess', args=['--symbol-default=exclude'])
3893 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
3894 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
3895 raise Failure()
3896 if conv.path_exists('tags', 'MOSTLY_TAG') \
3897 or conv.path_exists('branches', 'MOSTLY_TAG'):
3898 raise Failure()
3901 @Cvs2SvnTestFunction
3902 def add_on_branch2():
3903 "another add-on-branch test case"
3905 conv = ensure_conversion('add-on-branch2')
3906 if len(conv.logs) != 2:
3907 raise Failure()
3908 conv.logs[2].check('add file on branch', (
3909 ('/%(branches)s/BRANCH', 'A'),
3910 ('/%(branches)s/BRANCH/file1', 'A'),
3914 @Cvs2SvnTestFunction
3915 def branch_from_vendor_branch():
3916 "branch from vendor branch"
3918 ensure_conversion(
3919 'branch-from-vendor-branch',
3920 symbol_hints_file='branch-from-vendor-branch-symbol-hints.txt',
3924 @Cvs2SvnTestFunction
3925 def strange_default_branch():
3926 "default branch too deep in the hierarchy"
3928 ensure_conversion(
3929 'strange-default-branch',
3930 error_re=(
3931 r'ERROR\: The default branch 1\.2\.4\.3\.2\.1\.2 '
3932 r'in file .* is not a top-level branch'
3937 @Cvs2SvnTestFunction
3938 def move_parent():
3939 "graft onto preferred parent that was itself moved"
3941 conv = ensure_conversion(
3942 'move-parent',
3944 conv.logs[2].check('first', (
3945 ('/%(trunk)s/file1', 'A'),
3946 ('/%(trunk)s/file2', 'A'),
3948 conv.logs[3].check('This commit was manufactured', (
3949 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
3951 conv.logs[4].check('second', (
3952 ('/%(branches)s/b2/file1', 'M'),
3954 conv.logs[5].check('This commit was manufactured', (
3955 ('/%(branches)s/b1 (from /%(branches)s/b2:4)', 'A'),
3958 # b2 and b1 are equally good parents for b3, so accept either one.
3959 # (Currently, cvs2svn chooses b1 as the preferred parent because it
3960 # comes earlier than b2 in alphabetical order.)
3961 try:
3962 conv.logs[6].check('This commit was manufactured', (
3963 ('/%(branches)s/b3 (from /%(branches)s/b1:5)', 'A'),
3965 except Failure:
3966 conv.logs[6].check('This commit was manufactured', (
3967 ('/%(branches)s/b3 (from /%(branches)s/b2:4)', 'A'),
3971 @Cvs2SvnTestFunction
3972 def log_message_eols():
3973 "nonstandard EOLs in log messages"
3975 conv = ensure_conversion(
3976 'log-message-eols',
3978 conv.logs[2].check('The CRLF at the end of this line\nshould', (
3979 ('/%(trunk)s/lottalogs', 'A'),
3981 conv.logs[3].check('The CR at the end of this line\nshould', (
3982 ('/%(trunk)s/lottalogs', 'M'),
3986 @Cvs2SvnTestFunction
3987 def missing_vendor_branch():
3988 "default branch not present in RCS file"
3990 conv = ensure_conversion(
3991 'missing-vendor-branch',
3993 if not conv.output_found(
3994 r'.*vendor branch \'1\.1\.1\' is not present in file and will be ignored'
3996 raise Failure()
3999 @Cvs2SvnTestFunction
4000 def newphrases():
4001 "newphrases in RCS files"
4003 ensure_conversion(
4004 'newphrases',
4008 ########################################################################
4009 # Run the tests
4011 # list all tests here, starting with None:
4012 test_list = [
4013 None,
4014 # 1:
4015 show_usage,
4016 cvs2svn_manpage,
4017 cvs2git_manpage,
4018 cvs2hg_manpage,
4019 attr_exec,
4020 space_fname,
4021 two_quick,
4022 PruneWithCare(),
4023 PruneWithCare(variant=1, trunk='a', branches='b', tags='c'),
4024 # 10:
4025 PruneWithCare(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4026 PruneWithCare(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4027 interleaved_commits,
4028 simple_commits,
4029 SimpleTags(),
4030 SimpleTags(variant=1, trunk='a', branches='b', tags='c'),
4031 SimpleTags(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4032 SimpleTags(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4033 simple_branch_commits,
4034 mixed_time_tag,
4035 # 20:
4036 mixed_time_branch_with_added_file,
4037 mixed_commit,
4038 split_time_branch,
4039 bogus_tag,
4040 overlapping_branch,
4041 PhoenixBranch(),
4042 PhoenixBranch(variant=1, trunk='a/1', branches='b/1', tags='c/1'),
4043 ctrl_char_in_log,
4044 overdead,
4045 NoTrunkPrune(),
4046 # 30:
4047 NoTrunkPrune(variant=1, trunk='a', branches='b', tags='c'),
4048 NoTrunkPrune(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
4049 NoTrunkPrune(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
4050 double_delete,
4051 split_branch,
4052 resync_misgroups,
4053 TaggedBranchAndTrunk(),
4054 TaggedBranchAndTrunk(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4055 enroot_race,
4056 enroot_race_obo,
4057 # 40:
4058 BranchDeleteFirst(),
4059 BranchDeleteFirst(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4060 nonascii_cvsignore,
4061 nonascii_filenames,
4062 UnicodeAuthor(
4063 warning_expected=1),
4064 UnicodeAuthor(
4065 warning_expected=0,
4066 variant='encoding', args=['--encoding=utf_8']),
4067 UnicodeAuthor(
4068 warning_expected=0,
4069 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
4070 UnicodeLog(
4071 warning_expected=1),
4072 UnicodeLog(
4073 warning_expected=0,
4074 variant='encoding', args=['--encoding=utf_8']),
4075 UnicodeLog(
4076 warning_expected=0,
4077 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
4078 # 50:
4079 vendor_branch_sameness,
4080 vendor_branch_trunk_only,
4081 default_branches,
4082 default_branches_trunk_only,
4083 default_branch_and_1_2,
4084 compose_tag_three_sources,
4085 pass5_when_to_fill,
4086 PeerPathPruning(),
4087 PeerPathPruning(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4088 EmptyTrunk(),
4089 # 60:
4090 EmptyTrunk(variant=1, trunk='a', branches='b', tags='c'),
4091 EmptyTrunk(variant=2, trunk='a/1', branches='a/2', tags='a/3'),
4092 no_spurious_svn_commits,
4093 invalid_closings_on_trunk,
4094 individual_passes,
4095 resync_bug,
4096 branch_from_default_branch,
4097 file_in_attic_too,
4098 retain_file_in_attic_too,
4099 symbolic_name_filling_guide,
4100 # 70:
4101 eol_mime1,
4102 eol_mime2,
4103 eol_mime3,
4104 eol_mime4,
4105 cvs_revnums_off,
4106 cvs_revnums_on,
4107 keywords,
4108 ignore,
4109 requires_cvs,
4110 questionable_branch_names,
4111 # 80:
4112 questionable_tag_names,
4113 revision_reorder_bug,
4114 exclude,
4115 vendor_branch_delete_add,
4116 resync_pass2_pull_forward,
4117 native_eol,
4118 double_fill,
4119 double_fill2,
4120 resync_pass2_push_backward,
4121 double_add,
4122 # 90:
4123 bogus_branch_copy,
4124 nested_ttb_directories,
4125 auto_props_ignore_case,
4126 ctrl_char_in_filename,
4127 commit_dependencies,
4128 show_help_passes,
4129 multiple_tags,
4130 multiply_defined_symbols,
4131 multiply_defined_symbols_renamed,
4132 multiply_defined_symbols_ignored,
4133 # 100:
4134 repeatedly_defined_symbols,
4135 double_branch_delete,
4136 symbol_mismatches,
4137 overlook_symbol_mismatches,
4138 force_symbols,
4139 commit_blocks_tags,
4140 blocked_excludes,
4141 unblock_blocked_excludes,
4142 regexp_force_symbols,
4143 heuristic_symbol_default,
4144 # 110:
4145 branch_symbol_default,
4146 tag_symbol_default,
4147 symbol_transform,
4148 write_symbol_info,
4149 symbol_hints,
4150 parent_hints,
4151 parent_hints_invalid,
4152 parent_hints_wildcards,
4153 path_hints,
4154 issue_99,
4155 # 120:
4156 issue_100,
4157 issue_106,
4158 options_option,
4159 multiproject,
4160 crossproject,
4161 tag_with_no_revision,
4162 delete_cvsignore,
4163 repeated_deltatext,
4164 nasty_graphs,
4165 tagging_after_delete,
4166 # 130:
4167 crossed_branches,
4168 file_directory_conflict,
4169 attic_directory_conflict,
4170 use_rcs,
4171 internal_co_exclude,
4172 internal_co_trunk_only,
4173 internal_co_keywords,
4174 leftover_revs,
4175 requires_internal_co,
4176 timestamp_chaos,
4177 # 140:
4178 symlinks,
4179 empty_trunk_path,
4180 preferred_parent_cycle,
4181 branch_from_empty_dir,
4182 trunk_readd,
4183 branch_from_deleted_1_1,
4184 add_on_branch,
4185 main_git,
4186 main_git2,
4187 git_options,
4188 # 150:
4189 main_hg,
4190 invalid_symbol,
4191 invalid_symbol_ignore,
4192 invalid_symbol_ignore2,
4193 EOLVariants('LF'),
4194 EOLVariants('CR'),
4195 EOLVariants('CRLF'),
4196 EOLVariants('native'),
4197 no_revs_file,
4198 mirror_keyerror_test,
4199 # 160:
4200 exclude_ntdb_test,
4201 mirror_keyerror2_test,
4202 mirror_keyerror3_test,
4203 add_cvsignore_to_branch_test,
4204 missing_deltatext,
4205 transform_unlabeled_branch_name,
4206 ignore_unlabeled_branch,
4207 exclude_unlabeled_branch,
4208 unlabeled_branch_name_collision,
4209 collision_with_unlabeled_branch_name,
4210 # 170:
4211 many_deletes,
4212 cvs_description,
4213 include_empty_directories,
4214 include_empty_directories_no_prune,
4215 exclude_symbol_default,
4216 add_on_branch2,
4217 branch_from_vendor_branch,
4218 strange_default_branch,
4219 move_parent,
4220 log_message_eols,
4221 # 180:
4222 missing_vendor_branch,
4223 newphrases,
4226 if __name__ == '__main__':
4228 # Configure the environment for reproducable output from svn, etc.
4229 os.environ["LC_ALL"] = "C"
4231 # Unfortunately, there is no way under Windows to make Subversion
4232 # think that the local time zone is UTC, so we just work in the
4233 # local time zone.
4235 # The Subversion test suite code assumes it's being invoked from
4236 # within a working copy of the Subversion sources, and tries to use
4237 # the binaries in that tree. Since the cvs2svn tree never contains
4238 # a Subversion build, we just use the system's installed binaries.
4239 svntest.main.svn_binary = svn_binary
4240 svntest.main.svnlook_binary = svnlook_binary
4241 svntest.main.svnadmin_binary = svnadmin_binary
4242 svntest.main.svnversion_binary = svnversion_binary
4244 svntest.main.run_tests(test_list)
4245 # NOTREACHED
4248 ### End of file.