Add some svn:ignores to .gitignore.
[cvs2svn.git] / run-tests.py
blobee8afa58169d61b530e1404c25d7289d33d7b782
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
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 print the stderr lines if
124 svntest.main.options.verbose is true.
126 If ERROR_RE is not None, it is a string regular expression that must
127 match some line of stderr. If it fails to match, raise
128 MissingErrorExpection."""
130 # FIXME: exit_code is currently ignored.
131 exit_code, out, err = svntest.main.run_command(program, 1, 0, *varargs)
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 if svntest.main.options.verbose:
146 print '\n%s said:\n' % program
147 for line in err:
148 print ' ' + line,
149 print
150 raise RunProgramException()
152 return out
155 def run_script(script, error_re, *varargs):
156 """Run Python script SCRIPT with VARARGS, returning stdout as a list
157 of lines.
159 If there is any stderr and ERROR_RE is None, raise
160 RunProgramException, and print the stderr lines if
161 svntest.main.options.verbose is true.
163 If ERROR_RE is not None, it is a string regular expression that must
164 match some line of stderr. If it fails to match, raise
165 MissingErrorException."""
167 # Use the same python that is running this script
168 return run_program(sys.executable, error_re, script, *varargs)
169 # On Windows, for an unknown reason, the cmd.exe process invoked by
170 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
171 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
172 # this. Therefore, the redirection of the output to the .s-revs file fails.
173 # We no longer use the problematic invocation on any system, but this
174 # comment remains to warn about this problem.
177 def run_svn(*varargs):
178 """Run svn with VARARGS; return stdout as a list of lines.
179 If there is any stderr, raise RunProgramException, and print the
180 stderr lines if svntest.main.options.verbose is true."""
181 return run_program(svn_binary, None, *varargs)
184 def repos_to_url(path_to_svn_repos):
185 """This does what you think it does."""
186 rpath = os.path.abspath(path_to_svn_repos)
187 if rpath[0] != '/':
188 rpath = '/' + rpath
189 return 'file://%s' % rpath.replace(os.sep, '/')
192 def svn_strptime(timestr):
193 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
196 class Log:
197 def __init__(self, revision, author, date, symbols):
198 self.revision = revision
199 self.author = author
201 # Internally, we represent the date as seconds since epoch (UTC).
202 # Since standard subversion log output shows dates in localtime
204 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
206 # and time.mktime() converts from localtime, it all works out very
207 # happily.
208 self.date = time.mktime(svn_strptime(date[0:19]))
210 # The following symbols are used for string interpolation when
211 # checking paths:
212 self.symbols = symbols
214 # The changed paths will be accumulated later, as log data is read.
215 # Keys here are paths such as '/trunk/foo/bar', values are letter
216 # codes such as 'M', 'A', and 'D'.
217 self.changed_paths = { }
219 # The msg will be accumulated later, as log data is read.
220 self.msg = ''
222 def absorb_changed_paths(self, out):
223 'Read changed paths from OUT into self, until no more.'
224 while 1:
225 line = out.readline()
226 if len(line) == 1: return
227 line = line[:-1]
228 op_portion = line[3:4]
229 path_portion = line[5:]
230 # If we're running on Windows we get backslashes instead of
231 # forward slashes.
232 path_portion = path_portion.replace('\\', '/')
233 # # We could parse out history information, but currently we
234 # # just leave it in the path portion because that's how some
235 # # tests expect it.
237 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
238 # if m:
239 # path_portion = m.group(1)
240 self.changed_paths[path_portion] = op_portion
242 def __cmp__(self, other):
243 return cmp(self.revision, other.revision) or \
244 cmp(self.author, other.author) or cmp(self.date, other.date) or \
245 cmp(self.changed_paths, other.changed_paths) or \
246 cmp(self.msg, other.msg)
248 def get_path_op(self, path):
249 """Return the operator for the change involving PATH.
251 PATH is allowed to include string interpolation directives (e.g.,
252 '%(trunk)s'), which are interpolated against self.symbols. Return
253 None if there is no record for PATH."""
254 return self.changed_paths.get(path % self.symbols)
256 def check_msg(self, msg):
257 """Verify that this Log's message starts with the specified MSG."""
258 if self.msg.find(msg) != 0:
259 raise Failure(
260 "Revision %d log message was:\n%s\n\n"
261 "It should have begun with:\n%s\n\n"
262 % (self.revision, self.msg, msg,)
265 def check_change(self, path, op):
266 """Verify that this Log includes a change for PATH with operator OP.
268 PATH is allowed to include string interpolation directives (e.g.,
269 '%(trunk)s'), which are interpolated against self.symbols."""
271 path = path % self.symbols
272 found_op = self.changed_paths.get(path, None)
273 if found_op is None:
274 raise Failure(
275 "Revision %d does not include change for path %s "
276 "(it should have been %s).\n"
277 % (self.revision, path, op,)
279 if found_op != op:
280 raise Failure(
281 "Revision %d path %s had op %s (it should have been %s)\n"
282 % (self.revision, path, found_op, op,)
285 def check_changes(self, changed_paths):
286 """Verify that this Log has precisely the CHANGED_PATHS specified.
288 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
289 strings are allowed to include string interpolation directives
290 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
292 cp = {}
293 for (path, op) in changed_paths:
294 cp[path % self.symbols] = op
296 if self.changed_paths != cp:
297 raise Failure(
298 "Revision %d changed paths list was:\n%s\n\n"
299 "It should have been:\n%s\n\n"
300 % (self.revision, self.changed_paths, cp,)
303 def check(self, msg, changed_paths):
304 """Verify that this Log has the MSG and CHANGED_PATHS specified.
306 Convenience function to check two things at once. MSG is passed
307 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
309 self.check_msg(msg)
310 self.check_changes(changed_paths)
313 def parse_log(svn_repos, symbols):
314 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
316 Initialize the Logs' symbols with SYMBOLS."""
318 class LineFeeder:
319 'Make a list of lines behave like an open file handle.'
320 def __init__(self, lines):
321 self.lines = lines
322 def readline(self):
323 if len(self.lines) > 0:
324 return self.lines.pop(0)
325 else:
326 return None
328 def absorb_message_body(out, num_lines, log):
329 """Read NUM_LINES of log message body from OUT into Log item LOG."""
331 for i in range(num_lines):
332 log.msg += out.readline()
334 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
335 '(?P<author>[^\|]+) \| '
336 '(?P<date>[^\|]+) '
337 '\| (?P<lines>[0-9]+) (line|lines)$')
339 log_separator = '-' * 72
341 logs = { }
343 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
345 while 1:
346 this_log = None
347 line = out.readline()
348 if not line: break
349 line = line[:-1]
351 if line.find(log_separator) == 0:
352 line = out.readline()
353 if not line: break
354 line = line[:-1]
355 m = log_start_re.match(line)
356 if m:
357 this_log = Log(
358 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
359 line = out.readline()
360 if not line.find('Changed paths:') == 0:
361 print 'unexpected log output (missing changed paths)'
362 print "Line: '%s'" % line
363 sys.exit(1)
364 this_log.absorb_changed_paths(out)
365 absorb_message_body(out, int(m.group('lines')), this_log)
366 logs[this_log.revision] = this_log
367 elif len(line) == 0:
368 break # We've reached the end of the log output.
369 else:
370 print 'unexpected log output (missing revision line)'
371 print "Line: '%s'" % line
372 sys.exit(1)
373 else:
374 print 'unexpected log output (missing log separator)'
375 print "Line: '%s'" % line
376 sys.exit(1)
378 return logs
381 def erase(path):
382 """Unconditionally remove PATH and its subtree, if any. PATH may be
383 non-existent, a file or symlink, or a directory."""
384 if os.path.isdir(path):
385 safe_rmtree(path)
386 elif os.path.exists(path):
387 os.remove(path)
390 log_msg_text_wrapper = textwrap.TextWrapper(width=76, break_long_words=False)
392 def sym_log_msg(symbolic_name, is_tag=None):
393 """Return the expected log message for a cvs2svn-synthesized revision
394 creating branch or tag SYMBOLIC_NAME."""
396 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
397 if is_tag:
398 type = 'tag'
399 else:
400 type = 'branch'
402 return log_msg_text_wrapper.fill(
403 "This commit was manufactured by cvs2svn to create %s '%s'."
404 % (type, symbolic_name)
408 def make_conversion_id(
409 name, args, passbypass, options_file=None, symbol_hints_file=None
411 """Create an identifying tag for a conversion.
413 The return value can also be used as part of a filesystem path.
415 NAME is the name of the CVS repository.
417 ARGS are the extra arguments to be passed to cvs2svn.
419 PASSBYPASS is a boolean indicating whether the conversion is to be
420 run one pass at a time.
422 If OPTIONS_FILE is specified, it is an options file that will be
423 used for the conversion.
425 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
426 will be used for the conversion.
428 The 1-to-1 mapping between cvs2svn command parameters and
429 conversion_ids allows us to avoid running the same conversion more
430 than once, when multiple tests use exactly the same conversion."""
432 conv_id = name
434 args = args[:]
436 if passbypass:
437 args.append('--passbypass')
439 if symbol_hints_file is not None:
440 args.append('--symbol-hints=%s' % (symbol_hints_file,))
442 # There are some characters that are forbidden in filenames, and
443 # there is a limit on the total length of a path to a file. So use
444 # a hash of the parameters rather than concatenating the parameters
445 # into a string.
446 if args:
447 conv_id += "-" + md5('\0'.join(args)).hexdigest()
449 # Some options-file based tests rely on knowing the paths to which
450 # the repository should be written, so we handle that option as a
451 # predictable string:
452 if options_file is not None:
453 conv_id += '--options=%s' % (options_file,)
455 return conv_id
458 class Conversion:
459 """A record of a cvs2svn conversion.
461 Fields:
463 conv_id -- the conversion id for this Conversion.
465 name -- a one-word name indicating the involved repositories.
467 dumpfile -- the name of the SVN dumpfile created by the conversion
468 (if the DUMPFILE constructor argument was used); otherwise,
469 None.
471 repos -- the path to the svn repository. Unset if DUMPFILE was
472 specified.
474 logs -- a dictionary of Log instances, as returned by parse_log().
475 Unset if DUMPFILE was specified.
477 symbols -- a dictionary of symbols used for string interpolation
478 in path names.
480 stdout -- a list of lines written by cvs2svn to stdout
482 _wc -- the basename of the svn working copy (within tmp_dir).
483 Unset if DUMPFILE was specified.
485 _wc_path -- the path to the svn working copy, if it has already
486 been created; otherwise, None. (The working copy is created
487 lazily when get_wc() is called.) Unset if DUMPFILE was
488 specified.
490 _wc_tree -- the tree built from the svn working copy, if it has
491 already been created; otherwise, None. The tree is created
492 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
493 specified.
495 _svnrepos -- the basename of the svn repository (within tmp_dir).
496 Unset if DUMPFILE was specified."""
498 # The number of the last cvs2svn pass (determined lazily by
499 # get_last_pass()).
500 last_pass = None
502 @classmethod
503 def get_last_pass(cls):
504 """Return the number of cvs2svn's last pass."""
506 if cls.last_pass is None:
507 out = run_script(cvs2svn, None, '--help-passes')
508 cls.last_pass = int(out[-1].split()[0])
509 return cls.last_pass
511 def __init__(
512 self, conv_id, name, error_re, passbypass, symbols, args,
513 options_file=None, symbol_hints_file=None, dumpfile=None,
515 self.conv_id = conv_id
516 self.name = name
517 self.symbols = symbols
518 if not os.path.isdir(tmp_dir):
519 os.mkdir(tmp_dir)
521 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
523 if dumpfile:
524 self.dumpfile = os.path.join(tmp_dir, dumpfile)
525 # Clean up from any previous invocations of this script.
526 erase(self.dumpfile)
527 else:
528 self.dumpfile = None
529 self.repos = os.path.join(tmp_dir, '%s-svnrepos' % self.conv_id)
530 self._wc = os.path.join(tmp_dir, '%s-wc' % self.conv_id)
531 self._wc_path = None
532 self._wc_tree = None
534 # Clean up from any previous invocations of this script.
535 erase(self.repos)
536 erase(self._wc)
538 args = list(args)
539 args.extend([
540 '--svnadmin=%s' % (svntest.main.svnadmin_binary,),
542 if options_file:
543 self.options_file = os.path.join(cvsrepos, options_file)
544 args.extend([
545 '--options=%s' % self.options_file,
547 assert not symbol_hints_file
548 else:
549 self.options_file = None
550 if tmp_dir != 'cvs2svn-tmp':
551 # Only include this argument if it differs from cvs2svn's default:
552 args.extend([
553 '--tmpdir=%s' % tmp_dir,
556 if symbol_hints_file:
557 self.symbol_hints_file = os.path.join(cvsrepos, symbol_hints_file)
558 args.extend([
559 '--symbol-hints=%s' % self.symbol_hints_file,
562 if self.dumpfile:
563 args.extend(['--dumpfile=%s' % (self.dumpfile,)])
564 else:
565 args.extend(['-s', self.repos])
566 args.extend([cvsrepos])
568 if passbypass:
569 self.stdout = []
570 for p in range(1, self.get_last_pass() + 1):
571 self.stdout += run_script(cvs2svn, error_re, '-p', str(p), *args)
572 else:
573 self.stdout = run_script(cvs2svn, error_re, *args)
575 if self.dumpfile:
576 if not os.path.isfile(self.dumpfile):
577 raise Failure(
578 "Dumpfile not created: '%s'"
579 % os.path.join(os.getcwd(), self.dumpfile)
581 else:
582 if os.path.isdir(self.repos):
583 self.logs = parse_log(self.repos, self.symbols)
584 elif error_re is None:
585 raise Failure(
586 "Repository not created: '%s'"
587 % os.path.join(os.getcwd(), self.repos)
590 def output_found(self, pattern):
591 """Return True if PATTERN matches any line in self.stdout.
593 PATTERN is a regular expression pattern as a string.
596 pattern_re = re.compile(pattern)
598 for line in self.stdout:
599 if pattern_re.match(line):
600 # We found the pattern that we were looking for.
601 return 1
602 else:
603 return 0
605 def find_tag_log(self, tagname):
606 """Search LOGS for a log message containing 'TAGNAME' and return the
607 log in which it was found."""
608 for i in xrange(len(self.logs), 0, -1):
609 if self.logs[i].msg.find("'"+tagname+"'") != -1:
610 return self.logs[i]
611 raise ValueError("Tag %s not found in logs" % tagname)
613 def get_wc(self, *args):
614 """Return the path to the svn working copy, or a path within the WC.
616 If a working copy has not been created yet, create it now.
618 If ARGS are specified, then they should be strings that form
619 fragments of a path within the WC. They are joined using
620 os.path.join() and appended to the WC path."""
622 if self._wc_path is None:
623 run_svn('co', repos_to_url(self.repos), self._wc)
624 self._wc_path = self._wc
625 return os.path.join(self._wc_path, *args)
627 def get_wc_tree(self):
628 if self._wc_tree is None:
629 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
630 return self._wc_tree
632 def path_exists(self, *args):
633 """Return True if the specified path exists within the repository.
635 (The strings in ARGS are first joined into a path using
636 os.path.join().)"""
638 return os.path.exists(self.get_wc(*args))
640 def check_props(self, keys, checks):
641 """Helper function for checking lots of properties. For a list of
642 files in the conversion, check that the values of the properties
643 listed in KEYS agree with those listed in CHECKS. CHECKS is a
644 list of tuples: [ (filename, [value, value, ...]), ...], where the
645 values are listed in the same order as the key names are listed in
646 KEYS."""
648 for (file, values) in checks:
649 assert len(values) == len(keys)
650 props = props_for_path(self.get_wc_tree(), file)
651 for i in range(len(keys)):
652 if props.get(keys[i]) != values[i]:
653 raise Failure(
654 "File %s has property %s set to \"%s\" "
655 "(it should have been \"%s\").\n"
656 % (file, keys[i], props.get(keys[i]), values[i],)
660 class GitConversion:
661 """A record of a cvs2svn conversion.
663 Fields:
665 name -- a one-word name indicating the CVS repository to be converted.
667 stdout -- a list of lines written by cvs2svn to stdout."""
669 def __init__(self, name, error_re, args, options_file=None):
670 self.name = name
671 if not os.path.isdir(tmp_dir):
672 os.mkdir(tmp_dir)
674 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
676 args = list(args)
677 if options_file:
678 self.options_file = os.path.join(cvsrepos, options_file)
679 args.extend([
680 '--options=%s' % self.options_file,
682 else:
683 self.options_file = None
685 self.stdout = run_script(cvs2git, error_re, *args)
688 # Cache of conversions that have already been done. Keys are conv_id;
689 # values are Conversion instances.
690 already_converted = { }
692 def ensure_conversion(
693 name, error_re=None, passbypass=None,
694 trunk=None, branches=None, tags=None,
695 args=None, options_file=None, symbol_hints_file=None, dumpfile=None,
697 """Convert CVS repository NAME to Subversion, but only if it has not
698 been converted before by this invocation of this script. If it has
699 been converted before, return the Conversion object from the
700 previous invocation.
702 If no error, return a Conversion instance.
704 If ERROR_RE is a string, it is a regular expression expected to
705 match some line of stderr printed by the conversion. If there is an
706 error and ERROR_RE is not set, then raise Failure.
708 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
709 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
711 NAME is just one word. For example, 'main' would mean to convert
712 './test-data/main-cvsrepos', and after the conversion, the resulting
713 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
714 a checked out head working copy in './cvs2svn-tmp/main-wc'.
716 Any other options to pass to cvs2svn should be in ARGS, each element
717 being one option, e.g., '--trunk-only'. If the option takes an
718 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
719 are passed to cvs2svn in the order that they appear in ARGS.
721 If OPTIONS_FILE is specified, then it should be the name of a file
722 within the main directory of the cvs repository associated with this
723 test. It is passed to cvs2svn using the --options option (which
724 suppresses some other options that are incompatible with --options).
726 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
727 file within the main directory of the cvs repository associated with
728 this test. It is passed to cvs2svn using the --symbol-hints option.
730 If DUMPFILE is specified, then it is the name of a dumpfile within
731 the temporary directory to which the conversion output should be
732 written."""
734 if args is None:
735 args = []
736 else:
737 args = list(args)
739 if trunk is None:
740 trunk = 'trunk'
741 else:
742 args.append('--trunk=%s' % (trunk,))
744 if branches is None:
745 branches = 'branches'
746 else:
747 args.append('--branches=%s' % (branches,))
749 if tags is None:
750 tags = 'tags'
751 else:
752 args.append('--tags=%s' % (tags,))
754 conv_id = make_conversion_id(
755 name, args, passbypass, options_file, symbol_hints_file
758 if conv_id not in already_converted:
759 try:
760 # Run the conversion and store the result for the rest of this
761 # session:
762 already_converted[conv_id] = Conversion(
763 conv_id, name, error_re, passbypass,
764 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
765 args, options_file, symbol_hints_file, dumpfile,
767 except Failure:
768 # Remember the failure so that a future attempt to run this conversion
769 # does not bother to retry, but fails immediately.
770 already_converted[conv_id] = None
771 raise
773 conv = already_converted[conv_id]
774 if conv is None:
775 raise Failure()
776 return conv
779 class Cvs2SvnTestFunction(TestCase):
780 """A TestCase based on a naked Python function object.
782 FUNC should be a function that returns None on success and throws an
783 svntest.Failure exception on failure. It should have a brief
784 docstring describing what it does (and fulfilling certain
785 conditions). FUNC must take no arguments.
787 This class is almost identical to svntest.testcase.FunctionTestCase,
788 except that the test function does not require a sandbox and does
789 not accept any parameter (not even sandbox=None).
791 This class can be used as an annotation on a Python function.
795 def __init__(self, func):
796 # it better be a function that accepts no parameters and has a
797 # docstring on it.
798 assert isinstance(func, types.FunctionType)
800 name = func.func_name
802 assert func.func_code.co_argcount == 0, \
803 '%s must not take any arguments' % name
805 doc = func.__doc__.strip()
806 assert doc, '%s must have a docstring' % name
808 # enforce stylistic guidelines for the function docstrings:
809 # - no longer than 50 characters
810 # - should not end in a period
811 # - should not be capitalized
812 assert len(doc) <= 50, \
813 "%s's docstring must be 50 characters or less" % name
814 assert doc[-1] != '.', \
815 "%s's docstring should not end in a period" % name
816 assert doc[0].lower() == doc[0], \
817 "%s's docstring should not be capitalized" % name
819 TestCase.__init__(self, doc=doc)
820 self.func = func
822 def get_function_name(self):
823 return self.func.func_name
825 def get_sandbox_name(self):
826 return None
828 def run(self, sandbox):
829 return self.func()
832 class Cvs2HgTestFunction(Cvs2SvnTestFunction):
833 """Same as Cvs2SvnTestFunction, but for test cases that should be
834 skipped if Mercurial is not available.
836 def run(self, sandbox):
837 if not have_hg:
838 raise svntest.Skip()
839 else:
840 return self.func()
843 class Cvs2SvnTestCase(TestCase):
844 def __init__(
845 self, name, doc=None, variant=None,
846 error_re=None, passbypass=None,
847 trunk=None, branches=None, tags=None,
848 args=None,
849 options_file=None, symbol_hints_file=None, dumpfile=None,
851 self.name = name
853 if doc is None:
854 # By default, use the first line of the class docstring as the
855 # doc:
856 doc = self.__doc__.splitlines()[0]
858 if variant is not None:
859 # Modify doc to show the variant. Trim doc first if necessary
860 # to stay within the 50-character limit.
861 suffix = '...variant %s' % (variant,)
862 doc = doc[:50 - len(suffix)] + suffix
864 TestCase.__init__(self, doc=doc)
866 self.error_re = error_re
867 self.passbypass = passbypass
868 self.trunk = trunk
869 self.branches = branches
870 self.tags = tags
871 self.args = args
872 self.options_file = options_file
873 self.symbol_hints_file = symbol_hints_file
874 self.dumpfile = dumpfile
876 def ensure_conversion(self):
877 return ensure_conversion(
878 self.name,
879 error_re=self.error_re, passbypass=self.passbypass,
880 trunk=self.trunk, branches=self.branches, tags=self.tags,
881 args=self.args,
882 options_file=self.options_file,
883 symbol_hints_file=self.symbol_hints_file,
884 dumpfile=self.dumpfile,
887 def get_sandbox_name(self):
888 return None
891 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase):
892 """Test properties resulting from a conversion."""
894 def __init__(self, name, props_to_test, expected_props, **kw):
895 """Initialize an instance of Cvs2SvnPropertiesTestCase.
897 NAME is the name of the test, passed to Cvs2SvnTestCase.
898 PROPS_TO_TEST is a list of the names of svn properties that should
899 be tested. EXPECTED_PROPS is a list of tuples [(filename,
900 [value,...])], where the second item in each tuple is a list of
901 values expected for the properties listed in PROPS_TO_TEST for the
902 specified filename. If a property must *not* be set, then its
903 value should be listed as None."""
905 Cvs2SvnTestCase.__init__(self, name, **kw)
906 self.props_to_test = props_to_test
907 self.expected_props = expected_props
909 def run(self, sbox):
910 conv = self.ensure_conversion()
911 conv.check_props(self.props_to_test, self.expected_props)
914 #----------------------------------------------------------------------
915 # Tests.
916 #----------------------------------------------------------------------
919 @Cvs2SvnTestFunction
920 def show_usage():
921 "cvs2svn with no arguments shows usage"
922 out = run_script(cvs2svn, None)
923 if (len(out) > 2 and out[0].find('ERROR:') == 0
924 and out[1].find('DBM module')):
925 print 'cvs2svn cannot execute due to lack of proper DBM module.'
926 print 'Exiting without running any further tests.'
927 sys.exit(1)
928 if out[0].find('Usage:') < 0:
929 raise Failure('Basic cvs2svn invocation failed.')
932 @Cvs2SvnTestFunction
933 def cvs2svn_manpage():
934 "generate a manpage for cvs2svn"
935 out = run_script(cvs2svn, None, '--man')
938 @Cvs2SvnTestFunction
939 def cvs2git_manpage():
940 "generate a manpage for cvs2git"
941 out = run_script(cvs2git, None, '--man')
944 @Cvs2HgTestFunction
945 def cvs2hg_manpage():
946 "generate a manpage for cvs2hg"
947 out = run_script(cvs2hg, None, '--man')
950 @Cvs2SvnTestFunction
951 def show_help_passes():
952 "cvs2svn --help-passes shows pass information"
953 out = run_script(cvs2svn, None, '--help-passes')
954 if out[0].find('PASSES') < 0:
955 raise Failure('cvs2svn --help-passes failed.')
958 @Cvs2SvnTestFunction
959 def attr_exec():
960 "detection of the executable flag"
961 if sys.platform == 'win32':
962 raise svntest.Skip()
963 conv = ensure_conversion('main')
964 st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec'))
965 if not st.st_mode & stat.S_IXUSR:
966 raise Failure()
969 @Cvs2SvnTestFunction
970 def space_fname():
971 "conversion of filename with a space"
972 conv = ensure_conversion('main')
973 if not conv.path_exists('trunk', 'single-files', 'space fname'):
974 raise Failure()
977 @Cvs2SvnTestFunction
978 def two_quick():
979 "two commits in quick succession"
980 conv = ensure_conversion('main')
981 logs = parse_log(
982 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
983 if len(logs) != 2:
984 raise Failure()
987 class PruneWithCare(Cvs2SvnTestCase):
988 "prune, but never too much"
990 def __init__(self, **kw):
991 Cvs2SvnTestCase.__init__(self, 'main', **kw)
993 def run(self, sbox):
994 # Robert Pluim encountered this lovely one while converting the
995 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
996 # repository (see issue #1302). Step 4 is the doozy:
998 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
999 # revision 2: adds trunk/blah/NEWS
1000 # revision 3: deletes trunk/blah/cookie
1001 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
1002 # revision 5: does nothing
1004 # After fixing cvs2svn, the sequence (correctly) looks like this:
1006 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
1007 # revision 2: adds trunk/blah/NEWS
1008 # revision 3: deletes trunk/blah/cookie
1009 # revision 4: does nothing [because trunk/blah/cookie already deleted]
1010 # revision 5: deletes blah
1012 # The difference is in 4 and 5. In revision 4, it's not correct to
1013 # prune blah/, because NEWS is still in there, so revision 4 does
1014 # nothing now. But when we delete NEWS in 5, that should bubble up
1015 # and prune blah/ instead.
1017 # ### Note that empty revisions like 4 are probably going to become
1018 # ### at least optional, if not banished entirely from cvs2svn's
1019 # ### output. Hmmm, or they may stick around, with an extra
1020 # ### revision property explaining what happened. Need to think
1021 # ### about that. In some sense, it's a bug in Subversion itself,
1022 # ### that such revisions don't show up in 'svn log' output.
1024 # In the test below, 'trunk/full-prune/first' represents
1025 # cookie, and 'trunk/full-prune/second' represents NEWS.
1027 conv = self.ensure_conversion()
1029 # Confirm that revision 4 removes '/trunk/full-prune/first',
1030 # and that revision 6 removes '/trunk/full-prune'.
1032 # Also confirm similar things about '/full-prune-reappear/...',
1033 # which is similar, except that later on it reappears, restored
1034 # from pruneland, because a file gets added to it.
1036 # And finally, a similar thing for '/partial-prune/...', except that
1037 # in its case, a permanent file on the top level prevents the
1038 # pruning from going farther than the subdirectory containing first
1039 # and second.
1041 for path in ('full-prune/first',
1042 'full-prune-reappear/sub/first',
1043 'partial-prune/sub/first'):
1044 conv.logs[5].check_change('/%(trunk)s/' + path, 'D')
1046 for path in ('full-prune',
1047 'full-prune-reappear',
1048 'partial-prune/sub'):
1049 conv.logs[7].check_change('/%(trunk)s/' + path, 'D')
1051 for path in ('full-prune-reappear',
1052 'full-prune-reappear/appears-later'):
1053 conv.logs[33].check_change('/%(trunk)s/' + path, 'A')
1056 @Cvs2SvnTestFunction
1057 def interleaved_commits():
1058 "two interleaved trunk commits, different log msgs"
1059 # See test-data/main-cvsrepos/proj/README.
1060 conv = ensure_conversion('main')
1062 # The initial import.
1063 rev = 26
1064 conv.logs[rev].check('Initial import.', (
1065 ('/%(trunk)s/interleaved', 'A'),
1066 ('/%(trunk)s/interleaved/1', 'A'),
1067 ('/%(trunk)s/interleaved/2', 'A'),
1068 ('/%(trunk)s/interleaved/3', 'A'),
1069 ('/%(trunk)s/interleaved/4', 'A'),
1070 ('/%(trunk)s/interleaved/5', 'A'),
1071 ('/%(trunk)s/interleaved/a', 'A'),
1072 ('/%(trunk)s/interleaved/b', 'A'),
1073 ('/%(trunk)s/interleaved/c', 'A'),
1074 ('/%(trunk)s/interleaved/d', 'A'),
1075 ('/%(trunk)s/interleaved/e', 'A'),
1078 def check_letters(rev):
1079 """Check if REV is the rev where only letters were committed."""
1081 conv.logs[rev].check('Committing letters only.', (
1082 ('/%(trunk)s/interleaved/a', 'M'),
1083 ('/%(trunk)s/interleaved/b', 'M'),
1084 ('/%(trunk)s/interleaved/c', 'M'),
1085 ('/%(trunk)s/interleaved/d', 'M'),
1086 ('/%(trunk)s/interleaved/e', 'M'),
1089 def check_numbers(rev):
1090 """Check if REV is the rev where only numbers were committed."""
1092 conv.logs[rev].check('Committing numbers only.', (
1093 ('/%(trunk)s/interleaved/1', 'M'),
1094 ('/%(trunk)s/interleaved/2', 'M'),
1095 ('/%(trunk)s/interleaved/3', 'M'),
1096 ('/%(trunk)s/interleaved/4', 'M'),
1097 ('/%(trunk)s/interleaved/5', 'M'),
1100 # One of the commits was letters only, the other was numbers only.
1101 # But they happened "simultaneously", so we don't assume anything
1102 # about which commit appeared first, so we just try both ways.
1103 rev += 1
1104 try:
1105 check_letters(rev)
1106 check_numbers(rev + 1)
1107 except Failure:
1108 check_numbers(rev)
1109 check_letters(rev + 1)
1112 @Cvs2SvnTestFunction
1113 def simple_commits():
1114 "simple trunk commits"
1115 # See test-data/main-cvsrepos/proj/README.
1116 conv = ensure_conversion('main')
1118 # The initial import.
1119 conv.logs[13].check('Initial import.', (
1120 ('/%(trunk)s/proj', 'A'),
1121 ('/%(trunk)s/proj/default', 'A'),
1122 ('/%(trunk)s/proj/sub1', 'A'),
1123 ('/%(trunk)s/proj/sub1/default', 'A'),
1124 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1125 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1126 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1127 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1128 ('/%(trunk)s/proj/sub2', 'A'),
1129 ('/%(trunk)s/proj/sub2/default', 'A'),
1130 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1131 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1132 ('/%(trunk)s/proj/sub3', 'A'),
1133 ('/%(trunk)s/proj/sub3/default', 'A'),
1136 # The first commit.
1137 conv.logs[18].check('First commit to proj, affecting two files.', (
1138 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1139 ('/%(trunk)s/proj/sub3/default', 'M'),
1142 # The second commit.
1143 conv.logs[19].check('Second commit to proj, affecting all 7 files.', (
1144 ('/%(trunk)s/proj/default', 'M'),
1145 ('/%(trunk)s/proj/sub1/default', 'M'),
1146 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1147 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1148 ('/%(trunk)s/proj/sub2/default', 'M'),
1149 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1150 ('/%(trunk)s/proj/sub3/default', 'M')
1154 class SimpleTags(Cvs2SvnTestCase):
1155 "simple tags and branches, no commits"
1157 def __init__(self, **kw):
1158 # See test-data/main-cvsrepos/proj/README.
1159 Cvs2SvnTestCase.__init__(self, 'main', **kw)
1161 def run(self, sbox):
1162 conv = self.ensure_conversion()
1164 # Verify the copy source for the tags we are about to check
1165 # No need to verify the copyfrom revision, as simple_commits did that
1166 conv.logs[13].check('Initial import.', (
1167 ('/%(trunk)s/proj', 'A'),
1168 ('/%(trunk)s/proj/default', 'A'),
1169 ('/%(trunk)s/proj/sub1', 'A'),
1170 ('/%(trunk)s/proj/sub1/default', 'A'),
1171 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1172 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1173 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1174 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1175 ('/%(trunk)s/proj/sub2', 'A'),
1176 ('/%(trunk)s/proj/sub2/default', 'A'),
1177 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1178 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1179 ('/%(trunk)s/proj/sub3', 'A'),
1180 ('/%(trunk)s/proj/sub3/default', 'A'),
1183 fromstr = ' (from /%(branches)s/B_FROM_INITIALS:14)'
1185 # Tag on rev 1.1.1.1 of all files in proj
1186 conv.logs[14].check(sym_log_msg('B_FROM_INITIALS'), (
1187 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1188 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1189 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1192 # The same, as a tag
1193 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
1194 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1195 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr, 'A'),
1198 # Tag on rev 1.1.1.1 of all files in proj, except one
1199 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1200 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1201 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr, 'A'),
1202 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1205 # The same, as a branch
1206 conv.logs[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1207 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr, 'A'),
1208 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1212 @Cvs2SvnTestFunction
1213 def simple_branch_commits():
1214 "simple branch commits"
1215 # See test-data/main-cvsrepos/proj/README.
1216 conv = ensure_conversion('main')
1218 conv.logs[23].check('Modify three files, on branch B_MIXED.', (
1219 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1220 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1221 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1225 @Cvs2SvnTestFunction
1226 def mixed_time_tag():
1227 "mixed-time tag"
1228 # See test-data/main-cvsrepos/proj/README.
1229 conv = ensure_conversion('main')
1231 log = conv.find_tag_log('T_MIXED')
1232 log.check_changes((
1233 ('/%(tags)s/T_MIXED (from /%(branches)s/B_MIXED:20)', 'A'),
1237 @Cvs2SvnTestFunction
1238 def mixed_time_branch_with_added_file():
1239 "mixed-time branch, and a file added to the branch"
1240 # See test-data/main-cvsrepos/proj/README.
1241 conv = ensure_conversion('main')
1243 # A branch from the same place as T_MIXED in the previous test,
1244 # plus a file added directly to the branch
1245 conv.logs[20].check(sym_log_msg('B_MIXED'), (
1246 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1247 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1248 ('/%(branches)s/B_MIXED/single-files', 'D'),
1249 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1250 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1251 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1254 conv.logs[22].check('Add a file on branch B_MIXED.', (
1255 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1259 @Cvs2SvnTestFunction
1260 def mixed_commit():
1261 "a commit affecting both trunk and a branch"
1262 # See test-data/main-cvsrepos/proj/README.
1263 conv = ensure_conversion('main')
1265 conv.logs[24].check(
1266 'A single commit affecting one file on branch B_MIXED '
1267 'and one on trunk.', (
1268 ('/%(trunk)s/proj/sub2/default', 'M'),
1269 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1273 @Cvs2SvnTestFunction
1274 def split_time_branch():
1275 "branch some trunk files, and later branch the rest"
1276 # See test-data/main-cvsrepos/proj/README.
1277 conv = ensure_conversion('main')
1279 # First change on the branch, creating it
1280 conv.logs[25].check(sym_log_msg('B_SPLIT'), (
1281 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1282 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1283 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1284 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1287 conv.logs[29].check('First change on branch B_SPLIT.', (
1288 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1289 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1290 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1291 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1292 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1295 # A trunk commit for the file which was not branched
1296 conv.logs[30].check('A trunk change to sub1/subsubB/default. '
1297 'This was committed about an', (
1298 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1301 # Add the file not already branched to the branch, with modification:w
1302 conv.logs[31].check(sym_log_msg('B_SPLIT'), (
1303 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1304 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1307 conv.logs[32].check('This change affects sub3/default and '
1308 'sub1/subsubB/default, on branch', (
1309 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1310 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1314 @Cvs2SvnTestFunction
1315 def multiple_tags():
1316 "multiple tags referring to same revision"
1317 conv = ensure_conversion('main')
1318 if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1319 raise Failure()
1320 if not conv.path_exists(
1321 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1322 raise Failure()
1325 @Cvs2SvnTestFunction
1326 def multiply_defined_symbols():
1327 "multiple definitions of symbol names"
1329 # We can only check one line of the error output at a time, so test
1330 # twice. (The conversion only have to be done once because the
1331 # results are cached.)
1332 conv = ensure_conversion(
1333 'multiply-defined-symbols',
1334 error_re=(
1335 r"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1336 r"1\.2\.4 1\.2\.2"
1339 conv = ensure_conversion(
1340 'multiply-defined-symbols',
1341 error_re=(
1342 r"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1343 r"1\.2 1\.1"
1348 @Cvs2SvnTestFunction
1349 def multiply_defined_symbols_renamed():
1350 "rename multiply defined symbols"
1352 conv = ensure_conversion(
1353 'multiply-defined-symbols',
1354 options_file='cvs2svn-rename.options',
1358 @Cvs2SvnTestFunction
1359 def multiply_defined_symbols_ignored():
1360 "ignore multiply defined symbols"
1362 conv = ensure_conversion(
1363 'multiply-defined-symbols',
1364 options_file='cvs2svn-ignore.options',
1368 @Cvs2SvnTestFunction
1369 def repeatedly_defined_symbols():
1370 "multiple identical definitions of symbol names"
1372 # If a symbol is defined multiple times but has the same value each
1373 # time, that should not be an error.
1375 conv = ensure_conversion('repeatedly-defined-symbols')
1378 @Cvs2SvnTestFunction
1379 def bogus_tag():
1380 "conversion of invalid symbolic names"
1381 conv = ensure_conversion('bogus-tag')
1384 @Cvs2SvnTestFunction
1385 def overlapping_branch():
1386 "ignore a file with a branch with two names"
1387 conv = ensure_conversion('overlapping-branch')
1389 if not conv.output_found('.*cannot also have name \'vendorB\''):
1390 raise Failure()
1392 conv.logs[2].check('imported', (
1393 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1394 ('/%(trunk)s/overlapping-branch', 'A'),
1397 if len(conv.logs) != 2:
1398 raise Failure()
1401 class PhoenixBranch(Cvs2SvnTestCase):
1402 "convert a branch file rooted in a 'dead' revision"
1404 def __init__(self, **kw):
1405 Cvs2SvnTestCase.__init__(self, 'phoenix', **kw)
1407 def run(self, sbox):
1408 conv = self.ensure_conversion()
1409 conv.logs[8].check('This file was supplied by Jack Moffitt', (
1410 ('/%(branches)s/volsung_20010721', 'A'),
1411 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1413 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1414 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1418 ###TODO: We check for 4 changed paths here to accomodate creating tags
1419 ###and branches in rev 1, but that will change, so this will
1420 ###eventually change back.
1421 @Cvs2SvnTestFunction
1422 def ctrl_char_in_log():
1423 "handle a control char in a log message"
1424 # This was issue #1106.
1425 rev = 2
1426 conv = ensure_conversion('ctrl-char-in-log')
1427 conv.logs[rev].check_changes((
1428 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1430 if conv.logs[rev].msg.find('\x04') < 0:
1431 raise Failure(
1432 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1435 @Cvs2SvnTestFunction
1436 def overdead():
1437 "handle tags rooted in a redeleted revision"
1438 conv = ensure_conversion('overdead')
1441 class NoTrunkPrune(Cvs2SvnTestCase):
1442 "ensure that trunk doesn't get pruned"
1444 def __init__(self, **kw):
1445 Cvs2SvnTestCase.__init__(self, 'overdead', **kw)
1447 def run(self, sbox):
1448 conv = self.ensure_conversion()
1449 for rev in conv.logs.keys():
1450 rev_logs = conv.logs[rev]
1451 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1452 raise Failure()
1455 @Cvs2SvnTestFunction
1456 def double_delete():
1457 "file deleted twice, in the root of the repository"
1458 # This really tests several things: how we handle a file that's
1459 # removed (state 'dead') in two successive revisions; how we
1460 # handle a file in the root of the repository (there were some
1461 # bugs in cvs2svn's svn path construction for top-level files); and
1462 # the --no-prune option.
1463 conv = ensure_conversion(
1464 'double-delete', args=['--trunk-only', '--no-prune'])
1466 path = '/%(trunk)s/twice-removed'
1467 rev = 2
1468 conv.logs[rev].check('Updated CVS', (
1469 (path, 'A'),
1471 conv.logs[rev + 1].check('Remove this file for the first time.', (
1472 (path, 'D'),
1474 conv.logs[rev + 2].check('Remove this file for the second time,', (
1478 @Cvs2SvnTestFunction
1479 def split_branch():
1480 "branch created from both trunk and another branch"
1481 # See test-data/split-branch-cvsrepos/README.
1483 # The conversion will fail if the bug is present, and
1484 # ensure_conversion will raise Failure.
1485 conv = ensure_conversion('split-branch')
1488 @Cvs2SvnTestFunction
1489 def resync_misgroups():
1490 "resyncing should not misorder commit groups"
1491 # See test-data/resync-misgroups-cvsrepos/README.
1493 # The conversion will fail if the bug is present, and
1494 # ensure_conversion will raise Failure.
1495 conv = ensure_conversion('resync-misgroups')
1498 class TaggedBranchAndTrunk(Cvs2SvnTestCase):
1499 "allow tags with mixed trunk and branch sources"
1501 def __init__(self, **kw):
1502 Cvs2SvnTestCase.__init__(self, 'tagged-branch-n-trunk', **kw)
1504 def run(self, sbox):
1505 conv = self.ensure_conversion()
1507 tags = conv.symbols.get('tags', 'tags')
1509 a_path = conv.get_wc(tags, 'some-tag', 'a.txt')
1510 b_path = conv.get_wc(tags, 'some-tag', 'b.txt')
1511 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1512 raise Failure()
1513 if (open(a_path, 'r').read().find('1.24') == -1) \
1514 or (open(b_path, 'r').read().find('1.5') == -1):
1515 raise Failure()
1518 @Cvs2SvnTestFunction
1519 def enroot_race():
1520 "never use the rev-in-progress as a copy source"
1522 # See issue #1427 and r8544.
1523 conv = ensure_conversion('enroot-race')
1524 rev = 6
1525 conv.logs[rev].check_changes((
1526 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1527 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1528 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1530 conv.logs[rev + 1].check_changes((
1531 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1532 ('/%(trunk)s/proj/a.txt', 'M'),
1533 ('/%(trunk)s/proj/b.txt', 'M'),
1537 @Cvs2SvnTestFunction
1538 def enroot_race_obo():
1539 "do use the last completed rev as a copy source"
1540 conv = ensure_conversion('enroot-race-obo')
1541 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1542 if not len(conv.logs) == 3:
1543 raise Failure()
1546 class BranchDeleteFirst(Cvs2SvnTestCase):
1547 "correctly handle deletion as initial branch action"
1549 def __init__(self, **kw):
1550 Cvs2SvnTestCase.__init__(self, 'branch-delete-first', **kw)
1552 def run(self, sbox):
1553 # See test-data/branch-delete-first-cvsrepos/README.
1555 # The conversion will fail if the bug is present, and
1556 # ensure_conversion would raise Failure.
1557 conv = self.ensure_conversion()
1559 branches = conv.symbols.get('branches', 'branches')
1561 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1562 if conv.path_exists(branches, 'branch-1', 'file'):
1563 raise Failure()
1564 if conv.path_exists(branches, 'branch-2', 'file'):
1565 raise Failure()
1566 if not conv.path_exists(branches, 'branch-3', 'file'):
1567 raise Failure()
1570 @Cvs2SvnTestFunction
1571 def nonascii_filenames():
1572 "non ascii files converted incorrectly"
1573 # see issue #1255
1575 # on a en_US.iso-8859-1 machine this test fails with
1576 # svn: Can't recode ...
1578 # as described in the issue
1580 # on a en_US.UTF-8 machine this test fails with
1581 # svn: Malformed XML ...
1583 # which means at least it fails. Unfortunately it won't fail
1584 # with the same error...
1586 # mangle current locale settings so we know we're not running
1587 # a UTF-8 locale (which does not exhibit this problem)
1588 current_locale = locale.getlocale()
1589 new_locale = 'en_US.ISO8859-1'
1590 locale_changed = None
1592 # From http://docs.python.org/lib/module-sys.html
1594 # getfilesystemencoding():
1596 # Return the name of the encoding used to convert Unicode filenames
1597 # into system file names, or None if the system default encoding is
1598 # used. The result value depends on the operating system:
1600 # - On Windows 9x, the encoding is ``mbcs''.
1601 # - On Mac OS X, the encoding is ``utf-8''.
1602 # - On Unix, the encoding is the user's preference according to the
1603 # result of nl_langinfo(CODESET), or None if the
1604 # nl_langinfo(CODESET) failed.
1605 # - On Windows NT+, file names are Unicode natively, so no conversion is
1606 # performed.
1608 # So we're going to skip this test on Mac OS X for now.
1609 if sys.platform == "darwin":
1610 raise svntest.Skip()
1612 try:
1613 # change locale to non-UTF-8 locale to generate latin1 names
1614 locale.setlocale(locale.LC_ALL, # this might be too broad?
1615 new_locale)
1616 locale_changed = 1
1617 except locale.Error:
1618 raise svntest.Skip()
1620 try:
1621 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
1622 dstrepos_path = os.path.join(test_data_dir,'non-ascii-cvsrepos')
1623 if not os.path.exists(dstrepos_path):
1624 # create repos from existing main repos
1625 shutil.copytree(srcrepos_path, dstrepos_path)
1626 base_path = os.path.join(dstrepos_path, 'single-files')
1627 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1628 os.path.join(base_path, 'two\366uick,v'))
1629 new_path = os.path.join(dstrepos_path, 'single\366files')
1630 os.rename(base_path, new_path)
1632 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1633 finally:
1634 if locale_changed:
1635 locale.setlocale(locale.LC_ALL, current_locale)
1636 safe_rmtree(dstrepos_path)
1639 class UnicodeTest(Cvs2SvnTestCase):
1640 "metadata contains Unicode"
1642 warning_pattern = r'ERROR\: There were warnings converting .* messages'
1644 def __init__(self, name, warning_expected, **kw):
1645 if warning_expected:
1646 error_re = self.warning_pattern
1647 else:
1648 error_re = None
1650 Cvs2SvnTestCase.__init__(self, name, error_re=error_re, **kw)
1651 self.warning_expected = warning_expected
1653 def run(self, sbox):
1654 try:
1655 # ensure the availability of the "utf_8" encoding:
1656 u'a'.encode('utf_8').decode('utf_8')
1657 except LookupError:
1658 raise svntest.Skip()
1660 self.ensure_conversion()
1663 class UnicodeAuthor(UnicodeTest):
1664 "author name contains Unicode"
1666 def __init__(self, warning_expected, **kw):
1667 UnicodeTest.__init__(self, 'unicode-author', warning_expected, **kw)
1670 class UnicodeLog(UnicodeTest):
1671 "log message contains Unicode"
1673 def __init__(self, warning_expected, **kw):
1674 UnicodeTest.__init__(self, 'unicode-log', warning_expected, **kw)
1677 @Cvs2SvnTestFunction
1678 def vendor_branch_sameness():
1679 "avoid spurious changes for initial revs"
1680 conv = ensure_conversion(
1681 'vendor-branch-sameness', args=['--keep-trivial-imports']
1684 # The following files are in this repository:
1686 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1687 # the same contents, the file's default branch is 1.1.1,
1688 # and both revisions are in state 'Exp'.
1690 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1691 # 1.1 (the addition of a line of text).
1693 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1695 # d.txt: This file was created by 'cvs add' instead of import, so
1696 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1697 # The timestamp on the add is exactly the same as for the
1698 # imports of the other files.
1700 # e.txt: Like a.txt, except that the log message for revision 1.1
1701 # is not the standard import log message.
1703 # (Aside from e.txt, the log messages for the same revisions are the
1704 # same in all files.)
1706 # We expect that only a.txt is recognized as an import whose 1.1
1707 # revision can be omitted. The other files should be added on trunk
1708 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1709 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1710 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1711 # and c.txt should be 'D'eleted.
1713 rev = 2
1714 conv.logs[rev].check('Initial revision', (
1715 ('/%(trunk)s/proj', 'A'),
1716 ('/%(trunk)s/proj/b.txt', 'A'),
1717 ('/%(trunk)s/proj/c.txt', 'A'),
1718 ('/%(trunk)s/proj/d.txt', 'A'),
1721 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1722 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1723 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1726 conv.logs[rev + 2].check('First vendor branch revision.', (
1727 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1728 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1729 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1732 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1733 'to compensate for changes in r4,', (
1734 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1735 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1736 ('/%(trunk)s/proj/c.txt', 'D'),
1739 rev = 7
1740 conv.logs[rev].check('This log message is not the standard', (
1741 ('/%(trunk)s/proj/e.txt', 'A'),
1744 conv.logs[rev + 2].check('First vendor branch revision', (
1745 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1748 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1749 'to compensate for changes in r9,', (
1750 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1754 @Cvs2SvnTestFunction
1755 def vendor_branch_trunk_only():
1756 "handle vendor branches with --trunk-only"
1757 conv = ensure_conversion('vendor-branch-sameness', args=['--trunk-only'])
1759 rev = 2
1760 conv.logs[rev].check('Initial revision', (
1761 ('/%(trunk)s/proj', 'A'),
1762 ('/%(trunk)s/proj/b.txt', 'A'),
1763 ('/%(trunk)s/proj/c.txt', 'A'),
1764 ('/%(trunk)s/proj/d.txt', 'A'),
1767 conv.logs[rev + 1].check('First vendor branch revision', (
1768 ('/%(trunk)s/proj/a.txt', 'A'),
1769 ('/%(trunk)s/proj/b.txt', 'M'),
1770 ('/%(trunk)s/proj/c.txt', 'D'),
1773 conv.logs[rev + 2].check('This log message is not the standard', (
1774 ('/%(trunk)s/proj/e.txt', 'A'),
1777 conv.logs[rev + 3].check('First vendor branch revision', (
1778 ('/%(trunk)s/proj/e.txt', 'M'),
1782 @Cvs2SvnTestFunction
1783 def default_branches():
1784 "handle default branches correctly"
1785 conv = ensure_conversion('default-branches')
1787 # There are seven files in the repository:
1789 # a.txt:
1790 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1791 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1792 # committed (thus losing the default branch "1.1.1"), then
1793 # 1.1.1.4 was imported. All vendor import release tags are
1794 # still present.
1796 # b.txt:
1797 # Like a.txt, but without rev 1.2.
1799 # c.txt:
1800 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1802 # d.txt:
1803 # Same as the previous two, but 1.1.1 branch is unlabeled.
1805 # e.txt:
1806 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1808 # deleted-on-vendor-branch.txt,v:
1809 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1811 # added-then-imported.txt,v:
1812 # Added with 'cvs add' to create 1.1, then imported with
1813 # completely different contents to create 1.1.1.1, therefore
1814 # never had a default branch.
1817 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1818 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1819 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1820 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1821 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1822 ('/%(branches)s/vbranchA', 'A'),
1823 ('/%(branches)s/vbranchA/proj', 'A'),
1824 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1825 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1826 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1827 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1830 conv.logs[3].check("This commit was generated by cvs2svn "
1831 "to compensate for changes in r2,", (
1832 ('/%(trunk)s/proj', 'A'),
1833 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1834 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1835 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1836 ('/%(trunk)s/proj/d.txt '
1837 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1838 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1839 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1840 ('/%(trunk)s/proj/e.txt '
1841 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1844 conv.logs[4].check(sym_log_msg('vtag-1',1), (
1845 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1846 ('/%(tags)s/vtag-1/proj/d.txt '
1847 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1850 conv.logs[5].check("Import (vbranchA, vtag-2).", (
1851 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1852 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1853 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1854 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1855 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1856 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1859 conv.logs[6].check("This commit was generated by cvs2svn "
1860 "to compensate for changes in r5,", (
1861 ('/%(trunk)s/proj/a.txt '
1862 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1863 ('/%(trunk)s/proj/b.txt '
1864 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1865 ('/%(trunk)s/proj/c.txt '
1866 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1867 ('/%(trunk)s/proj/d.txt '
1868 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1869 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1870 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1871 'R'),
1872 ('/%(trunk)s/proj/e.txt '
1873 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1876 conv.logs[7].check(sym_log_msg('vtag-2',1), (
1877 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1878 ('/%(tags)s/vtag-2/proj/d.txt '
1879 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1882 conv.logs[8].check("Import (vbranchA, vtag-3).", (
1883 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1884 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1885 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1886 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1887 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1888 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1891 conv.logs[9].check("This commit was generated by cvs2svn "
1892 "to compensate for changes in r8,", (
1893 ('/%(trunk)s/proj/a.txt '
1894 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1895 ('/%(trunk)s/proj/b.txt '
1896 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1897 ('/%(trunk)s/proj/c.txt '
1898 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1899 ('/%(trunk)s/proj/d.txt '
1900 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1901 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1902 ('/%(trunk)s/proj/e.txt '
1903 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1906 conv.logs[10].check(sym_log_msg('vtag-3',1), (
1907 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1908 ('/%(tags)s/vtag-3/proj/d.txt '
1909 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1910 ('/%(tags)s/vtag-3/proj/e.txt '
1911 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1914 conv.logs[11].check("First regular commit, to a.txt, on vtag-3.", (
1915 ('/%(trunk)s/proj/a.txt', 'M'),
1918 conv.logs[12].check("Add a file to the working copy.", (
1919 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1922 conv.logs[13].check(sym_log_msg('vbranchA'), (
1923 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1924 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1927 conv.logs[14].check("Import (vbranchA, vtag-4).", (
1928 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1929 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1930 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1931 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1932 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1933 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1934 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1937 conv.logs[15].check("This commit was generated by cvs2svn "
1938 "to compensate for changes in r14,", (
1939 ('/%(trunk)s/proj/b.txt '
1940 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1941 ('/%(trunk)s/proj/c.txt '
1942 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1943 ('/%(trunk)s/proj/d.txt '
1944 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1945 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1946 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1947 'A'),
1948 ('/%(trunk)s/proj/e.txt '
1949 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1952 conv.logs[16].check(sym_log_msg('vtag-4',1), (
1953 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1954 ('/%(tags)s/vtag-4/proj/d.txt '
1955 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
1959 @Cvs2SvnTestFunction
1960 def default_branches_trunk_only():
1961 "handle default branches with --trunk-only"
1963 conv = ensure_conversion('default-branches', args=['--trunk-only'])
1965 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1966 ('/%(trunk)s/proj', 'A'),
1967 ('/%(trunk)s/proj/a.txt', 'A'),
1968 ('/%(trunk)s/proj/b.txt', 'A'),
1969 ('/%(trunk)s/proj/c.txt', 'A'),
1970 ('/%(trunk)s/proj/d.txt', 'A'),
1971 ('/%(trunk)s/proj/e.txt', 'A'),
1972 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1975 conv.logs[3].check("Import (vbranchA, vtag-2).", (
1976 ('/%(trunk)s/proj/a.txt', 'M'),
1977 ('/%(trunk)s/proj/b.txt', 'M'),
1978 ('/%(trunk)s/proj/c.txt', 'M'),
1979 ('/%(trunk)s/proj/d.txt', 'M'),
1980 ('/%(trunk)s/proj/e.txt', 'M'),
1981 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
1984 conv.logs[4].check("Import (vbranchA, vtag-3).", (
1985 ('/%(trunk)s/proj/a.txt', 'M'),
1986 ('/%(trunk)s/proj/b.txt', 'M'),
1987 ('/%(trunk)s/proj/c.txt', 'M'),
1988 ('/%(trunk)s/proj/d.txt', 'M'),
1989 ('/%(trunk)s/proj/e.txt', 'M'),
1990 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1993 conv.logs[5].check("First regular commit, to a.txt, on vtag-3.", (
1994 ('/%(trunk)s/proj/a.txt', 'M'),
1997 conv.logs[6].check("Add a file to the working copy.", (
1998 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
2001 conv.logs[7].check("Import (vbranchA, vtag-4).", (
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', 'A'),
2010 @Cvs2SvnTestFunction
2011 def default_branch_and_1_2():
2012 "do not allow 1.2 revision with default branch"
2014 conv = ensure_conversion(
2015 'default-branch-and-1-2',
2016 error_re=(
2017 r'.*File \'.*\' has default branch=1\.1\.1 but also a revision 1\.2'
2022 @Cvs2SvnTestFunction
2023 def compose_tag_three_sources():
2024 "compose a tag from three sources"
2025 conv = ensure_conversion('compose-tag-three-sources')
2027 conv.logs[2].check("Add on trunk", (
2028 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
2029 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
2030 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
2031 ('/%(trunk)s/tagged-on-b1', 'A'),
2032 ('/%(trunk)s/tagged-on-b2', 'A'),
2035 conv.logs[3].check(sym_log_msg('b1'), (
2036 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
2039 conv.logs[4].check(sym_log_msg('b2'), (
2040 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
2043 conv.logs[5].check("Commit on branch b1", (
2044 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
2045 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
2046 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
2047 ('/%(branches)s/b1/tagged-on-b1', 'M'),
2048 ('/%(branches)s/b1/tagged-on-b2', 'M'),
2051 conv.logs[6].check("Commit on branch b2", (
2052 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
2053 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
2054 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
2055 ('/%(branches)s/b2/tagged-on-b1', 'M'),
2056 ('/%(branches)s/b2/tagged-on-b2', 'M'),
2059 conv.logs[7].check("Commit again on trunk", (
2060 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
2061 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
2062 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
2063 ('/%(trunk)s/tagged-on-b1', 'M'),
2064 ('/%(trunk)s/tagged-on-b2', 'M'),
2067 conv.logs[8].check(sym_log_msg('T',1), (
2068 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
2069 ('/%(tags)s/T/tagged-on-trunk-1.1 '
2070 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
2071 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:5)', 'R'),
2072 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:6)', 'R'),
2076 @Cvs2SvnTestFunction
2077 def pass5_when_to_fill():
2078 "reserve a svn revnum for a fill only when required"
2079 # The conversion will fail if the bug is present, and
2080 # ensure_conversion would raise Failure.
2081 conv = ensure_conversion('pass5-when-to-fill')
2084 class EmptyTrunk(Cvs2SvnTestCase):
2085 "don't break when the trunk is empty"
2087 def __init__(self, **kw):
2088 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
2090 def run(self, sbox):
2091 # The conversion will fail if the bug is present, and
2092 # ensure_conversion would raise Failure.
2093 conv = self.ensure_conversion()
2096 @Cvs2SvnTestFunction
2097 def no_spurious_svn_commits():
2098 "ensure that we don't create any spurious commits"
2099 conv = ensure_conversion('phoenix')
2101 # Check spurious commit that could be created in
2102 # SVNCommitCreator._pre_commit()
2104 # (When you add a file on a branch, CVS creates a trunk revision
2105 # in state 'dead'. If the log message of that commit is equal to
2106 # the one that CVS generates, we do not ever create a 'fill'
2107 # SVNCommit for it.)
2109 # and spurious commit that could be created in
2110 # SVNCommitCreator._commit()
2112 # (When you add a file on a branch, CVS creates a trunk revision
2113 # in state 'dead'. If the log message of that commit is equal to
2114 # the one that CVS generates, we do not create a primary SVNCommit
2115 # for it.)
2116 conv.logs[17].check('File added on branch xiphophorus', (
2117 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
2120 # Check to make sure that a commit *is* generated:
2121 # (When you add a file on a branch, CVS creates a trunk revision
2122 # in state 'dead'. If the log message of that commit is NOT equal
2123 # to the one that CVS generates, we create a primary SVNCommit to
2124 # serve as a home for the log message in question.
2125 conv.logs[18].check('file added-on-branch2.txt was initially added on '
2126 + 'branch xiphophorus,\nand this log message was tweaked', ())
2128 # Check spurious commit that could be created in
2129 # SVNCommitCreator._commit_symbols().
2130 conv.logs[19].check('This file was also added on branch xiphophorus,', (
2131 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
2135 class PeerPathPruning(Cvs2SvnTestCase):
2136 "make sure that filling prunes paths correctly"
2138 def __init__(self, **kw):
2139 Cvs2SvnTestCase.__init__(self, 'peer-path-pruning', **kw)
2141 def run(self, sbox):
2142 conv = self.ensure_conversion()
2143 conv.logs[6].check(sym_log_msg('BRANCH'), (
2144 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A'),
2145 ('/%(branches)s/BRANCH/bar', 'D'),
2146 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:5)', 'R'),
2150 @Cvs2SvnTestFunction
2151 def invalid_closings_on_trunk():
2152 "verify correct revs are copied to default branches"
2153 # The conversion will fail if the bug is present, and
2154 # ensure_conversion would raise Failure.
2155 conv = ensure_conversion('invalid-closings-on-trunk')
2158 @Cvs2SvnTestFunction
2159 def individual_passes():
2160 "run each pass individually"
2161 conv = ensure_conversion('main')
2162 conv2 = ensure_conversion('main', passbypass=1)
2164 if conv.logs != conv2.logs:
2165 raise Failure()
2168 @Cvs2SvnTestFunction
2169 def resync_bug():
2170 "reveal a big bug in our resync algorithm"
2171 # This will fail if the bug is present
2172 conv = ensure_conversion('resync-bug')
2175 @Cvs2SvnTestFunction
2176 def branch_from_default_branch():
2177 "reveal a bug in our default branch detection code"
2178 conv = ensure_conversion('branch-from-default-branch')
2180 # This revision will be a default branch synchronization only
2181 # if cvs2svn is correctly determining default branch revisions.
2183 # The bug was that cvs2svn was treating revisions on branches off of
2184 # default branches as default branch revisions, resulting in
2185 # incorrectly regarding the branch off of the default branch as a
2186 # non-trunk default branch. Crystal clear? I thought so. See
2187 # issue #42 for more incoherent blathering.
2188 conv.logs[5].check("This commit was generated by cvs2svn", (
2189 ('/%(trunk)s/proj/file.txt '
2190 '(from /%(branches)s/upstream/proj/file.txt:4)', 'R'),
2194 @Cvs2SvnTestFunction
2195 def file_in_attic_too():
2196 "die if a file exists in and out of the attic"
2197 ensure_conversion(
2198 'file-in-attic-too',
2199 error_re=(
2200 r'.*A CVS repository cannot contain both '
2201 r'(.*)' + re.escape(os.sep) + r'(.*) '
2202 + r'and '
2203 r'\1' + re.escape(os.sep) + r'Attic' + re.escape(os.sep) + r'\2'
2208 @Cvs2SvnTestFunction
2209 def retain_file_in_attic_too():
2210 "test --retain-conflicting-attic-files option"
2211 conv = ensure_conversion(
2212 'file-in-attic-too', args=['--retain-conflicting-attic-files'])
2213 if not conv.path_exists('trunk', 'file.txt'):
2214 raise Failure()
2215 if not conv.path_exists('trunk', 'Attic', 'file.txt'):
2216 raise Failure()
2219 @Cvs2SvnTestFunction
2220 def symbolic_name_filling_guide():
2221 "reveal a big bug in our SymbolFillingGuide"
2222 # This will fail if the bug is present
2223 conv = ensure_conversion('symbolic-name-overfill')
2226 # Helpers for tests involving file contents and properties.
2228 class NodeTreeWalkException:
2229 "Exception class for node tree traversals."
2230 pass
2232 def node_for_path(node, path):
2233 "In the tree rooted under SVNTree NODE, return the node at PATH."
2234 if node.name != '__SVN_ROOT_NODE':
2235 raise NodeTreeWalkException()
2236 path = path.strip('/')
2237 components = path.split('/')
2238 for component in components:
2239 node = svntest.tree.get_child(node, component)
2240 return node
2242 # Helper for tests involving properties.
2243 def props_for_path(node, path):
2244 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2245 return node_for_path(node, path).props
2248 class EOLMime(Cvs2SvnPropertiesTestCase):
2249 """eol settings and mime types together
2251 The files are as follows:
2253 trunk/foo.txt: no -kb, mime file says nothing.
2254 trunk/foo.xml: no -kb, mime file says text.
2255 trunk/foo.zip: no -kb, mime file says non-text.
2256 trunk/foo.bin: has -kb, mime file says nothing.
2257 trunk/foo.csv: has -kb, mime file says text.
2258 trunk/foo.dbf: has -kb, mime file says non-text.
2261 def __init__(self, args, **kw):
2262 # TODO: It's a bit klugey to construct this path here. But so far
2263 # there's only one test with a mime.types file. If we have more,
2264 # we should abstract this into some helper, which would be located
2265 # near ensure_conversion(). Note that it is a convention of this
2266 # test suite for a mime.types file to be located in the top level
2267 # of the CVS repository to which it applies.
2268 self.mime_path = os.path.join(
2269 test_data_dir, 'eol-mime-cvsrepos', 'mime.types')
2271 Cvs2SvnPropertiesTestCase.__init__(
2272 self, 'eol-mime',
2273 props_to_test=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2274 args=['--mime-types=%s' % self.mime_path] + args,
2275 **kw)
2278 # We do four conversions. Each time, we pass --mime-types=FILE with
2279 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2280 # Thus there's one conversion with neither flag, one with just the
2281 # former, one with just the latter, and one with both.
2284 # Neither --no-default-eol nor --eol-from-mime-type:
2285 eol_mime1 = EOLMime(
2286 variant=1,
2287 args=[],
2288 expected_props=[
2289 ('trunk/foo.txt', [None, None, None]),
2290 ('trunk/foo.xml', [None, 'text/xml', None]),
2291 ('trunk/foo.zip', [None, 'application/zip', None]),
2292 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2293 ('trunk/foo.csv', [None, 'text/csv', None]),
2294 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2298 # Just --no-default-eol, not --eol-from-mime-type:
2299 eol_mime2 = EOLMime(
2300 variant=2,
2301 args=['--default-eol=native'],
2302 expected_props=[
2303 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2304 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2305 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS]),
2306 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2307 ('trunk/foo.csv', [None, 'text/csv', None]),
2308 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2312 # Just --eol-from-mime-type, not --no-default-eol:
2313 eol_mime3 = EOLMime(
2314 variant=3,
2315 args=['--eol-from-mime-type'],
2316 expected_props=[
2317 ('trunk/foo.txt', [None, None, None]),
2318 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2319 ('trunk/foo.zip', [None, 'application/zip', None]),
2320 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2321 ('trunk/foo.csv', [None, 'text/csv', None]),
2322 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2326 # Both --no-default-eol and --eol-from-mime-type:
2327 eol_mime4 = EOLMime(
2328 variant=4,
2329 args=['--eol-from-mime-type', '--default-eol=native'],
2330 expected_props=[
2331 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2332 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2333 ('trunk/foo.zip', [None, 'application/zip', None]),
2334 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2335 ('trunk/foo.csv', [None, 'text/csv', None]),
2336 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2340 cvs_revnums_off = Cvs2SvnPropertiesTestCase(
2341 'eol-mime',
2342 doc='test non-setting of cvs2svn:cvs-rev property',
2343 args=[],
2344 props_to_test=['cvs2svn:cvs-rev'],
2345 expected_props=[
2346 ('trunk/foo.txt', [None]),
2347 ('trunk/foo.xml', [None]),
2348 ('trunk/foo.zip', [None]),
2349 ('trunk/foo.bin', [None]),
2350 ('trunk/foo.csv', [None]),
2351 ('trunk/foo.dbf', [None]),
2355 cvs_revnums_on = Cvs2SvnPropertiesTestCase(
2356 'eol-mime',
2357 doc='test setting of cvs2svn:cvs-rev property',
2358 args=['--cvs-revnums'],
2359 props_to_test=['cvs2svn:cvs-rev'],
2360 expected_props=[
2361 ('trunk/foo.txt', ['1.2']),
2362 ('trunk/foo.xml', ['1.2']),
2363 ('trunk/foo.zip', ['1.2']),
2364 ('trunk/foo.bin', ['1.2']),
2365 ('trunk/foo.csv', ['1.2']),
2366 ('trunk/foo.dbf', ['1.2']),
2370 keywords = Cvs2SvnPropertiesTestCase(
2371 'keywords',
2372 doc='test setting of svn:keywords property among others',
2373 args=['--default-eol=native'],
2374 props_to_test=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2375 expected_props=[
2376 ('trunk/foo.default', [KEYWORDS, 'native', None]),
2377 ('trunk/foo.kkvl', [KEYWORDS, 'native', None]),
2378 ('trunk/foo.kkv', [KEYWORDS, 'native', None]),
2379 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2380 ('trunk/foo.kk', [None, 'native', None]),
2381 ('trunk/foo.ko', [None, 'native', None]),
2382 ('trunk/foo.kv', [None, 'native', None]),
2386 @Cvs2SvnTestFunction
2387 def ignore():
2388 "test setting of svn:ignore property"
2389 conv = ensure_conversion('cvsignore')
2390 wc_tree = conv.get_wc_tree()
2391 topdir_props = props_for_path(wc_tree, 'trunk/proj')
2392 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
2394 if topdir_props['svn:ignore'] != \
2395 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2396 raise Failure()
2398 if subdir_props['svn:ignore'] != \
2399 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2400 raise Failure()
2403 @Cvs2SvnTestFunction
2404 def requires_cvs():
2405 "test that CVS can still do what RCS can't"
2406 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2407 conv = ensure_conversion(
2408 'requires-cvs', args=['--use-cvs', '--default-eol=native'],
2411 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
2412 cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read()
2414 if atsign_contents[-1:] == "@":
2415 raise Failure()
2416 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
2417 raise Failure()
2419 if not (conv.logs[6].author == "William Lyon Phelps III" and
2420 conv.logs[5].author == "j random"):
2421 raise Failure()
2424 @Cvs2SvnTestFunction
2425 def questionable_branch_names():
2426 "test that we can handle weird branch names"
2427 conv = ensure_conversion('questionable-symbols')
2428 # If the conversion succeeds, then we're okay. We could check the
2429 # actual branch paths, too, but the main thing is to know that the
2430 # conversion doesn't fail.
2433 @Cvs2SvnTestFunction
2434 def questionable_tag_names():
2435 "test that we can handle weird tag names"
2436 conv = ensure_conversion('questionable-symbols')
2437 conv.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2438 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2440 conv.find_tag_log('TagWith/Backslash_E').check(
2441 sym_log_msg('TagWith/Backslash_E',1),
2443 ('/%(tags)s/TagWith', 'A'),
2444 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2447 conv.find_tag_log('TagWith/Slash_Z').check(
2448 sym_log_msg('TagWith/Slash_Z',1),
2450 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2455 @Cvs2SvnTestFunction
2456 def revision_reorder_bug():
2457 "reveal a bug that reorders file revisions"
2458 conv = ensure_conversion('revision-reorder-bug')
2459 # If the conversion succeeds, then we're okay. We could check the
2460 # actual revisions, too, but the main thing is to know that the
2461 # conversion doesn't fail.
2464 @Cvs2SvnTestFunction
2465 def exclude():
2466 "test that exclude really excludes everything"
2467 conv = ensure_conversion('main', args=['--exclude=.*'])
2468 for log in conv.logs.values():
2469 for item in log.changed_paths.keys():
2470 if item.startswith('/branches/') or item.startswith('/tags/'):
2471 raise Failure()
2474 @Cvs2SvnTestFunction
2475 def vendor_branch_delete_add():
2476 "add trunk file that was deleted on vendor branch"
2477 # This will error if the bug is present
2478 conv = ensure_conversion('vendor-branch-delete-add')
2481 @Cvs2SvnTestFunction
2482 def resync_pass2_pull_forward():
2483 "ensure pass2 doesn't pull rev too far forward"
2484 conv = ensure_conversion('resync-pass2-pull-forward')
2485 # If the conversion succeeds, then we're okay. We could check the
2486 # actual revisions, too, but the main thing is to know that the
2487 # conversion doesn't fail.
2490 @Cvs2SvnTestFunction
2491 def native_eol():
2492 "only LFs for svn:eol-style=native files"
2493 conv = ensure_conversion('native-eol', args=['--default-eol=native'])
2494 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
2495 conv.repos)
2496 # Verify that all files in the dump have LF EOLs. We're actually
2497 # testing the whole dump file, but the dump file itself only uses
2498 # LF EOLs, so we're safe.
2499 for line in lines:
2500 if line[-1] != '\n' or line[:-1].find('\r') != -1:
2501 raise Failure()
2504 @Cvs2SvnTestFunction
2505 def double_fill():
2506 "reveal a bug that created a branch twice"
2507 conv = ensure_conversion('double-fill')
2508 # If the conversion succeeds, then we're okay. We could check the
2509 # actual revisions, too, but the main thing is to know that the
2510 # conversion doesn't fail.
2513 @Cvs2SvnTestFunction
2514 def double_fill2():
2515 "reveal a second bug that created a branch twice"
2516 conv = ensure_conversion('double-fill2')
2517 conv.logs[6].check_msg(sym_log_msg('BRANCH1'))
2518 conv.logs[7].check_msg(sym_log_msg('BRANCH2'))
2519 try:
2520 # This check should fail:
2521 conv.logs[8].check_msg(sym_log_msg('BRANCH2'))
2522 except Failure:
2523 pass
2524 else:
2525 raise Failure('Symbol filled twice in a row')
2528 @Cvs2SvnTestFunction
2529 def resync_pass2_push_backward():
2530 "ensure pass2 doesn't push rev too far backward"
2531 conv = ensure_conversion('resync-pass2-push-backward')
2532 # If the conversion succeeds, then we're okay. We could check the
2533 # actual revisions, too, but the main thing is to know that the
2534 # conversion doesn't fail.
2537 @Cvs2SvnTestFunction
2538 def double_add():
2539 "reveal a bug that added a branch file twice"
2540 conv = ensure_conversion('double-add')
2541 # If the conversion succeeds, then we're okay. We could check the
2542 # actual revisions, too, but the main thing is to know that the
2543 # conversion doesn't fail.
2546 @Cvs2SvnTestFunction
2547 def bogus_branch_copy():
2548 "reveal a bug that copies a branch file wrongly"
2549 conv = ensure_conversion('bogus-branch-copy')
2550 # If the conversion succeeds, then we're okay. We could check the
2551 # actual revisions, too, but the main thing is to know that the
2552 # conversion doesn't fail.
2555 @Cvs2SvnTestFunction
2556 def nested_ttb_directories():
2557 "require error if ttb directories are not disjoint"
2558 opts_list = [
2559 {'trunk' : 'a', 'branches' : 'a',},
2560 {'trunk' : 'a', 'tags' : 'a',},
2561 {'branches' : 'a', 'tags' : 'a',},
2562 # This option conflicts with the default trunk path:
2563 {'branches' : 'trunk',},
2564 # Try some nested directories:
2565 {'trunk' : 'a', 'branches' : 'a/b',},
2566 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2567 {'branches' : 'a', 'tags' : 'a/b',},
2570 for opts in opts_list:
2571 ensure_conversion(
2572 'main', error_re=r'The following paths are not disjoint\:', **opts
2576 class AutoProps(Cvs2SvnPropertiesTestCase):
2577 """Test auto-props.
2579 The files are as follows:
2581 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2582 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2583 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2584 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2585 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2586 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2587 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2588 trunk/foo.UPCASE1: no -kb, no mime type.
2589 trunk/foo.UPCASE2: no -kb, no mime type.
2592 def __init__(self, args, **kw):
2593 ### TODO: It's a bit klugey to construct this path here. See also
2594 ### the comment in eol_mime().
2595 auto_props_path = os.path.join(
2596 test_data_dir, 'eol-mime-cvsrepos', 'auto-props')
2598 Cvs2SvnPropertiesTestCase.__init__(
2599 self, 'eol-mime',
2600 props_to_test=[
2601 'myprop',
2602 'svn:eol-style',
2603 'svn:mime-type',
2604 'svn:keywords',
2605 'svn:executable',
2607 args=[
2608 '--auto-props=%s' % auto_props_path,
2609 '--eol-from-mime-type'
2610 ] + args,
2611 **kw)
2614 auto_props_ignore_case = AutoProps(
2615 doc="test auto-props",
2616 args=['--default-eol=native'],
2617 expected_props=[
2618 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS, None]),
2619 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS, None]),
2620 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2621 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2622 ('trunk/foo.bin',
2623 ['bin', None, 'application/octet-stream', None, '']),
2624 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2625 ('trunk/foo.dbf',
2626 ['dbf', None, 'application/what-is-dbf', None, None]),
2627 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS, None]),
2628 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS, None]),
2632 @Cvs2SvnTestFunction
2633 def ctrl_char_in_filename():
2634 "do not allow control characters in filenames"
2636 try:
2637 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
2638 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
2639 if os.path.exists(dstrepos_path):
2640 safe_rmtree(dstrepos_path)
2642 # create repos from existing main repos
2643 shutil.copytree(srcrepos_path, dstrepos_path)
2644 base_path = os.path.join(dstrepos_path, 'single-files')
2645 try:
2646 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2647 os.path.join(base_path, 'two\rquick,v'))
2648 except:
2649 # Operating systems that don't allow control characters in
2650 # filenames will hopefully have thrown an exception; in that
2651 # case, just skip this test.
2652 raise svntest.Skip()
2654 conv = ensure_conversion(
2655 'ctrl-char-filename',
2656 error_re=(r'.*Subversion does not allow character .*.'),
2658 finally:
2659 safe_rmtree(dstrepos_path)
2662 @Cvs2SvnTestFunction
2663 def commit_dependencies():
2664 "interleaved and multi-branch commits to same files"
2665 conv = ensure_conversion("commit-dependencies")
2666 conv.logs[2].check('adding', (
2667 ('/%(trunk)s/interleaved', 'A'),
2668 ('/%(trunk)s/interleaved/file1', 'A'),
2669 ('/%(trunk)s/interleaved/file2', 'A'),
2671 conv.logs[3].check('big commit', (
2672 ('/%(trunk)s/interleaved/file1', 'M'),
2673 ('/%(trunk)s/interleaved/file2', 'M'),
2675 conv.logs[4].check('dependant small commit', (
2676 ('/%(trunk)s/interleaved/file1', 'M'),
2678 conv.logs[5].check('adding', (
2679 ('/%(trunk)s/multi-branch', 'A'),
2680 ('/%(trunk)s/multi-branch/file1', 'A'),
2681 ('/%(trunk)s/multi-branch/file2', 'A'),
2683 conv.logs[6].check(sym_log_msg("branch"), (
2684 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2685 ('/%(branches)s/branch/interleaved', 'D'),
2687 conv.logs[7].check('multi-branch-commit', (
2688 ('/%(trunk)s/multi-branch/file1', 'M'),
2689 ('/%(trunk)s/multi-branch/file2', 'M'),
2690 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2691 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2695 @Cvs2SvnTestFunction
2696 def double_branch_delete():
2697 "fill branches before modifying files on them"
2698 conv = ensure_conversion('double-branch-delete')
2700 # Test for issue #102. The file IMarshalledValue.java is branched,
2701 # deleted, readded on the branch, and then deleted again. If the
2702 # fill for the file on the branch is postponed until after the
2703 # modification, the file will end up live on the branch instead of
2704 # dead! Make sure it happens at the right time.
2706 conv.logs[6].check('JBAS-2436 - Adding LGPL Header2', (
2707 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2710 conv.logs[7].check('JBAS-3025 - Removing dependency', (
2711 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2715 @Cvs2SvnTestFunction
2716 def symbol_mismatches():
2717 "error for conflicting tag/branch"
2719 ensure_conversion(
2720 'symbol-mess',
2721 args=['--symbol-default=strict'],
2722 error_re=r'.*Problems determining how symbols should be converted',
2726 @Cvs2SvnTestFunction
2727 def overlook_symbol_mismatches():
2728 "overlook conflicting tag/branch when --trunk-only"
2730 # This is a test for issue #85.
2732 ensure_conversion('symbol-mess', args=['--trunk-only'])
2735 @Cvs2SvnTestFunction
2736 def force_symbols():
2737 "force symbols to be tags/branches"
2739 conv = ensure_conversion(
2740 'symbol-mess',
2741 args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2742 if conv.path_exists('tags', 'BRANCH') \
2743 or not conv.path_exists('branches', 'BRANCH'):
2744 raise Failure()
2745 if not conv.path_exists('tags', 'TAG') \
2746 or conv.path_exists('branches', 'TAG'):
2747 raise Failure()
2748 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2749 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2750 raise Failure()
2751 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2752 or conv.path_exists('branches', 'MOSTLY_TAG'):
2753 raise Failure()
2756 @Cvs2SvnTestFunction
2757 def commit_blocks_tags():
2758 "commit prevents forced tag"
2760 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2761 ensure_conversion(
2762 'symbol-mess',
2763 args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']),
2764 error_re=(
2765 r'.*The following branches cannot be forced to be tags '
2766 r'because they have commits'
2771 @Cvs2SvnTestFunction
2772 def blocked_excludes():
2773 "error for blocked excludes"
2775 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2776 for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']:
2777 try:
2778 ensure_conversion(
2779 'symbol-mess',
2780 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker]))
2781 raise MissingErrorException()
2782 except Failure:
2783 pass
2786 @Cvs2SvnTestFunction
2787 def unblock_blocked_excludes():
2788 "excluding blocker removes blockage"
2790 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2791 for blocker in ['BRANCH', 'COMMIT']:
2792 ensure_conversion(
2793 'symbol-mess',
2794 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker,
2795 '--exclude=BLOCKING_%s' % blocker]))
2798 @Cvs2SvnTestFunction
2799 def regexp_force_symbols():
2800 "force symbols via regular expressions"
2802 conv = ensure_conversion(
2803 'symbol-mess',
2804 args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2805 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2806 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2807 raise Failure()
2808 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2809 or conv.path_exists('branches', 'MOSTLY_TAG'):
2810 raise Failure()
2813 @Cvs2SvnTestFunction
2814 def heuristic_symbol_default():
2815 "test 'heuristic' symbol default"
2817 conv = ensure_conversion(
2818 'symbol-mess', args=['--symbol-default=heuristic'])
2819 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2820 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2821 raise Failure()
2822 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2823 or conv.path_exists('branches', 'MOSTLY_TAG'):
2824 raise Failure()
2827 @Cvs2SvnTestFunction
2828 def branch_symbol_default():
2829 "test 'branch' symbol default"
2831 conv = ensure_conversion(
2832 'symbol-mess', args=['--symbol-default=branch'])
2833 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2834 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2835 raise Failure()
2836 if conv.path_exists('tags', 'MOSTLY_TAG') \
2837 or not conv.path_exists('branches', 'MOSTLY_TAG'):
2838 raise Failure()
2841 @Cvs2SvnTestFunction
2842 def tag_symbol_default():
2843 "test 'tag' symbol default"
2845 conv = ensure_conversion(
2846 'symbol-mess', args=['--symbol-default=tag'])
2847 if not conv.path_exists('tags', 'MOSTLY_BRANCH') \
2848 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
2849 raise Failure()
2850 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2851 or conv.path_exists('branches', 'MOSTLY_TAG'):
2852 raise Failure()
2855 @Cvs2SvnTestFunction
2856 def symbol_transform():
2857 "test --symbol-transform"
2859 conv = ensure_conversion(
2860 'symbol-mess',
2861 args=[
2862 '--symbol-default=heuristic',
2863 '--symbol-transform=BRANCH:branch',
2864 '--symbol-transform=TAG:tag',
2865 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2867 if not conv.path_exists('branches', 'branch'):
2868 raise Failure()
2869 if not conv.path_exists('tags', 'tag'):
2870 raise Failure()
2871 if not conv.path_exists('branches', 'MOSTLY.BRANCH'):
2872 raise Failure()
2873 if not conv.path_exists('tags', 'MOSTLY.TAG'):
2874 raise Failure()
2877 @Cvs2SvnTestFunction
2878 def write_symbol_info():
2879 "test --write-symbol-info"
2881 expected_lines = [
2882 ['0', '.trunk.',
2883 'trunk', 'trunk', '.'],
2884 ['0', 'BLOCKED_BY_UNNAMED',
2885 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2886 ['0', 'BLOCKING_COMMIT',
2887 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2888 ['0', 'BLOCKED_BY_COMMIT',
2889 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2890 ['0', 'BLOCKING_BRANCH',
2891 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2892 ['0', 'BLOCKED_BY_BRANCH',
2893 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2894 ['0', 'MOSTLY_BRANCH',
2895 '.', '.', '.'],
2896 ['0', 'MOSTLY_TAG',
2897 '.', '.', '.'],
2898 ['0', 'BRANCH_WITH_COMMIT',
2899 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2900 ['0', 'BRANCH',
2901 'branch', 'branches/BRANCH', '.trunk.'],
2902 ['0', 'TAG',
2903 'tag', 'tags/TAG', '.trunk.'],
2904 ['0', 'unlabeled-1.1.12.1.2',
2905 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2907 expected_lines.sort()
2909 symbol_info_file = os.path.join(tmp_dir, 'symbol-mess-symbol-info.txt')
2910 try:
2911 ensure_conversion(
2912 'symbol-mess',
2913 args=[
2914 '--symbol-default=strict',
2915 '--write-symbol-info=%s' % (symbol_info_file,),
2916 '--passes=:CollateSymbolsPass',
2919 raise MissingErrorException()
2920 except Failure:
2921 pass
2922 lines = []
2923 comment_re = re.compile(r'^\s*\#')
2924 for l in open(symbol_info_file, 'r'):
2925 if comment_re.match(l):
2926 continue
2927 lines.append(l.strip().split())
2928 lines.sort()
2929 if lines != expected_lines:
2930 s = ['Symbol info incorrect\n']
2931 differ = Differ()
2932 for diffline in differ.compare(
2933 [' '.join(line) + '\n' for line in expected_lines],
2934 [' '.join(line) + '\n' for line in lines],
2936 s.append(diffline)
2937 raise Failure(''.join(s))
2940 @Cvs2SvnTestFunction
2941 def symbol_hints():
2942 "test --symbol-hints for setting branch/tag"
2944 conv = ensure_conversion(
2945 'symbol-mess', symbol_hints_file='symbol-mess-symbol-hints.txt',
2947 if not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2948 raise Failure()
2949 if not conv.path_exists('tags', 'MOSTLY_TAG'):
2950 raise Failure()
2951 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2952 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2954 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2955 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
2957 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2958 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
2962 @Cvs2SvnTestFunction
2963 def parent_hints():
2964 "test --symbol-hints for setting parent"
2966 conv = ensure_conversion(
2967 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints.txt',
2969 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2970 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
2974 @Cvs2SvnTestFunction
2975 def parent_hints_invalid():
2976 "test --symbol-hints with an invalid parent"
2978 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2979 # this symbol hints file sets the preferred parent to BRANCH
2980 # instead:
2981 conv = ensure_conversion(
2982 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-invalid.txt',
2983 error_re=(
2984 r"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
2989 @Cvs2SvnTestFunction
2990 def parent_hints_wildcards():
2991 "test --symbol-hints wildcards"
2993 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2994 # this symbol hints file sets the preferred parent to BRANCH
2995 # instead:
2996 conv = ensure_conversion(
2997 'symbol-mess',
2998 symbol_hints_file='symbol-mess-parent-hints-wildcards.txt',
3000 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3001 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3005 @Cvs2SvnTestFunction
3006 def path_hints():
3007 "test --symbol-hints for setting svn paths"
3009 conv = ensure_conversion(
3010 'symbol-mess', symbol_hints_file='symbol-mess-path-hints.txt',
3012 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3013 ('/trunk', 'A'),
3014 ('/a', 'A'),
3015 ('/a/strange', 'A'),
3016 ('/a/strange/trunk', 'A'),
3017 ('/a/strange/trunk/path', 'A'),
3018 ('/branches', 'A'),
3019 ('/tags', 'A'),
3021 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3022 ('/special', 'A'),
3023 ('/special/tag', 'A'),
3024 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
3026 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3027 ('/special/other', 'A'),
3028 ('/special/other/branch', 'A'),
3029 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
3031 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3032 ('/special/branch', 'A'),
3033 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
3037 @Cvs2SvnTestFunction
3038 def issue_99():
3039 "test problem from issue 99"
3041 conv = ensure_conversion('issue-99')
3044 @Cvs2SvnTestFunction
3045 def issue_100():
3046 "test problem from issue 100"
3048 conv = ensure_conversion('issue-100')
3049 file1 = conv.get_wc('trunk', 'file1.txt')
3050 if file(file1).read() != 'file1.txt<1.2>\n':
3051 raise Failure()
3054 @Cvs2SvnTestFunction
3055 def issue_106():
3056 "test problem from issue 106"
3058 conv = ensure_conversion('issue-106')
3061 @Cvs2SvnTestFunction
3062 def options_option():
3063 "use of the --options option"
3065 conv = ensure_conversion('main', options_file='cvs2svn.options')
3068 @Cvs2SvnTestFunction
3069 def multiproject():
3070 "multiproject conversion"
3072 conv = ensure_conversion(
3073 'main', options_file='cvs2svn-multiproject.options'
3075 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3076 ('/partial-prune', 'A'),
3077 ('/partial-prune/trunk', 'A'),
3078 ('/partial-prune/branches', 'A'),
3079 ('/partial-prune/tags', 'A'),
3080 ('/partial-prune/releases', 'A'),
3084 @Cvs2SvnTestFunction
3085 def crossproject():
3086 "multiproject conversion with cross-project commits"
3088 conv = ensure_conversion(
3089 'main', options_file='cvs2svn-crossproject.options'
3093 @Cvs2SvnTestFunction
3094 def tag_with_no_revision():
3095 "tag defined but revision is deleted"
3097 conv = ensure_conversion('tag-with-no-revision')
3100 @Cvs2SvnTestFunction
3101 def delete_cvsignore():
3102 "svn:ignore should vanish when .cvsignore does"
3104 # This is issue #81.
3106 conv = ensure_conversion('delete-cvsignore')
3108 wc_tree = conv.get_wc_tree()
3109 props = props_for_path(wc_tree, 'trunk/proj')
3111 if props.has_key('svn:ignore'):
3112 raise Failure()
3115 @Cvs2SvnTestFunction
3116 def repeated_deltatext():
3117 "ignore repeated deltatext blocks with warning"
3119 conv = ensure_conversion('repeated-deltatext')
3120 warning_re = r'.*Deltatext block for revision 1.1 appeared twice'
3121 if not conv.output_found(warning_re):
3122 raise Failure()
3125 @Cvs2SvnTestFunction
3126 def nasty_graphs():
3127 "process some nasty dependency graphs"
3129 # It's not how well the bear can dance, but that the bear can dance
3130 # at all:
3131 conv = ensure_conversion('nasty-graphs')
3134 @Cvs2SvnTestFunction
3135 def tagging_after_delete():
3136 "optimal tag after deleting files"
3138 conv = ensure_conversion('tagging-after-delete')
3140 # tag should be 'clean', no deletes
3141 log = conv.find_tag_log('tag1')
3142 expected = (
3143 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
3145 log.check_changes(expected)
3148 @Cvs2SvnTestFunction
3149 def crossed_branches():
3150 "branches created in inconsistent orders"
3152 conv = ensure_conversion('crossed-branches')
3155 @Cvs2SvnTestFunction
3156 def file_directory_conflict():
3157 "error when filename conflicts with directory name"
3159 conv = ensure_conversion(
3160 'file-directory-conflict',
3161 error_re=r'.*Directory name conflicts with filename',
3165 @Cvs2SvnTestFunction
3166 def attic_directory_conflict():
3167 "error when attic filename conflicts with dirname"
3169 # This tests the problem reported in issue #105.
3171 conv = ensure_conversion(
3172 'attic-directory-conflict',
3173 error_re=r'.*Directory name conflicts with filename',
3177 @Cvs2SvnTestFunction
3178 def use_rcs():
3179 "verify that --use-rcs and --use-internal-co agree"
3181 rcs_conv = ensure_conversion(
3182 'main', args=['--use-rcs', '--default-eol=native'],
3184 conv = ensure_conversion(
3185 'main', args=['--default-eol=native'],
3187 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3188 raise Failure()
3189 rcs_lines = run_program(
3190 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3191 rcs_conv.repos)
3192 lines = run_program(
3193 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3194 conv.repos)
3195 # Compare all lines following the repository UUID:
3196 if lines[3:] != rcs_lines[3:]:
3197 raise Failure()
3200 @Cvs2SvnTestFunction
3201 def internal_co_exclude():
3202 "verify that --use-internal-co --exclude=... works"
3204 rcs_conv = ensure_conversion(
3205 'internal-co',
3206 args=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3208 conv = ensure_conversion(
3209 'internal-co',
3210 args=['--exclude=BRANCH', '--default-eol=native'],
3212 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3213 raise Failure()
3214 rcs_lines = run_program(
3215 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3216 rcs_conv.repos)
3217 lines = run_program(
3218 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3219 conv.repos)
3220 # Compare all lines following the repository UUID:
3221 if lines[3:] != rcs_lines[3:]:
3222 raise Failure()
3225 @Cvs2SvnTestFunction
3226 def internal_co_trunk_only():
3227 "verify that --use-internal-co --trunk-only works"
3229 conv = ensure_conversion(
3230 'internal-co',
3231 args=['--trunk-only', '--default-eol=native'],
3233 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3234 raise Failure()
3237 @Cvs2SvnTestFunction
3238 def leftover_revs():
3239 "check for leftover checked-out revisions"
3241 conv = ensure_conversion(
3242 'leftover-revs',
3243 args=['--exclude=BRANCH', '--default-eol=native'],
3245 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3246 raise Failure()
3249 @Cvs2SvnTestFunction
3250 def requires_internal_co():
3251 "test that internal co can do more than RCS"
3252 # See issues 4, 11 for the bugs whose regression we're testing for.
3253 # Unlike in requires_cvs above, issue 29 is not covered.
3254 conv = ensure_conversion('requires-cvs')
3256 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
3258 if atsign_contents[-1:] == "@":
3259 raise Failure()
3261 if not (conv.logs[6].author == "William Lyon Phelps III" and
3262 conv.logs[5].author == "j random"):
3263 raise Failure()
3266 @Cvs2SvnTestFunction
3267 def internal_co_keywords():
3268 "test that internal co handles keywords correctly"
3269 conv_ic = ensure_conversion('internal-co-keywords',
3270 args=["--keywords-off"])
3271 conv_cvs = ensure_conversion('internal-co-keywords',
3272 args=["--use-cvs", "--keywords-off"])
3274 ko_ic = file(conv_ic.get_wc('trunk', 'dir', 'ko.txt')).read()
3275 ko_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'ko.txt')).read()
3276 kk_ic = file(conv_ic.get_wc('trunk', 'dir', 'kk.txt')).read()
3277 kk_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kk.txt')).read()
3278 kv_ic = file(conv_ic.get_wc('trunk', 'dir', 'kv.txt')).read()
3279 kv_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kv.txt')).read()
3281 if ko_ic != ko_cvs:
3282 raise Failure()
3283 if kk_ic != kk_cvs:
3284 raise Failure()
3286 # The date format changed between cvs and co ('/' instead of '-').
3287 # Accept either one:
3288 date_substitution_re = re.compile(r' ([0-9]*)-([0-9]*)-([0-9]*) ')
3289 if kv_ic != kv_cvs \
3290 and date_substitution_re.sub(r' \1/\2/\3 ', kv_ic) != kv_cvs:
3291 raise Failure()
3294 @Cvs2SvnTestFunction
3295 def timestamp_chaos():
3296 "test timestamp adjustments"
3298 conv = ensure_conversion('timestamp-chaos', args=["-v"])
3300 # The times are expressed here in UTC:
3301 times = [
3302 '2007-01-01 21:00:00', # Initial commit
3303 '2007-01-01 21:00:00', # revision 1.1 of both files
3304 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3305 '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards
3306 '2007-01-01 22:00:00', # revision 1.3 of both files
3309 # Convert the times to seconds since the epoch, in UTC:
3310 times = [calendar.timegm(svn_strptime(t)) for t in times]
3312 for i in range(len(times)):
3313 if abs(conv.logs[i + 1].date - times[i]) > 0.1:
3314 raise Failure()
3317 @Cvs2SvnTestFunction
3318 def symlinks():
3319 "convert a repository that contains symlinks"
3321 # This is a test for issue #97.
3323 proj = os.path.join(test_data_dir, 'symlinks-cvsrepos', 'proj')
3324 links = [
3326 os.path.join('..', 'file.txt,v'),
3327 os.path.join(proj, 'dir1', 'file.txt,v'),
3330 'dir1',
3331 os.path.join(proj, 'dir2'),
3335 try:
3336 os.symlink
3337 except AttributeError:
3338 # Apparently this OS doesn't support symlinks, so skip test.
3339 raise svntest.Skip()
3341 try:
3342 for (src,dst) in links:
3343 os.symlink(src, dst)
3345 conv = ensure_conversion('symlinks')
3346 conv.logs[2].check('', (
3347 ('/%(trunk)s/proj', 'A'),
3348 ('/%(trunk)s/proj/file.txt', 'A'),
3349 ('/%(trunk)s/proj/dir1', 'A'),
3350 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3351 ('/%(trunk)s/proj/dir2', 'A'),
3352 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3354 finally:
3355 for (src,dst) in links:
3356 os.remove(dst)
3359 @Cvs2SvnTestFunction
3360 def empty_trunk_path():
3361 "allow --trunk to be empty if --trunk-only"
3363 # This is a test for issue #53.
3365 conv = ensure_conversion(
3366 'main', args=['--trunk-only', '--trunk='],
3370 @Cvs2SvnTestFunction
3371 def preferred_parent_cycle():
3372 "handle a cycle in branch parent preferences"
3374 conv = ensure_conversion('preferred-parent-cycle')
3377 @Cvs2SvnTestFunction
3378 def branch_from_empty_dir():
3379 "branch from an empty directory"
3381 conv = ensure_conversion('branch-from-empty-dir')
3384 @Cvs2SvnTestFunction
3385 def trunk_readd():
3386 "add a file on a branch then on trunk"
3388 conv = ensure_conversion('trunk-readd')
3391 @Cvs2SvnTestFunction
3392 def branch_from_deleted_1_1():
3393 "branch from a 1.1 revision that will be deleted"
3395 conv = ensure_conversion('branch-from-deleted-1-1')
3396 conv.logs[5].check('Adding b.txt:1.1.2.1', (
3397 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3399 conv.logs[6].check('Adding b.txt:1.1.4.1', (
3400 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3402 conv.logs[7].check('Adding b.txt:1.2', (
3403 ('/%(trunk)s/proj/b.txt', 'A'),
3406 conv.logs[8].check('Adding c.txt:1.1.2.1', (
3407 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3409 conv.logs[9].check('Adding c.txt:1.1.4.1', (
3410 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3414 @Cvs2SvnTestFunction
3415 def add_on_branch():
3416 "add a file on a branch using newer CVS"
3418 conv = ensure_conversion('add-on-branch')
3419 conv.logs[6].check('Adding b.txt:1.1', (
3420 ('/%(trunk)s/proj/b.txt', 'A'),
3422 conv.logs[7].check('Adding b.txt:1.1.2.2', (
3423 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3425 conv.logs[8].check('Adding c.txt:1.1', (
3426 ('/%(trunk)s/proj/c.txt', 'A'),
3428 conv.logs[9].check('Removing c.txt:1.2', (
3429 ('/%(trunk)s/proj/c.txt', 'D'),
3431 conv.logs[10].check('Adding c.txt:1.2.2.2', (
3432 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3434 conv.logs[11].check('Adding d.txt:1.1', (
3435 ('/%(trunk)s/proj/d.txt', 'A'),
3437 conv.logs[12].check('Adding d.txt:1.1.2.2', (
3438 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3442 @Cvs2SvnTestFunction
3443 def main_git():
3444 "test output in git-fast-import format"
3446 # Note: To test importing into git, do
3448 # ./run-tests <test-number>
3449 # rm -rf .git
3450 # git-init
3451 # cat cvs2svn-tmp/{blobfile,dumpfile}.out | git fast-import
3453 # Or, to load the dumpfiles separately:
3455 # cat cvs2svn-tmp/git-blob.dat \
3456 # | git fast-import --export-marks=cvs2svn-tmp/git-marks.dat
3457 # cat cvs2svn-tmp/git-dump.dat \
3458 # | git fast-import --import-marks=cvs2svn-tmp/git-marks.dat
3460 # Then use "gitk --all", "git log", etc. to test the contents of the
3461 # repository.
3463 # We don't have the infrastructure to check that the resulting git
3464 # repository is correct, so we just check that the conversion runs
3465 # to completion:
3466 conv = GitConversion('main', None, [
3467 '--blobfile=cvs2svn-tmp/blobfile.out',
3468 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3469 '--username=cvs2git',
3470 'test-data/main-cvsrepos',
3474 @Cvs2SvnTestFunction
3475 def main_git2():
3476 "test cvs2git --use-external-blob-generator option"
3478 # See comment in main_git() for more information.
3480 conv = GitConversion('main', None, [
3481 '--use-external-blob-generator',
3482 '--blobfile=cvs2svn-tmp/blobfile.out',
3483 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3484 '--username=cvs2git',
3485 'test-data/main-cvsrepos',
3489 @Cvs2SvnTestFunction
3490 def git_options():
3491 "test cvs2git using options file"
3493 conv = GitConversion('main', None, [], options_file='cvs2git.options')
3496 @Cvs2SvnTestFunction
3497 def main_hg():
3498 "output in git-fast-import format with inline data"
3500 # The output should be suitable for import by Mercurial.
3502 # We don't have the infrastructure to check that the resulting
3503 # Mercurial repository is correct, so we just check that the
3504 # conversion runs to completion:
3505 conv = GitConversion('main', None, [], options_file='cvs2hg.options')
3508 @Cvs2SvnTestFunction
3509 def invalid_symbol():
3510 "a symbol with the incorrect format"
3512 conv = ensure_conversion('invalid-symbol')
3513 if not conv.output_found(
3514 r".*branch 'SYMBOL' references invalid revision 1$"
3516 raise Failure()
3519 @Cvs2SvnTestFunction
3520 def invalid_symbol_ignore():
3521 "ignore a symbol using a SymbolMapper"
3523 conv = ensure_conversion(
3524 'invalid-symbol', options_file='cvs2svn-ignore.options'
3528 @Cvs2SvnTestFunction
3529 def invalid_symbol_ignore2():
3530 "ignore a symbol using an IgnoreSymbolTransform"
3532 conv = ensure_conversion(
3533 'invalid-symbol', options_file='cvs2svn-ignore2.options'
3537 class EOLVariants(Cvs2SvnTestCase):
3538 "handle various --eol-style options"
3540 eol_style_strings = {
3541 'LF' : '\n',
3542 'CR' : '\r',
3543 'CRLF' : '\r\n',
3544 'native' : '\n',
3547 def __init__(self, eol_style):
3548 self.eol_style = eol_style
3549 self.dumpfile = 'eol-variants-%s.dump' % (self.eol_style,)
3550 Cvs2SvnTestCase.__init__(
3551 self, 'eol-variants', variant=self.eol_style,
3552 dumpfile=self.dumpfile,
3553 args=[
3554 '--default-eol=%s' % (self.eol_style,),
3558 def run(self, sbox):
3559 conv = self.ensure_conversion()
3560 dump_contents = open(conv.dumpfile, 'rb').read()
3561 expected_text = self.eol_style_strings[self.eol_style].join(
3562 ['line 1', 'line 2', '\n\n']
3564 if not dump_contents.endswith(expected_text):
3565 raise Failure()
3568 @Cvs2SvnTestFunction
3569 def no_revs_file():
3570 "handle a file with no revisions (issue #80)"
3572 conv = ensure_conversion('no-revs-file')
3575 @Cvs2SvnTestFunction
3576 def mirror_keyerror_test():
3577 "a case that gave KeyError in SVNRepositoryMirror"
3579 conv = ensure_conversion('mirror-keyerror')
3582 @Cvs2SvnTestFunction
3583 def exclude_ntdb_test():
3584 "exclude a non-trunk default branch"
3586 symbol_info_file = os.path.join(tmp_dir, 'exclude-ntdb-symbol-info.txt')
3587 conv = ensure_conversion(
3588 'exclude-ntdb',
3589 args=[
3590 '--write-symbol-info=%s' % (symbol_info_file,),
3591 '--exclude=branch3',
3592 '--exclude=tag3',
3593 '--exclude=vendortag3',
3594 '--exclude=vendorbranch',
3599 @Cvs2SvnTestFunction
3600 def mirror_keyerror2_test():
3601 "a case that gave KeyError in RepositoryMirror"
3603 conv = ensure_conversion('mirror-keyerror2')
3606 @Cvs2SvnTestFunction
3607 def mirror_keyerror3_test():
3608 "a case that gave KeyError in RepositoryMirror"
3610 conv = ensure_conversion('mirror-keyerror3')
3613 @Cvs2SvnTestFunction
3614 def add_cvsignore_to_branch_test():
3615 "check adding .cvsignore to an existing branch"
3617 # This a test for issue #122.
3619 conv = ensure_conversion('add-cvsignore-to-branch')
3620 wc_tree = conv.get_wc_tree()
3621 trunk_props = props_for_path(wc_tree, 'trunk/dir')
3622 if trunk_props['svn:ignore'] != '*.o\n\n':
3623 raise Failure()
3625 branch_props = props_for_path(wc_tree, 'branches/BRANCH/dir')
3626 if branch_props['svn:ignore'] != '*.o\n\n':
3627 raise Failure()
3630 @Cvs2SvnTestFunction
3631 def missing_deltatext():
3632 "a revision's deltatext is missing"
3634 # This is a type of RCS file corruption that has been observed.
3635 conv = ensure_conversion(
3636 'missing-deltatext',
3637 error_re=(
3638 r"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3643 @Cvs2SvnTestFunction
3644 def transform_unlabeled_branch_name():
3645 "transform name of unlabeled branch"
3647 conv = ensure_conversion(
3648 'unlabeled-branch',
3649 args=[
3650 '--symbol-transform=unlabeled-1.1.4:BRANCH2',
3653 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3654 raise Failure('Branch unlabeled-1.1.4 not excluded')
3655 if not conv.path_exists('branches', 'BRANCH2'):
3656 raise Failure('Branch BRANCH2 not found')
3659 @Cvs2SvnTestFunction
3660 def ignore_unlabeled_branch():
3661 "ignoring an unlabeled branch is not allowed"
3663 conv = ensure_conversion(
3664 'unlabeled-branch',
3665 options_file='cvs2svn-ignore.options',
3666 error_re=(
3667 r"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' "
3668 r"in \'.*\' contains commits"
3673 @Cvs2SvnTestFunction
3674 def exclude_unlabeled_branch():
3675 "exclude unlabeled branch"
3677 conv = ensure_conversion(
3678 'unlabeled-branch',
3679 args=['--exclude=unlabeled-.*'],
3681 if conv.path_exists('branches', 'unlabeled-1.1.4'):
3682 raise Failure('Branch unlabeled-1.1.4 not excluded')
3685 @Cvs2SvnTestFunction
3686 def unlabeled_branch_name_collision():
3687 "transform unlabeled branch to same name as branch"
3689 conv = ensure_conversion(
3690 'unlabeled-branch',
3691 args=[
3692 '--symbol-transform=unlabeled-1.1.4:BRANCH',
3694 error_re=(
3695 r"ERROR\: Symbol name \'BRANCH\' is already used"
3700 @Cvs2SvnTestFunction
3701 def collision_with_unlabeled_branch_name():
3702 "transform branch to same name as unlabeled branch"
3704 conv = ensure_conversion(
3705 'unlabeled-branch',
3706 args=[
3707 '--symbol-transform=BRANCH:unlabeled-1.1.4',
3709 error_re=(
3710 r"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used"
3715 @Cvs2SvnTestFunction
3716 def many_deletes():
3717 "a repo with many removable dead revisions"
3719 conv = ensure_conversion('many-deletes')
3720 conv.logs[5].check('Add files on BRANCH', (
3721 ('/%(branches)s/BRANCH/proj/b.txt', 'A'),
3723 conv.logs[6].check('Add files on BRANCH2', (
3724 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3725 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3726 ('/%(branches)s/BRANCH2/proj/d.txt', 'A'),
3730 cvs_description = Cvs2SvnPropertiesTestCase(
3731 'main',
3732 doc='test handling of CVS file descriptions',
3733 props_to_test=['cvs:description'],
3734 expected_props=[
3735 ('trunk/proj/default', ['This is an example file description.']),
3736 ('trunk/proj/sub1/default', [None]),
3740 @Cvs2SvnTestFunction
3741 def include_empty_directories():
3742 "test --include-empty-directories option"
3744 conv = ensure_conversion(
3745 'empty-directories', args=['--include-empty-directories'],
3747 conv.logs[1].check('Standard project directories', (
3748 ('/%(trunk)s', 'A'),
3749 ('/%(branches)s', 'A'),
3750 ('/%(tags)s', 'A'),
3751 ('/%(trunk)s/root-empty-directory', 'A'),
3752 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3754 conv.logs[3].check('Add b.txt.', (
3755 ('/%(trunk)s/direct', 'A'),
3756 ('/%(trunk)s/direct/b.txt', 'A'),
3757 ('/%(trunk)s/direct/empty-directory', 'A'),
3758 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3760 conv.logs[4].check('Add c.txt.', (
3761 ('/%(trunk)s/indirect', 'A'),
3762 ('/%(trunk)s/indirect/subdirectory', 'A'),
3763 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3764 ('/%(trunk)s/indirect/empty-directory', 'A'),
3765 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3767 conv.logs[5].check('Remove b.txt', (
3768 ('/%(trunk)s/direct', 'D'),
3770 conv.logs[6].check('Remove c.txt', (
3771 ('/%(trunk)s/indirect', 'D'),
3773 conv.logs[7].check('Re-add b.txt.', (
3774 ('/%(trunk)s/direct', 'A'),
3775 ('/%(trunk)s/direct/b.txt', 'A'),
3776 ('/%(trunk)s/direct/empty-directory', 'A'),
3777 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3779 conv.logs[8].check('Re-add c.txt.', (
3780 ('/%(trunk)s/indirect', 'A'),
3781 ('/%(trunk)s/indirect/subdirectory', 'A'),
3782 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3783 ('/%(trunk)s/indirect/empty-directory', 'A'),
3784 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3786 conv.logs[9].check('This commit was manufactured', (
3787 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3789 conv.logs[10].check('This commit was manufactured', (
3790 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3792 conv.logs[11].check('Import d.txt.', (
3793 ('/%(branches)s/VENDORBRANCH', 'A'),
3794 ('/%(branches)s/VENDORBRANCH/import', 'A'),
3795 ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'),
3796 ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'),
3797 ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory',
3798 'A'),
3799 ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'),
3800 ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory',
3801 'A'),
3803 conv.logs[12].check('This commit was generated', (
3804 ('/%(trunk)s/import', 'A'),
3805 ('/%(trunk)s/import/d.txt '
3806 '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'),
3807 ('/%(trunk)s/import/empty-directory', 'A'),
3808 ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'),
3812 @Cvs2SvnTestFunction
3813 def include_empty_directories_no_prune():
3814 "test --include-empty-directories with --no-prune"
3816 conv = ensure_conversion(
3817 'empty-directories', args=['--include-empty-directories', '--no-prune'],
3819 conv.logs[1].check('Standard project directories', (
3820 ('/%(trunk)s', 'A'),
3821 ('/%(branches)s', 'A'),
3822 ('/%(tags)s', 'A'),
3823 ('/%(trunk)s/root-empty-directory', 'A'),
3824 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3826 conv.logs[3].check('Add b.txt.', (
3827 ('/%(trunk)s/direct', 'A'),
3828 ('/%(trunk)s/direct/b.txt', 'A'),
3829 ('/%(trunk)s/direct/empty-directory', 'A'),
3830 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3832 conv.logs[4].check('Add c.txt.', (
3833 ('/%(trunk)s/indirect', 'A'),
3834 ('/%(trunk)s/indirect/subdirectory', 'A'),
3835 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3836 ('/%(trunk)s/indirect/empty-directory', 'A'),
3837 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3839 conv.logs[5].check('Remove b.txt', (
3840 ('/%(trunk)s/direct/b.txt', 'D'),
3842 conv.logs[6].check('Remove c.txt', (
3843 ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'),
3845 conv.logs[7].check('Re-add b.txt.', (
3846 ('/%(trunk)s/direct/b.txt', 'A'),
3848 conv.logs[8].check('Re-add c.txt.', (
3849 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3851 conv.logs[9].check('This commit was manufactured', (
3852 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3854 conv.logs[10].check('This commit was manufactured', (
3855 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3859 @Cvs2SvnTestFunction
3860 def exclude_symbol_default():
3861 "test 'exclude' symbol default"
3863 conv = ensure_conversion(
3864 'symbol-mess', args=['--symbol-default=exclude'])
3865 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
3866 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
3867 raise Failure()
3868 if conv.path_exists('tags', 'MOSTLY_TAG') \
3869 or conv.path_exists('branches', 'MOSTLY_TAG'):
3870 raise Failure()
3873 @Cvs2SvnTestFunction
3874 def add_on_branch2():
3875 "another add-on-branch test case"
3877 conv = ensure_conversion('add-on-branch2')
3878 if len(conv.logs) != 2:
3879 raise Failure()
3880 conv.logs[2].check('add file on branch', (
3881 ('/%(branches)s/BRANCH', 'A'),
3882 ('/%(branches)s/BRANCH/file1', 'A'),
3886 @Cvs2SvnTestFunction
3887 def branch_from_vendor_branch():
3888 "branch from vendor branch"
3890 ensure_conversion(
3891 'branch-from-vendor-branch',
3892 symbol_hints_file='branch-from-vendor-branch-symbol-hints.txt',
3896 @Cvs2SvnTestFunction
3897 def strange_default_branch():
3898 "default branch too deep in the hierarchy"
3900 ensure_conversion(
3901 'strange-default-branch',
3902 error_re=(
3903 r'ERROR\: The default branch 1\.2\.4\.3\.2\.1\.2 '
3904 r'in file .* is not a top-level branch'
3909 @Cvs2SvnTestFunction
3910 def move_parent():
3911 "graft onto preferred parent that was itself moved"
3913 conv = ensure_conversion(
3914 'move-parent',
3916 conv.logs[2].check('first', (
3917 ('/%(trunk)s/file1', 'A'),
3918 ('/%(trunk)s/file2', 'A'),
3920 conv.logs[3].check('This commit was manufactured', (
3921 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
3923 conv.logs[4].check('second', (
3924 ('/%(branches)s/b2/file1', 'M'),
3926 conv.logs[5].check('This commit was manufactured', (
3927 ('/%(branches)s/b1 (from /%(branches)s/b2:4)', 'A'),
3930 # b2 and b1 are equally good parents for b3, so accept either one.
3931 # (Currently, cvs2svn chooses b1 as the preferred parent because it
3932 # comes earlier than b2 in alphabetical order.)
3933 try:
3934 conv.logs[6].check('This commit was manufactured', (
3935 ('/%(branches)s/b3 (from /%(branches)s/b1:5)', 'A'),
3937 except Failure:
3938 conv.logs[6].check('This commit was manufactured', (
3939 ('/%(branches)s/b3 (from /%(branches)s/b2:4)', 'A'),
3943 ########################################################################
3944 # Run the tests
3946 # list all tests here, starting with None:
3947 test_list = [
3948 None,
3949 # 1:
3950 show_usage,
3951 cvs2svn_manpage,
3952 cvs2git_manpage,
3953 XFail(cvs2hg_manpage),
3954 attr_exec,
3955 space_fname,
3956 two_quick,
3957 PruneWithCare(),
3958 PruneWithCare(variant=1, trunk='a', branches='b', tags='c'),
3959 # 10:
3960 PruneWithCare(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3961 PruneWithCare(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3962 interleaved_commits,
3963 simple_commits,
3964 SimpleTags(),
3965 SimpleTags(variant=1, trunk='a', branches='b', tags='c'),
3966 SimpleTags(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3967 SimpleTags(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3968 simple_branch_commits,
3969 mixed_time_tag,
3970 # 20:
3971 mixed_time_branch_with_added_file,
3972 mixed_commit,
3973 split_time_branch,
3974 bogus_tag,
3975 overlapping_branch,
3976 PhoenixBranch(),
3977 PhoenixBranch(variant=1, trunk='a/1', branches='b/1', tags='c/1'),
3978 ctrl_char_in_log,
3979 overdead,
3980 NoTrunkPrune(),
3981 # 30:
3982 NoTrunkPrune(variant=1, trunk='a', branches='b', tags='c'),
3983 NoTrunkPrune(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3984 NoTrunkPrune(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3985 double_delete,
3986 split_branch,
3987 resync_misgroups,
3988 TaggedBranchAndTrunk(),
3989 TaggedBranchAndTrunk(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3990 enroot_race,
3991 enroot_race_obo,
3992 # 40:
3993 BranchDeleteFirst(),
3994 BranchDeleteFirst(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3995 nonascii_filenames,
3996 UnicodeAuthor(
3997 warning_expected=1),
3998 UnicodeAuthor(
3999 warning_expected=0,
4000 variant='encoding', args=['--encoding=utf_8']),
4001 UnicodeAuthor(
4002 warning_expected=0,
4003 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
4004 UnicodeLog(
4005 warning_expected=1),
4006 UnicodeLog(
4007 warning_expected=0,
4008 variant='encoding', args=['--encoding=utf_8']),
4009 UnicodeLog(
4010 warning_expected=0,
4011 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
4012 vendor_branch_sameness,
4013 # 50:
4014 vendor_branch_trunk_only,
4015 default_branches,
4016 default_branches_trunk_only,
4017 default_branch_and_1_2,
4018 compose_tag_three_sources,
4019 pass5_when_to_fill,
4020 PeerPathPruning(),
4021 PeerPathPruning(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
4022 EmptyTrunk(),
4023 EmptyTrunk(variant=1, trunk='a', branches='b', tags='c'),
4024 # 60:
4025 EmptyTrunk(variant=2, trunk='a/1', branches='a/2', tags='a/3'),
4026 no_spurious_svn_commits,
4027 invalid_closings_on_trunk,
4028 individual_passes,
4029 resync_bug,
4030 branch_from_default_branch,
4031 file_in_attic_too,
4032 retain_file_in_attic_too,
4033 symbolic_name_filling_guide,
4034 eol_mime1,
4035 # 70:
4036 eol_mime2,
4037 eol_mime3,
4038 eol_mime4,
4039 cvs_revnums_off,
4040 cvs_revnums_on,
4041 keywords,
4042 ignore,
4043 requires_cvs,
4044 questionable_branch_names,
4045 questionable_tag_names,
4046 # 80:
4047 revision_reorder_bug,
4048 exclude,
4049 vendor_branch_delete_add,
4050 resync_pass2_pull_forward,
4051 native_eol,
4052 double_fill,
4053 XFail(double_fill2),
4054 resync_pass2_push_backward,
4055 double_add,
4056 bogus_branch_copy,
4057 # 90:
4058 nested_ttb_directories,
4059 auto_props_ignore_case,
4060 ctrl_char_in_filename,
4061 commit_dependencies,
4062 show_help_passes,
4063 multiple_tags,
4064 multiply_defined_symbols,
4065 multiply_defined_symbols_renamed,
4066 multiply_defined_symbols_ignored,
4067 repeatedly_defined_symbols,
4068 # 100:
4069 double_branch_delete,
4070 symbol_mismatches,
4071 overlook_symbol_mismatches,
4072 force_symbols,
4073 commit_blocks_tags,
4074 blocked_excludes,
4075 unblock_blocked_excludes,
4076 regexp_force_symbols,
4077 heuristic_symbol_default,
4078 branch_symbol_default,
4079 # 110:
4080 tag_symbol_default,
4081 symbol_transform,
4082 write_symbol_info,
4083 symbol_hints,
4084 parent_hints,
4085 parent_hints_invalid,
4086 parent_hints_wildcards,
4087 path_hints,
4088 issue_99,
4089 issue_100,
4090 # 120:
4091 issue_106,
4092 options_option,
4093 multiproject,
4094 crossproject,
4095 tag_with_no_revision,
4096 delete_cvsignore,
4097 repeated_deltatext,
4098 nasty_graphs,
4099 XFail(tagging_after_delete),
4100 crossed_branches,
4101 # 130:
4102 file_directory_conflict,
4103 attic_directory_conflict,
4104 use_rcs,
4105 internal_co_exclude,
4106 internal_co_trunk_only,
4107 internal_co_keywords,
4108 leftover_revs,
4109 requires_internal_co,
4110 timestamp_chaos,
4111 symlinks,
4112 # 140:
4113 empty_trunk_path,
4114 preferred_parent_cycle,
4115 branch_from_empty_dir,
4116 trunk_readd,
4117 branch_from_deleted_1_1,
4118 add_on_branch,
4119 main_git,
4120 main_git2,
4121 git_options,
4122 main_hg,
4123 # 150:
4124 invalid_symbol,
4125 invalid_symbol_ignore,
4126 invalid_symbol_ignore2,
4127 EOLVariants('LF'),
4128 EOLVariants('CR'),
4129 EOLVariants('CRLF'),
4130 EOLVariants('native'),
4131 no_revs_file,
4132 mirror_keyerror_test,
4133 exclude_ntdb_test,
4134 # 160:
4135 mirror_keyerror2_test,
4136 mirror_keyerror3_test,
4137 XFail(add_cvsignore_to_branch_test),
4138 missing_deltatext,
4139 transform_unlabeled_branch_name,
4140 ignore_unlabeled_branch,
4141 exclude_unlabeled_branch,
4142 unlabeled_branch_name_collision,
4143 collision_with_unlabeled_branch_name,
4144 many_deletes,
4145 # 170:
4146 cvs_description,
4147 include_empty_directories,
4148 include_empty_directories_no_prune,
4149 exclude_symbol_default,
4150 add_on_branch2,
4151 XFail(branch_from_vendor_branch),
4152 strange_default_branch,
4153 move_parent,
4156 if __name__ == '__main__':
4158 # Configure the environment for reproducable output from svn, etc.
4159 os.environ["LC_ALL"] = "C"
4161 # Unfortunately, there is no way under Windows to make Subversion
4162 # think that the local time zone is UTC, so we just work in the
4163 # local time zone.
4165 # The Subversion test suite code assumes it's being invoked from
4166 # within a working copy of the Subversion sources, and tries to use
4167 # the binaries in that tree. Since the cvs2svn tree never contains
4168 # a Subversion build, we just use the system's installed binaries.
4169 svntest.main.svn_binary = svn_binary
4170 svntest.main.svnlook_binary = svnlook_binary
4171 svntest.main.svnadmin_binary = svnadmin_binary
4172 svntest.main.svnversion_binary = svnversion_binary
4174 svntest.main.run_tests(test_list)
4175 # NOTREACHED
4178 ### End of file.