Handle --dumpfile via optparse.
[cvs2svn.git] / run-tests.py
blob3908d1fac2a58531d3c4cbb900a8cbdddb35533a
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-2007 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 from difflib import Differ
48 # Make sure that a supported version of Python is being used:
49 if not (0x02040000 <= sys.hexversion < 0x03000000):
50 sys.stderr.write(
51 'error: Python 2, version 2.4 or higher required.\n'
53 sys.exit(1)
55 # This script needs to run in the correct directory. Make sure we're there.
56 if not (os.path.exists('cvs2svn') and os.path.exists('test-data')):
57 sys.stderr.write("error: I need to be run in the directory containing "
58 "'cvs2svn' and 'test-data'.\n")
59 sys.exit(1)
61 # Load the Subversion test framework.
62 import svntest
63 from svntest import Failure
64 from svntest.main import run_command
65 from svntest.main import run_tests
66 from svntest.main import safe_rmtree
67 from svntest.testcase import TestCase
68 from svntest.testcase import Skip
69 from svntest.testcase import XFail
70 from svntest.tree import build_tree_from_wc
71 from svntest.tree import get_child
73 cvs2svn = os.path.abspath('cvs2svn')
75 # We use the installed svn and svnlook binaries, instead of using
76 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
77 # behavior -- or even existence -- of local builds shouldn't affect
78 # the cvs2svn test suite.
79 svn = 'svn'
80 svnlook = 'svnlook'
82 test_data_dir = 'test-data'
83 tmp_dir = 'cvs2svn-tmp'
86 #----------------------------------------------------------------------
87 # Helpers.
88 #----------------------------------------------------------------------
91 # The value to expect for svn:keywords if it is set:
92 KEYWORDS = 'Author Date Id Revision'
95 class RunProgramException(Failure):
96 pass
99 class MissingErrorException(Failure):
100 def __init__(self, error_re):
101 Failure.__init__(
102 self, "Test failed because no error matched '%s'" % (error_re,)
106 def run_program(program, error_re, *varargs):
107 """Run PROGRAM with VARARGS, return stdout as a list of lines.
109 If there is any stderr and ERROR_RE is None, raise
110 RunProgramException, and print the stderr lines if
111 svntest.main.verbose_mode is true.
113 If ERROR_RE is not None, it is a string regular expression that must
114 match some line of stderr. If it fails to match, raise
115 MissingErrorExpection."""
117 # FIXME: exit_code is currently ignored.
118 exit_code, out, err = run_command(program, 1, 0, *varargs)
120 if error_re:
121 # Specified error expected on stderr.
122 if not err:
123 raise MissingErrorException(error_re)
124 else:
125 for line in err:
126 if re.match(error_re, line):
127 return out
128 raise MissingErrorException(error_re)
129 else:
130 # No stderr allowed.
131 if err:
132 if svntest.main.verbose_mode:
133 print '\n%s said:\n' % program
134 for line in err:
135 print ' ' + line,
136 print
137 raise RunProgramException()
139 return out
142 def run_cvs2svn(error_re, *varargs):
143 """Run cvs2svn with VARARGS, return stdout as a list of lines.
145 If there is any stderr and ERROR_RE is None, raise
146 RunProgramException, and print the stderr lines if
147 svntest.main.verbose_mode is true.
149 If ERROR_RE is not None, it is a string regular expression that must
150 match some line of stderr. If it fails to match, raise
151 MissingErrorException."""
153 # Use the same python that is running this script
154 return run_program(sys.executable, error_re, cvs2svn, *varargs)
155 # On Windows, for an unknown reason, the cmd.exe process invoked by
156 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
157 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
158 # this. Therefore, the redirection of the output to the .s-revs file fails.
159 # We no longer use the problematic invocation on any system, but this
160 # comment remains to warn about this problem.
163 def run_svn(*varargs):
164 """Run svn with VARARGS; return stdout as a list of lines.
165 If there is any stderr, raise RunProgramException, and print the
166 stderr lines if svntest.main.verbose_mode is true."""
167 return run_program(svn, None, *varargs)
170 def repos_to_url(path_to_svn_repos):
171 """This does what you think it does."""
172 rpath = os.path.abspath(path_to_svn_repos)
173 if rpath[0] != '/':
174 rpath = '/' + rpath
175 return 'file://%s' % rpath.replace(os.sep, '/')
178 def svn_strptime(timestr):
179 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
182 class Log:
183 def __init__(self, revision, author, date, symbols):
184 self.revision = revision
185 self.author = author
187 # Internally, we represent the date as seconds since epoch (UTC).
188 # Since standard subversion log output shows dates in localtime
190 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
192 # and time.mktime() converts from localtime, it all works out very
193 # happily.
194 self.date = time.mktime(svn_strptime(date[0:19]))
196 # The following symbols are used for string interpolation when
197 # checking paths:
198 self.symbols = symbols
200 # The changed paths will be accumulated later, as log data is read.
201 # Keys here are paths such as '/trunk/foo/bar', values are letter
202 # codes such as 'M', 'A', and 'D'.
203 self.changed_paths = { }
205 # The msg will be accumulated later, as log data is read.
206 self.msg = ''
208 def absorb_changed_paths(self, out):
209 'Read changed paths from OUT into self, until no more.'
210 while 1:
211 line = out.readline()
212 if len(line) == 1: return
213 line = line[:-1]
214 op_portion = line[3:4]
215 path_portion = line[5:]
216 # If we're running on Windows we get backslashes instead of
217 # forward slashes.
218 path_portion = path_portion.replace('\\', '/')
219 # # We could parse out history information, but currently we
220 # # just leave it in the path portion because that's how some
221 # # tests expect it.
223 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
224 # if m:
225 # path_portion = m.group(1)
226 self.changed_paths[path_portion] = op_portion
228 def __cmp__(self, other):
229 return cmp(self.revision, other.revision) or \
230 cmp(self.author, other.author) or cmp(self.date, other.date) or \
231 cmp(self.changed_paths, other.changed_paths) or \
232 cmp(self.msg, other.msg)
234 def get_path_op(self, path):
235 """Return the operator for the change involving PATH.
237 PATH is allowed to include string interpolation directives (e.g.,
238 '%(trunk)s'), which are interpolated against self.symbols. Return
239 None if there is no record for PATH."""
240 return self.changed_paths.get(path % self.symbols)
242 def check_msg(self, msg):
243 """Verify that this Log's message starts with the specified MSG."""
244 if self.msg.find(msg) != 0:
245 raise Failure(
246 "Revision %d log message was:\n%s\n\n"
247 "It should have begun with:\n%s\n\n"
248 % (self.revision, self.msg, msg,)
251 def check_change(self, path, op):
252 """Verify that this Log includes a change for PATH with operator OP.
254 PATH is allowed to include string interpolation directives (e.g.,
255 '%(trunk)s'), which are interpolated against self.symbols."""
257 path = path % self.symbols
258 found_op = self.changed_paths.get(path, None)
259 if found_op is None:
260 raise Failure(
261 "Revision %d does not include change for path %s "
262 "(it should have been %s).\n"
263 % (self.revision, path, op,)
265 if found_op != op:
266 raise Failure(
267 "Revision %d path %s had op %s (it should have been %s)\n"
268 % (self.revision, path, found_op, op,)
271 def check_changes(self, changed_paths):
272 """Verify that this Log has precisely the CHANGED_PATHS specified.
274 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
275 strings are allowed to include string interpolation directives
276 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
278 cp = {}
279 for (path, op) in changed_paths:
280 cp[path % self.symbols] = op
282 if self.changed_paths != cp:
283 raise Failure(
284 "Revision %d changed paths list was:\n%s\n\n"
285 "It should have been:\n%s\n\n"
286 % (self.revision, self.changed_paths, cp,)
289 def check(self, msg, changed_paths):
290 """Verify that this Log has the MSG and CHANGED_PATHS specified.
292 Convenience function to check two things at once. MSG is passed
293 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
295 self.check_msg(msg)
296 self.check_changes(changed_paths)
299 def parse_log(svn_repos, symbols):
300 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
302 Initialize the Logs' symbols with SYMBOLS."""
304 class LineFeeder:
305 'Make a list of lines behave like an open file handle.'
306 def __init__(self, lines):
307 self.lines = lines
308 def readline(self):
309 if len(self.lines) > 0:
310 return self.lines.pop(0)
311 else:
312 return None
314 def absorb_message_body(out, num_lines, log):
315 """Read NUM_LINES of log message body from OUT into Log item LOG."""
317 for i in range(num_lines):
318 log.msg += out.readline()
320 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
321 '(?P<author>[^\|]+) \| '
322 '(?P<date>[^\|]+) '
323 '\| (?P<lines>[0-9]+) (line|lines)$')
325 log_separator = '-' * 72
327 logs = { }
329 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
331 while 1:
332 this_log = None
333 line = out.readline()
334 if not line: break
335 line = line[:-1]
337 if line.find(log_separator) == 0:
338 line = out.readline()
339 if not line: break
340 line = line[:-1]
341 m = log_start_re.match(line)
342 if m:
343 this_log = Log(
344 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
345 line = out.readline()
346 if not line.find('Changed paths:') == 0:
347 print 'unexpected log output (missing changed paths)'
348 print "Line: '%s'" % line
349 sys.exit(1)
350 this_log.absorb_changed_paths(out)
351 absorb_message_body(out, int(m.group('lines')), this_log)
352 logs[this_log.revision] = this_log
353 elif len(line) == 0:
354 break # We've reached the end of the log output.
355 else:
356 print 'unexpected log output (missing revision line)'
357 print "Line: '%s'" % line
358 sys.exit(1)
359 else:
360 print 'unexpected log output (missing log separator)'
361 print "Line: '%s'" % line
362 sys.exit(1)
364 return logs
367 def erase(path):
368 """Unconditionally remove PATH and its subtree, if any. PATH may be
369 non-existent, a file or symlink, or a directory."""
370 if os.path.isdir(path):
371 safe_rmtree(path)
372 elif os.path.exists(path):
373 os.remove(path)
376 log_msg_text_wrapper = textwrap.TextWrapper(width=76)
378 def sym_log_msg(symbolic_name, is_tag=None):
379 """Return the expected log message for a cvs2svn-synthesized revision
380 creating branch or tag SYMBOLIC_NAME."""
382 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
383 if is_tag:
384 type = 'tag'
385 else:
386 type = 'branch'
388 return log_msg_text_wrapper.fill(
389 "This commit was manufactured by cvs2svn to create %s '%s'."
390 % (type, symbolic_name)
394 def make_conversion_id(
395 name, args, passbypass, options_file=None, symbol_hints_file=None
397 """Create an identifying tag for a conversion.
399 The return value can also be used as part of a filesystem path.
401 NAME is the name of the CVS repository.
403 ARGS are the extra arguments to be passed to cvs2svn.
405 PASSBYPASS is a boolean indicating whether the conversion is to be
406 run one pass at a time.
408 If OPTIONS_FILE is specified, it is an options file that will be
409 used for the conversion.
411 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
412 will be used for the conversion.
414 The 1-to-1 mapping between cvs2svn command parameters and
415 conversion_ids allows us to avoid running the same conversion more
416 than once, when multiple tests use exactly the same conversion."""
418 conv_id = name
420 _win32_fname_mapping = { '/': '_sl_', '\\': '_bs_', ':': '_co_',
421 '*': '_st_', '?': '_qm_', '"': '_qq_',
422 '<': '_lt_', '>': '_gt_', '|': '_pi_', }
423 for arg in args:
424 # Replace some characters that Win32 isn't happy about having in a
425 # filename (which was causing the eol_mime test to fail).
426 sanitized_arg = arg
427 for a, b in _win32_fname_mapping.items():
428 sanitized_arg = sanitized_arg.replace(a, b)
429 conv_id += sanitized_arg
431 if passbypass:
432 conv_id += '-passbypass'
434 if options_file is not None:
435 conv_id += '--options=%s' % options_file
437 if symbol_hints_file is not None:
438 conv_id += '--symbol-hints=%s' % symbol_hints_file
440 return conv_id
443 class Conversion:
444 """A record of a cvs2svn conversion.
446 Fields:
448 conv_id -- the conversion id for this Conversion.
450 name -- a one-word name indicating the involved repositories.
452 dumpfile -- the name of the SVN dumpfile created by the conversion
453 (if the DUMPFILE constructor argument was used); otherwise,
454 None.
456 repos -- the path to the svn repository. Unset if DUMPFILE was
457 specified.
459 logs -- a dictionary of Log instances, as returned by parse_log().
460 Unset if DUMPFILE was specified.
462 symbols -- a dictionary of symbols used for string interpolation
463 in path names.
465 stdout -- a list of lines written by cvs2svn to stdout
467 _wc -- the basename of the svn working copy (within tmp_dir).
468 Unset if DUMPFILE was specified.
470 _wc_path -- the path to the svn working copy, if it has already
471 been created; otherwise, None. (The working copy is created
472 lazily when get_wc() is called.) Unset if DUMPFILE was
473 specified.
475 _wc_tree -- the tree built from the svn working copy, if it has
476 already been created; otherwise, None. The tree is created
477 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
478 specified.
480 _svnrepos -- the basename of the svn repository (within tmp_dir).
481 Unset if DUMPFILE was specified."""
483 # The number of the last cvs2svn pass (determined lazily by
484 # get_last_pass()).
485 last_pass = None
487 @classmethod
488 def get_last_pass(cls):
489 """Return the number of cvs2svn's last pass."""
491 if cls.last_pass is None:
492 out = run_cvs2svn(None, '--help-passes')
493 cls.last_pass = int(out[-1].split()[0])
494 return cls.last_pass
496 def __init__(
497 self, conv_id, name, error_re, passbypass, symbols, args,
498 options_file=None, symbol_hints_file=None, dumpfile=None,
500 self.conv_id = conv_id
501 self.name = name
502 self.symbols = symbols
503 if not os.path.isdir(tmp_dir):
504 os.mkdir(tmp_dir)
506 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
508 if dumpfile:
509 self.dumpfile = os.path.join(tmp_dir, dumpfile)
510 # Clean up from any previous invocations of this script.
511 erase(self.dumpfile)
512 else:
513 self.dumpfile = None
514 self.repos = os.path.join(tmp_dir, '%s-svnrepos' % self.conv_id)
515 self._wc = os.path.join(tmp_dir, '%s-wc' % self.conv_id)
516 self._wc_path = None
517 self._wc_tree = None
519 # Clean up from any previous invocations of this script.
520 erase(self.repos)
521 erase(self._wc)
523 args = list(args)
524 if options_file:
525 self.options_file = os.path.join(cvsrepos, options_file)
526 args.extend([
527 '--options=%s' % self.options_file,
529 assert not symbol_hints_file
530 else:
531 self.options_file = None
532 if tmp_dir != 'cvs2svn-tmp':
533 # Only include this argument if it differs from cvs2svn's default:
534 args.extend([
535 '--tmpdir=%s' % tmp_dir,
538 if symbol_hints_file:
539 self.symbol_hints_file = os.path.join(cvsrepos, symbol_hints_file)
540 args.extend([
541 '--symbol-hints=%s' % self.symbol_hints_file,
544 if self.dumpfile:
545 args.extend(['--dumpfile=%s' % (self.dumpfile,)])
546 else:
547 args.extend(['-s', self.repos])
548 args.extend([cvsrepos])
550 if passbypass:
551 self.stdout = []
552 for p in range(1, self.get_last_pass() + 1):
553 self.stdout += run_cvs2svn(error_re, '-p', str(p), *args)
554 else:
555 self.stdout = run_cvs2svn(error_re, *args)
557 if self.dumpfile:
558 if not os.path.isfile(self.dumpfile):
559 raise Failure(
560 "Dumpfile not created: '%s'"
561 % os.path.join(os.getcwd(), self.dumpfile)
563 else:
564 if os.path.isdir(self.repos):
565 self.logs = parse_log(self.repos, self.symbols)
566 elif error_re is None:
567 raise Failure(
568 "Repository not created: '%s'"
569 % os.path.join(os.getcwd(), self.repos)
572 def output_found(self, pattern):
573 """Return True if PATTERN matches any line in self.stdout.
575 PATTERN is a regular expression pattern as a string.
578 pattern_re = re.compile(pattern)
580 for line in self.stdout:
581 if pattern_re.match(line):
582 # We found the pattern that we were looking for.
583 return 1
584 else:
585 return 0
587 def find_tag_log(self, tagname):
588 """Search LOGS for a log message containing 'TAGNAME' and return the
589 log in which it was found."""
590 for i in xrange(len(self.logs), 0, -1):
591 if self.logs[i].msg.find("'"+tagname+"'") != -1:
592 return self.logs[i]
593 raise ValueError("Tag %s not found in logs" % tagname)
595 def get_wc(self, *args):
596 """Return the path to the svn working copy, or a path within the WC.
598 If a working copy has not been created yet, create it now.
600 If ARGS are specified, then they should be strings that form
601 fragments of a path within the WC. They are joined using
602 os.path.join() and appended to the WC path."""
604 if self._wc_path is None:
605 run_svn('co', repos_to_url(self.repos), self._wc)
606 self._wc_path = self._wc
607 return os.path.join(self._wc_path, *args)
609 def get_wc_tree(self):
610 if self._wc_tree is None:
611 self._wc_tree = build_tree_from_wc(self.get_wc(), 1)
612 return self._wc_tree
614 def path_exists(self, *args):
615 """Return True if the specified path exists within the repository.
617 (The strings in ARGS are first joined into a path using
618 os.path.join().)"""
620 return os.path.exists(self.get_wc(*args))
622 def check_props(self, keys, checks):
623 """Helper function for checking lots of properties. For a list of
624 files in the conversion, check that the values of the properties
625 listed in KEYS agree with those listed in CHECKS. CHECKS is a
626 list of tuples: [ (filename, [value, value, ...]), ...], where the
627 values are listed in the same order as the key names are listed in
628 KEYS."""
630 for (file, values) in checks:
631 assert len(values) == len(keys)
632 props = props_for_path(self.get_wc_tree(), file)
633 for i in range(len(keys)):
634 if props.get(keys[i]) != values[i]:
635 raise Failure(
636 "File %s has property %s set to \"%s\" "
637 "(it should have been \"%s\").\n"
638 % (file, keys[i], props.get(keys[i]), values[i],)
642 # Cache of conversions that have already been done. Keys are conv_id;
643 # values are Conversion instances.
644 already_converted = { }
646 def ensure_conversion(
647 name, error_re=None, passbypass=None,
648 trunk=None, branches=None, tags=None,
649 args=None, options_file=None, symbol_hints_file=None, dumpfile=None,
651 """Convert CVS repository NAME to Subversion, but only if it has not
652 been converted before by this invocation of this script. If it has
653 been converted before, return the Conversion object from the
654 previous invocation.
656 If no error, return a Conversion instance.
658 If ERROR_RE is a string, it is a regular expression expected to
659 match some line of stderr printed by the conversion. If there is an
660 error and ERROR_RE is not set, then raise Failure.
662 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
663 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
665 NAME is just one word. For example, 'main' would mean to convert
666 './test-data/main-cvsrepos', and after the conversion, the resulting
667 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
668 a checked out head working copy in './cvs2svn-tmp/main-wc'.
670 Any other options to pass to cvs2svn should be in ARGS, each element
671 being one option, e.g., '--trunk-only'. If the option takes an
672 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
673 are passed to cvs2svn in the order that they appear in ARGS.
675 If OPTIONS_FILE is specified, then it should be the name of a file
676 within the main directory of the cvs repository associated with this
677 test. It is passed to cvs2svn using the --options option (which
678 suppresses some other options that are incompatible with --options).
680 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
681 file within the main directory of the cvs repository associated with
682 this test. It is passed to cvs2svn using the --symbol-hints option.
684 If DUMPFILE is specified, then it is the name of a dumpfile within
685 the temporary directory to which the conversion output should be
686 written."""
688 if args is None:
689 args = []
690 else:
691 args = list(args)
693 if trunk is None:
694 trunk = 'trunk'
695 else:
696 args.append('--trunk=%s' % (trunk,))
698 if branches is None:
699 branches = 'branches'
700 else:
701 args.append('--branches=%s' % (branches,))
703 if tags is None:
704 tags = 'tags'
705 else:
706 args.append('--tags=%s' % (tags,))
708 conv_id = make_conversion_id(
709 name, args, passbypass, options_file, symbol_hints_file
712 if conv_id not in already_converted:
713 try:
714 # Run the conversion and store the result for the rest of this
715 # session:
716 already_converted[conv_id] = Conversion(
717 conv_id, name, error_re, passbypass,
718 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
719 args, options_file, symbol_hints_file, dumpfile,
721 except Failure:
722 # Remember the failure so that a future attempt to run this conversion
723 # does not bother to retry, but fails immediately.
724 already_converted[conv_id] = None
725 raise
727 conv = already_converted[conv_id]
728 if conv is None:
729 raise Failure()
730 return conv
733 class Cvs2SvnTestCase(TestCase):
734 def __init__(
735 self, name, description=None, variant=None,
736 error_re=None, passbypass=None,
737 trunk=None, branches=None, tags=None,
738 args=None,
739 options_file=None, symbol_hints_file=None, dumpfile=None,
741 TestCase.__init__(self)
742 self.name = name
744 if description is not None:
745 self._description = description
746 else:
747 # By default, use the first line of the class docstring as the
748 # description:
749 self._description = self.__doc__.splitlines()[0]
751 # Check that the original description is OK before we tinker with
752 # it:
753 self.check_description()
755 if variant is not None:
756 # Modify description to show the variant. Trim description
757 # first if necessary to stay within the 50-character limit.
758 suffix = '...variant %s' % (variant,)
759 self._description = self._description[:50 - len(suffix)] + suffix
760 # Check that the description is still OK:
761 self.check_description()
763 self.error_re = error_re
764 self.passbypass = passbypass
765 self.trunk = trunk
766 self.branches = branches
767 self.tags = tags
768 self.args = args
769 self.options_file = options_file
770 self.symbol_hints_file = symbol_hints_file
771 self.dumpfile = dumpfile
773 def get_description(self):
774 return self._description
776 def ensure_conversion(self):
777 return ensure_conversion(
778 self.name,
779 error_re=self.error_re, passbypass=self.passbypass,
780 trunk=self.trunk, branches=self.branches, tags=self.tags,
781 args=self.args,
782 options_file=self.options_file,
783 symbol_hints_file=self.symbol_hints_file,
784 dumpfile=self.dumpfile,
788 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase):
789 """Test properties resulting from a conversion."""
791 def __init__(self, name, props_to_test, expected_props, **kw):
792 """Initialize an instance of Cvs2SvnPropertiesTestCase.
794 NAME is the name of the test, passed to Cvs2SvnTestCase.
795 PROPS_TO_TEST is a list of the names of svn properties that should
796 be tested. EXPECTED_PROPS is a list of tuples [(filename,
797 [value,...])], where the second item in each tuple is a list of
798 values expected for the properties listed in PROPS_TO_TEST for the
799 specified filename. If a property must *not* be set, then its
800 value should be listed as None."""
802 Cvs2SvnTestCase.__init__(self, name, **kw)
803 self.props_to_test = props_to_test
804 self.expected_props = expected_props
806 def run(self):
807 conv = self.ensure_conversion()
808 conv.check_props(self.props_to_test, self.expected_props)
811 #----------------------------------------------------------------------
812 # Tests.
813 #----------------------------------------------------------------------
816 def show_usage():
817 "cvs2svn with no arguments shows usage"
818 out = run_cvs2svn(None)
819 if (len(out) > 2 and out[0].find('ERROR:') == 0
820 and out[1].find('DBM module')):
821 print 'cvs2svn cannot execute due to lack of proper DBM module.'
822 print 'Exiting without running any further tests.'
823 sys.exit(1)
824 if out[0].find('Usage:') < 0:
825 raise Failure('Basic cvs2svn invocation failed.')
828 def show_help_passes():
829 "cvs2svn --help-passes shows pass information"
830 out = run_cvs2svn(None, '--help-passes')
831 if out[0].find('PASSES') < 0:
832 raise Failure('cvs2svn --help-passes failed.')
835 def attr_exec():
836 "detection of the executable flag"
837 if sys.platform == 'win32':
838 raise svntest.Skip()
839 conv = ensure_conversion('main')
840 st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec'))
841 if not st[0] & stat.S_IXUSR:
842 raise Failure()
845 def space_fname():
846 "conversion of filename with a space"
847 conv = ensure_conversion('main')
848 if not conv.path_exists('trunk', 'single-files', 'space fname'):
849 raise Failure()
852 def two_quick():
853 "two commits in quick succession"
854 conv = ensure_conversion('main')
855 logs = parse_log(
856 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
857 if len(logs) != 2:
858 raise Failure()
861 class PruneWithCare(Cvs2SvnTestCase):
862 "prune, but never too much"
864 def __init__(self, **kw):
865 Cvs2SvnTestCase.__init__(self, 'main', **kw)
867 def run(self):
868 # Robert Pluim encountered this lovely one while converting the
869 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
870 # repository (see issue #1302). Step 4 is the doozy:
872 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
873 # revision 2: adds trunk/blah/NEWS
874 # revision 3: deletes trunk/blah/cookie
875 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
876 # revision 5: does nothing
878 # After fixing cvs2svn, the sequence (correctly) looks like this:
880 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
881 # revision 2: adds trunk/blah/NEWS
882 # revision 3: deletes trunk/blah/cookie
883 # revision 4: does nothing [because trunk/blah/cookie already deleted]
884 # revision 5: deletes blah
886 # The difference is in 4 and 5. In revision 4, it's not correct to
887 # prune blah/, because NEWS is still in there, so revision 4 does
888 # nothing now. But when we delete NEWS in 5, that should bubble up
889 # and prune blah/ instead.
891 # ### Note that empty revisions like 4 are probably going to become
892 # ### at least optional, if not banished entirely from cvs2svn's
893 # ### output. Hmmm, or they may stick around, with an extra
894 # ### revision property explaining what happened. Need to think
895 # ### about that. In some sense, it's a bug in Subversion itself,
896 # ### that such revisions don't show up in 'svn log' output.
898 # In the test below, 'trunk/full-prune/first' represents
899 # cookie, and 'trunk/full-prune/second' represents NEWS.
901 conv = self.ensure_conversion()
903 # Confirm that revision 4 removes '/trunk/full-prune/first',
904 # and that revision 6 removes '/trunk/full-prune'.
906 # Also confirm similar things about '/full-prune-reappear/...',
907 # which is similar, except that later on it reappears, restored
908 # from pruneland, because a file gets added to it.
910 # And finally, a similar thing for '/partial-prune/...', except that
911 # in its case, a permanent file on the top level prevents the
912 # pruning from going farther than the subdirectory containing first
913 # and second.
915 for path in ('full-prune/first',
916 'full-prune-reappear/sub/first',
917 'partial-prune/sub/first'):
918 conv.logs[5].check_change('/%(trunk)s/' + path, 'D')
920 for path in ('full-prune',
921 'full-prune-reappear',
922 'partial-prune/sub'):
923 conv.logs[7].check_change('/%(trunk)s/' + path, 'D')
925 for path in ('full-prune-reappear',
926 'full-prune-reappear/appears-later'):
927 conv.logs[33].check_change('/%(trunk)s/' + path, 'A')
930 def interleaved_commits():
931 "two interleaved trunk commits, different log msgs"
932 # See test-data/main-cvsrepos/proj/README.
933 conv = ensure_conversion('main')
935 # The initial import.
936 rev = 26
937 conv.logs[rev].check('Initial import.', (
938 ('/%(trunk)s/interleaved', 'A'),
939 ('/%(trunk)s/interleaved/1', 'A'),
940 ('/%(trunk)s/interleaved/2', 'A'),
941 ('/%(trunk)s/interleaved/3', 'A'),
942 ('/%(trunk)s/interleaved/4', 'A'),
943 ('/%(trunk)s/interleaved/5', 'A'),
944 ('/%(trunk)s/interleaved/a', 'A'),
945 ('/%(trunk)s/interleaved/b', 'A'),
946 ('/%(trunk)s/interleaved/c', 'A'),
947 ('/%(trunk)s/interleaved/d', 'A'),
948 ('/%(trunk)s/interleaved/e', 'A'),
951 def check_letters(rev):
952 """Check if REV is the rev where only letters were committed."""
954 conv.logs[rev].check('Committing letters only.', (
955 ('/%(trunk)s/interleaved/a', 'M'),
956 ('/%(trunk)s/interleaved/b', 'M'),
957 ('/%(trunk)s/interleaved/c', 'M'),
958 ('/%(trunk)s/interleaved/d', 'M'),
959 ('/%(trunk)s/interleaved/e', 'M'),
962 def check_numbers(rev):
963 """Check if REV is the rev where only numbers were committed."""
965 conv.logs[rev].check('Committing numbers only.', (
966 ('/%(trunk)s/interleaved/1', 'M'),
967 ('/%(trunk)s/interleaved/2', 'M'),
968 ('/%(trunk)s/interleaved/3', 'M'),
969 ('/%(trunk)s/interleaved/4', 'M'),
970 ('/%(trunk)s/interleaved/5', 'M'),
973 # One of the commits was letters only, the other was numbers only.
974 # But they happened "simultaneously", so we don't assume anything
975 # about which commit appeared first, so we just try both ways.
976 rev += 1
977 try:
978 check_letters(rev)
979 check_numbers(rev + 1)
980 except Failure:
981 check_numbers(rev)
982 check_letters(rev + 1)
985 def simple_commits():
986 "simple trunk commits"
987 # See test-data/main-cvsrepos/proj/README.
988 conv = ensure_conversion('main')
990 # The initial import.
991 conv.logs[13].check('Initial import.', (
992 ('/%(trunk)s/proj', 'A'),
993 ('/%(trunk)s/proj/default', 'A'),
994 ('/%(trunk)s/proj/sub1', 'A'),
995 ('/%(trunk)s/proj/sub1/default', 'A'),
996 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
997 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
998 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
999 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1000 ('/%(trunk)s/proj/sub2', 'A'),
1001 ('/%(trunk)s/proj/sub2/default', 'A'),
1002 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1003 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1004 ('/%(trunk)s/proj/sub3', 'A'),
1005 ('/%(trunk)s/proj/sub3/default', 'A'),
1008 # The first commit.
1009 conv.logs[18].check('First commit to proj, affecting two files.', (
1010 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1011 ('/%(trunk)s/proj/sub3/default', 'M'),
1014 # The second commit.
1015 conv.logs[19].check('Second commit to proj, affecting all 7 files.', (
1016 ('/%(trunk)s/proj/default', 'M'),
1017 ('/%(trunk)s/proj/sub1/default', 'M'),
1018 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1019 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1020 ('/%(trunk)s/proj/sub2/default', 'M'),
1021 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1022 ('/%(trunk)s/proj/sub3/default', 'M')
1026 class SimpleTags(Cvs2SvnTestCase):
1027 "simple tags and branches, no commits"
1029 def __init__(self, **kw):
1030 # See test-data/main-cvsrepos/proj/README.
1031 Cvs2SvnTestCase.__init__(self, 'main', **kw)
1033 def run(self):
1034 conv = self.ensure_conversion()
1036 # Verify the copy source for the tags we are about to check
1037 # No need to verify the copyfrom revision, as simple_commits did that
1038 conv.logs[13].check('Initial import.', (
1039 ('/%(trunk)s/proj', 'A'),
1040 ('/%(trunk)s/proj/default', 'A'),
1041 ('/%(trunk)s/proj/sub1', 'A'),
1042 ('/%(trunk)s/proj/sub1/default', 'A'),
1043 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1044 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1045 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1046 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1047 ('/%(trunk)s/proj/sub2', 'A'),
1048 ('/%(trunk)s/proj/sub2/default', 'A'),
1049 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1050 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1051 ('/%(trunk)s/proj/sub3', 'A'),
1052 ('/%(trunk)s/proj/sub3/default', 'A'),
1055 fromstr = ' (from /%(branches)s/B_FROM_INITIALS:14)'
1057 # Tag on rev 1.1.1.1 of all files in proj
1058 conv.logs[14].check(sym_log_msg('B_FROM_INITIALS'), (
1059 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1060 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1061 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1064 # The same, as a tag
1065 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
1066 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1067 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr, 'A'),
1070 # Tag on rev 1.1.1.1 of all files in proj, except one
1071 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1072 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1073 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr, 'A'),
1074 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1077 # The same, as a branch
1078 conv.logs[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1079 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr, 'A'),
1080 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1084 def simple_branch_commits():
1085 "simple branch commits"
1086 # See test-data/main-cvsrepos/proj/README.
1087 conv = ensure_conversion('main')
1089 conv.logs[23].check('Modify three files, on branch B_MIXED.', (
1090 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1091 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1092 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1096 def mixed_time_tag():
1097 "mixed-time tag"
1098 # See test-data/main-cvsrepos/proj/README.
1099 conv = ensure_conversion('main')
1101 log = conv.find_tag_log('T_MIXED')
1102 log.check_changes((
1103 ('/%(tags)s/T_MIXED (from /%(branches)s/B_MIXED:20)', 'A'),
1107 def mixed_time_branch_with_added_file():
1108 "mixed-time branch, and a file added to the branch"
1109 # See test-data/main-cvsrepos/proj/README.
1110 conv = ensure_conversion('main')
1112 # A branch from the same place as T_MIXED in the previous test,
1113 # plus a file added directly to the branch
1114 conv.logs[20].check(sym_log_msg('B_MIXED'), (
1115 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1116 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1117 ('/%(branches)s/B_MIXED/single-files', 'D'),
1118 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1119 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1120 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1123 conv.logs[22].check('Add a file on branch B_MIXED.', (
1124 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1128 def mixed_commit():
1129 "a commit affecting both trunk and a branch"
1130 # See test-data/main-cvsrepos/proj/README.
1131 conv = ensure_conversion('main')
1133 conv.logs[24].check(
1134 'A single commit affecting one file on branch B_MIXED '
1135 'and one on trunk.', (
1136 ('/%(trunk)s/proj/sub2/default', 'M'),
1137 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1141 def split_time_branch():
1142 "branch some trunk files, and later branch the rest"
1143 # See test-data/main-cvsrepos/proj/README.
1144 conv = ensure_conversion('main')
1146 # First change on the branch, creating it
1147 conv.logs[25].check(sym_log_msg('B_SPLIT'), (
1148 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1149 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1150 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1151 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1154 conv.logs[29].check('First change on branch B_SPLIT.', (
1155 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1156 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1157 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1158 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1159 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1162 # A trunk commit for the file which was not branched
1163 conv.logs[30].check('A trunk change to sub1/subsubB/default. '
1164 'This was committed about an', (
1165 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1168 # Add the file not already branched to the branch, with modification:w
1169 conv.logs[31].check(sym_log_msg('B_SPLIT'), (
1170 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1171 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1174 conv.logs[32].check('This change affects sub3/default and '
1175 'sub1/subsubB/default, on branch', (
1176 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1177 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1181 def multiple_tags():
1182 "multiple tags referring to same revision"
1183 conv = ensure_conversion('main')
1184 if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1185 raise Failure()
1186 if not conv.path_exists(
1187 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1188 raise Failure()
1191 def multiply_defined_symbols():
1192 "multiple definitions of symbol names"
1194 # We can only check one line of the error output at a time, so test
1195 # twice. (The conversion only have to be done once because the
1196 # results are cached.)
1197 conv = ensure_conversion(
1198 'multiply-defined-symbols',
1199 error_re=(
1200 r"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1201 r"1\.2\.4 1\.2\.2"
1204 conv = ensure_conversion(
1205 'multiply-defined-symbols',
1206 error_re=(
1207 r"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1208 r"1\.2 1\.1"
1213 def multiply_defined_symbols_renamed():
1214 "rename multiply defined symbols"
1216 conv = ensure_conversion(
1217 'multiply-defined-symbols',
1218 options_file='cvs2svn-rename.options',
1222 def multiply_defined_symbols_ignored():
1223 "ignore multiply defined symbols"
1225 conv = ensure_conversion(
1226 'multiply-defined-symbols',
1227 options_file='cvs2svn-ignore.options',
1231 def repeatedly_defined_symbols():
1232 "multiple identical definitions of symbol names"
1234 # If a symbol is defined multiple times but has the same value each
1235 # time, that should not be an error.
1237 conv = ensure_conversion('repeatedly-defined-symbols')
1240 def bogus_tag():
1241 "conversion of invalid symbolic names"
1242 conv = ensure_conversion('bogus-tag')
1245 def overlapping_branch():
1246 "ignore a file with a branch with two names"
1247 conv = ensure_conversion('overlapping-branch')
1249 if not conv.output_found('.*cannot also have name \'vendorB\''):
1250 raise Failure()
1252 conv.logs[2].check('imported', (
1253 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1254 ('/%(trunk)s/overlapping-branch', 'A'),
1257 if len(conv.logs) != 2:
1258 raise Failure()
1261 class PhoenixBranch(Cvs2SvnTestCase):
1262 "convert a branch file rooted in a 'dead' revision"
1264 def __init__(self, **kw):
1265 Cvs2SvnTestCase.__init__(self, 'phoenix', **kw)
1267 def run(self):
1268 conv = self.ensure_conversion()
1269 conv.logs[8].check('This file was supplied by Jack Moffitt', (
1270 ('/%(branches)s/volsung_20010721', 'A'),
1271 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1273 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1274 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1278 ###TODO: We check for 4 changed paths here to accomodate creating tags
1279 ###and branches in rev 1, but that will change, so this will
1280 ###eventually change back.
1281 def ctrl_char_in_log():
1282 "handle a control char in a log message"
1283 # This was issue #1106.
1284 rev = 2
1285 conv = ensure_conversion('ctrl-char-in-log')
1286 conv.logs[rev].check_changes((
1287 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1289 if conv.logs[rev].msg.find('\x04') < 0:
1290 raise Failure(
1291 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1294 def overdead():
1295 "handle tags rooted in a redeleted revision"
1296 conv = ensure_conversion('overdead')
1299 class NoTrunkPrune(Cvs2SvnTestCase):
1300 "ensure that trunk doesn't get pruned"
1302 def __init__(self, **kw):
1303 Cvs2SvnTestCase.__init__(self, 'overdead', **kw)
1305 def run(self):
1306 conv = self.ensure_conversion()
1307 for rev in conv.logs.keys():
1308 rev_logs = conv.logs[rev]
1309 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1310 raise Failure()
1313 def double_delete():
1314 "file deleted twice, in the root of the repository"
1315 # This really tests several things: how we handle a file that's
1316 # removed (state 'dead') in two successive revisions; how we
1317 # handle a file in the root of the repository (there were some
1318 # bugs in cvs2svn's svn path construction for top-level files); and
1319 # the --no-prune option.
1320 conv = ensure_conversion(
1321 'double-delete', args=['--trunk-only', '--no-prune'])
1323 path = '/%(trunk)s/twice-removed'
1324 rev = 2
1325 conv.logs[rev].check('Updated CVS', (
1326 (path, 'A'),
1328 conv.logs[rev + 1].check('Remove this file for the first time.', (
1329 (path, 'D'),
1331 conv.logs[rev + 2].check('Remove this file for the second time,', (
1335 def split_branch():
1336 "branch created from both trunk and another branch"
1337 # See test-data/split-branch-cvsrepos/README.
1339 # The conversion will fail if the bug is present, and
1340 # ensure_conversion will raise Failure.
1341 conv = ensure_conversion('split-branch')
1344 def resync_misgroups():
1345 "resyncing should not misorder commit groups"
1346 # See test-data/resync-misgroups-cvsrepos/README.
1348 # The conversion will fail if the bug is present, and
1349 # ensure_conversion will raise Failure.
1350 conv = ensure_conversion('resync-misgroups')
1353 class TaggedBranchAndTrunk(Cvs2SvnTestCase):
1354 "allow tags with mixed trunk and branch sources"
1356 def __init__(self, **kw):
1357 Cvs2SvnTestCase.__init__(self, 'tagged-branch-n-trunk', **kw)
1359 def run(self):
1360 conv = self.ensure_conversion()
1362 tags = conv.symbols.get('tags', 'tags')
1364 a_path = conv.get_wc(tags, 'some-tag', 'a.txt')
1365 b_path = conv.get_wc(tags, 'some-tag', 'b.txt')
1366 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1367 raise Failure()
1368 if (open(a_path, 'r').read().find('1.24') == -1) \
1369 or (open(b_path, 'r').read().find('1.5') == -1):
1370 raise Failure()
1373 def enroot_race():
1374 "never use the rev-in-progress as a copy source"
1376 # See issue #1427 and r8544.
1377 conv = ensure_conversion('enroot-race')
1378 rev = 6
1379 conv.logs[rev].check_changes((
1380 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1381 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1382 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1384 conv.logs[rev + 1].check_changes((
1385 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1386 ('/%(trunk)s/proj/a.txt', 'M'),
1387 ('/%(trunk)s/proj/b.txt', 'M'),
1391 def enroot_race_obo():
1392 "do use the last completed rev as a copy source"
1393 conv = ensure_conversion('enroot-race-obo')
1394 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1395 if not len(conv.logs) == 3:
1396 raise Failure()
1399 class BranchDeleteFirst(Cvs2SvnTestCase):
1400 "correctly handle deletion as initial branch action"
1402 def __init__(self, **kw):
1403 Cvs2SvnTestCase.__init__(self, 'branch-delete-first', **kw)
1405 def run(self):
1406 # See test-data/branch-delete-first-cvsrepos/README.
1408 # The conversion will fail if the bug is present, and
1409 # ensure_conversion would raise Failure.
1410 conv = self.ensure_conversion()
1412 branches = conv.symbols.get('branches', 'branches')
1414 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1415 if conv.path_exists(branches, 'branch-1', 'file'):
1416 raise Failure()
1417 if conv.path_exists(branches, 'branch-2', 'file'):
1418 raise Failure()
1419 if not conv.path_exists(branches, 'branch-3', 'file'):
1420 raise Failure()
1423 def nonascii_filenames():
1424 "non ascii files converted incorrectly"
1425 # see issue #1255
1427 # on a en_US.iso-8859-1 machine this test fails with
1428 # svn: Can't recode ...
1430 # as described in the issue
1432 # on a en_US.UTF-8 machine this test fails with
1433 # svn: Malformed XML ...
1435 # which means at least it fails. Unfortunately it won't fail
1436 # with the same error...
1438 # mangle current locale settings so we know we're not running
1439 # a UTF-8 locale (which does not exhibit this problem)
1440 current_locale = locale.getlocale()
1441 new_locale = 'en_US.ISO8859-1'
1442 locale_changed = None
1444 # From http://docs.python.org/lib/module-sys.html
1446 # getfilesystemencoding():
1448 # Return the name of the encoding used to convert Unicode filenames
1449 # into system file names, or None if the system default encoding is
1450 # used. The result value depends on the operating system:
1452 # - On Windows 9x, the encoding is ``mbcs''.
1453 # - On Mac OS X, the encoding is ``utf-8''.
1454 # - On Unix, the encoding is the user's preference according to the
1455 # result of nl_langinfo(CODESET), or None if the
1456 # nl_langinfo(CODESET) failed.
1457 # - On Windows NT+, file names are Unicode natively, so no conversion is
1458 # performed.
1460 # So we're going to skip this test on Mac OS X for now.
1461 if sys.platform == "darwin":
1462 raise svntest.Skip()
1464 try:
1465 # change locale to non-UTF-8 locale to generate latin1 names
1466 locale.setlocale(locale.LC_ALL, # this might be too broad?
1467 new_locale)
1468 locale_changed = 1
1469 except locale.Error:
1470 raise svntest.Skip()
1472 try:
1473 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
1474 dstrepos_path = os.path.join(test_data_dir,'non-ascii-cvsrepos')
1475 if not os.path.exists(dstrepos_path):
1476 # create repos from existing main repos
1477 shutil.copytree(srcrepos_path, dstrepos_path)
1478 base_path = os.path.join(dstrepos_path, 'single-files')
1479 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1480 os.path.join(base_path, 'two\366uick,v'))
1481 new_path = os.path.join(dstrepos_path, 'single\366files')
1482 os.rename(base_path, new_path)
1484 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1485 finally:
1486 if locale_changed:
1487 locale.setlocale(locale.LC_ALL, current_locale)
1488 safe_rmtree(dstrepos_path)
1491 class UnicodeTest(Cvs2SvnTestCase):
1492 "metadata contains unicode"
1494 warning_pattern = r'ERROR\: There were warnings converting .* messages'
1496 def __init__(self, name, warning_expected, **kw):
1497 if warning_expected:
1498 error_re = self.warning_pattern
1499 else:
1500 error_re = None
1502 Cvs2SvnTestCase.__init__(self, name, error_re=error_re, **kw)
1503 self.warning_expected = warning_expected
1505 def run(self):
1506 try:
1507 # ensure the availability of the "utf_8" encoding:
1508 u'a'.encode('utf_8').decode('utf_8')
1509 except LookupError:
1510 raise svntest.Skip()
1512 self.ensure_conversion()
1515 class UnicodeAuthor(UnicodeTest):
1516 "author name contains unicode"
1518 def __init__(self, warning_expected, **kw):
1519 UnicodeTest.__init__(self, 'unicode-author', warning_expected, **kw)
1522 class UnicodeLog(UnicodeTest):
1523 "log message contains unicode"
1525 def __init__(self, warning_expected, **kw):
1526 UnicodeTest.__init__(self, 'unicode-log', warning_expected, **kw)
1529 def vendor_branch_sameness():
1530 "avoid spurious changes for initial revs"
1531 conv = ensure_conversion(
1532 'vendor-branch-sameness', args=['--keep-trivial-imports']
1535 # The following files are in this repository:
1537 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1538 # the same contents, the file's default branch is 1.1.1,
1539 # and both revisions are in state 'Exp'.
1541 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1542 # 1.1 (the addition of a line of text).
1544 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1546 # d.txt: This file was created by 'cvs add' instead of import, so
1547 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1548 # The timestamp on the add is exactly the same as for the
1549 # imports of the other files.
1551 # e.txt: Like a.txt, except that the log message for revision 1.1
1552 # is not the standard import log message.
1554 # (Aside from e.txt, the log messages for the same revisions are the
1555 # same in all files.)
1557 # We expect that only a.txt is recognized as an import whose 1.1
1558 # revision can be omitted. The other files should be added on trunk
1559 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1560 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1561 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1562 # and c.txt should be 'D'eleted.
1564 rev = 2
1565 conv.logs[rev].check('Initial revision', (
1566 ('/%(trunk)s/proj', 'A'),
1567 ('/%(trunk)s/proj/b.txt', 'A'),
1568 ('/%(trunk)s/proj/c.txt', 'A'),
1569 ('/%(trunk)s/proj/d.txt', 'A'),
1572 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1573 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1574 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1577 conv.logs[rev + 2].check('First vendor branch revision.', (
1578 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1579 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1580 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1583 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1584 'to compensate for changes in r4,', (
1585 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1586 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1587 ('/%(trunk)s/proj/c.txt', 'D'),
1590 rev = 7
1591 conv.logs[rev].check('This log message is not the standard', (
1592 ('/%(trunk)s/proj/e.txt', 'A'),
1595 conv.logs[rev + 2].check('First vendor branch revision', (
1596 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1599 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1600 'to compensate for changes in r9,', (
1601 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1605 def vendor_branch_trunk_only():
1606 "handle vendor branches with --trunk-only"
1607 conv = ensure_conversion('vendor-branch-sameness', args=['--trunk-only'])
1609 rev = 2
1610 conv.logs[rev].check('Initial revision', (
1611 ('/%(trunk)s/proj', 'A'),
1612 ('/%(trunk)s/proj/b.txt', 'A'),
1613 ('/%(trunk)s/proj/c.txt', 'A'),
1614 ('/%(trunk)s/proj/d.txt', 'A'),
1617 conv.logs[rev + 1].check('First vendor branch revision', (
1618 ('/%(trunk)s/proj/a.txt', 'A'),
1619 ('/%(trunk)s/proj/b.txt', 'M'),
1620 ('/%(trunk)s/proj/c.txt', 'D'),
1623 conv.logs[rev + 2].check('This log message is not the standard', (
1624 ('/%(trunk)s/proj/e.txt', 'A'),
1627 conv.logs[rev + 3].check('First vendor branch revision', (
1628 ('/%(trunk)s/proj/e.txt', 'M'),
1632 def default_branches():
1633 "handle default branches correctly"
1634 conv = ensure_conversion('default-branches')
1636 # There are seven files in the repository:
1638 # a.txt:
1639 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1640 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1641 # committed (thus losing the default branch "1.1.1"), then
1642 # 1.1.1.4 was imported. All vendor import release tags are
1643 # still present.
1645 # b.txt:
1646 # Like a.txt, but without rev 1.2.
1648 # c.txt:
1649 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1651 # d.txt:
1652 # Same as the previous two, but 1.1.1 branch is unlabeled.
1654 # e.txt:
1655 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1657 # deleted-on-vendor-branch.txt,v:
1658 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1660 # added-then-imported.txt,v:
1661 # Added with 'cvs add' to create 1.1, then imported with
1662 # completely different contents to create 1.1.1.1, therefore
1663 # never had a default branch.
1666 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1667 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1668 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1669 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1670 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1671 ('/%(branches)s/vbranchA', 'A'),
1672 ('/%(branches)s/vbranchA/proj', 'A'),
1673 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1674 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1675 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1676 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1679 conv.logs[3].check("This commit was generated by cvs2svn "
1680 "to compensate for changes in r2,", (
1681 ('/%(trunk)s/proj', 'A'),
1682 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1683 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1684 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1685 ('/%(trunk)s/proj/d.txt '
1686 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1687 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1688 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1689 ('/%(trunk)s/proj/e.txt '
1690 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1693 conv.logs[4].check(sym_log_msg('vtag-1',1), (
1694 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1695 ('/%(tags)s/vtag-1/proj/d.txt '
1696 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1699 conv.logs[5].check("Import (vbranchA, vtag-2).", (
1700 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1701 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1702 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1703 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1704 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1705 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1708 conv.logs[6].check("This commit was generated by cvs2svn "
1709 "to compensate for changes in r5,", (
1710 ('/%(trunk)s/proj/a.txt '
1711 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1712 ('/%(trunk)s/proj/b.txt '
1713 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1714 ('/%(trunk)s/proj/c.txt '
1715 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1716 ('/%(trunk)s/proj/d.txt '
1717 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1718 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1719 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1720 'R'),
1721 ('/%(trunk)s/proj/e.txt '
1722 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1725 conv.logs[7].check(sym_log_msg('vtag-2',1), (
1726 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1727 ('/%(tags)s/vtag-2/proj/d.txt '
1728 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1731 conv.logs[8].check("Import (vbranchA, vtag-3).", (
1732 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1733 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1734 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1735 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1736 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1737 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1740 conv.logs[9].check("This commit was generated by cvs2svn "
1741 "to compensate for changes in r8,", (
1742 ('/%(trunk)s/proj/a.txt '
1743 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1744 ('/%(trunk)s/proj/b.txt '
1745 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1746 ('/%(trunk)s/proj/c.txt '
1747 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1748 ('/%(trunk)s/proj/d.txt '
1749 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1750 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1751 ('/%(trunk)s/proj/e.txt '
1752 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1755 conv.logs[10].check(sym_log_msg('vtag-3',1), (
1756 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1757 ('/%(tags)s/vtag-3/proj/d.txt '
1758 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1759 ('/%(tags)s/vtag-3/proj/e.txt '
1760 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1763 conv.logs[11].check("First regular commit, to a.txt, on vtag-3.", (
1764 ('/%(trunk)s/proj/a.txt', 'M'),
1767 conv.logs[12].check("Add a file to the working copy.", (
1768 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1771 conv.logs[13].check(sym_log_msg('vbranchA'), (
1772 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1773 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1776 conv.logs[14].check("Import (vbranchA, vtag-4).", (
1777 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1778 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1779 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1780 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1781 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1782 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1783 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1786 conv.logs[15].check("This commit was generated by cvs2svn "
1787 "to compensate for changes in r14,", (
1788 ('/%(trunk)s/proj/b.txt '
1789 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1790 ('/%(trunk)s/proj/c.txt '
1791 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1792 ('/%(trunk)s/proj/d.txt '
1793 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1794 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1795 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1796 'A'),
1797 ('/%(trunk)s/proj/e.txt '
1798 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1801 conv.logs[16].check(sym_log_msg('vtag-4',1), (
1802 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1803 ('/%(tags)s/vtag-4/proj/d.txt '
1804 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
1808 def default_branches_trunk_only():
1809 "handle default branches with --trunk-only"
1811 conv = ensure_conversion('default-branches', args=['--trunk-only'])
1813 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1814 ('/%(trunk)s/proj', 'A'),
1815 ('/%(trunk)s/proj/a.txt', 'A'),
1816 ('/%(trunk)s/proj/b.txt', 'A'),
1817 ('/%(trunk)s/proj/c.txt', 'A'),
1818 ('/%(trunk)s/proj/d.txt', 'A'),
1819 ('/%(trunk)s/proj/e.txt', 'A'),
1820 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1823 conv.logs[3].check("Import (vbranchA, vtag-2).", (
1824 ('/%(trunk)s/proj/a.txt', 'M'),
1825 ('/%(trunk)s/proj/b.txt', 'M'),
1826 ('/%(trunk)s/proj/c.txt', 'M'),
1827 ('/%(trunk)s/proj/d.txt', 'M'),
1828 ('/%(trunk)s/proj/e.txt', 'M'),
1829 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
1832 conv.logs[4].check("Import (vbranchA, vtag-3).", (
1833 ('/%(trunk)s/proj/a.txt', 'M'),
1834 ('/%(trunk)s/proj/b.txt', 'M'),
1835 ('/%(trunk)s/proj/c.txt', 'M'),
1836 ('/%(trunk)s/proj/d.txt', 'M'),
1837 ('/%(trunk)s/proj/e.txt', 'M'),
1838 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1841 conv.logs[5].check("First regular commit, to a.txt, on vtag-3.", (
1842 ('/%(trunk)s/proj/a.txt', 'M'),
1845 conv.logs[6].check("Add a file to the working copy.", (
1846 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1849 conv.logs[7].check("Import (vbranchA, vtag-4).", (
1850 ('/%(trunk)s/proj/b.txt', 'M'),
1851 ('/%(trunk)s/proj/c.txt', 'M'),
1852 ('/%(trunk)s/proj/d.txt', 'M'),
1853 ('/%(trunk)s/proj/e.txt', 'M'),
1854 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1858 def default_branch_and_1_2():
1859 "do not allow 1.2 revision with default branch"
1861 conv = ensure_conversion(
1862 'default-branch-and-1-2',
1863 error_re=(
1864 r'.*File \'.*\' has default branch=1\.1\.1 but also a revision 1\.2'
1869 def compose_tag_three_sources():
1870 "compose a tag from three sources"
1871 conv = ensure_conversion('compose-tag-three-sources')
1873 conv.logs[2].check("Add on trunk", (
1874 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
1875 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
1876 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
1877 ('/%(trunk)s/tagged-on-b1', 'A'),
1878 ('/%(trunk)s/tagged-on-b2', 'A'),
1881 conv.logs[3].check(sym_log_msg('b1'), (
1882 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
1885 conv.logs[4].check(sym_log_msg('b2'), (
1886 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
1889 conv.logs[5].check("Commit on branch b1", (
1890 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
1891 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
1892 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
1893 ('/%(branches)s/b1/tagged-on-b1', 'M'),
1894 ('/%(branches)s/b1/tagged-on-b2', 'M'),
1897 conv.logs[6].check("Commit on branch b2", (
1898 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
1899 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
1900 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
1901 ('/%(branches)s/b2/tagged-on-b1', 'M'),
1902 ('/%(branches)s/b2/tagged-on-b2', 'M'),
1905 conv.logs[7].check("Commit again on trunk", (
1906 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
1907 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
1908 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
1909 ('/%(trunk)s/tagged-on-b1', 'M'),
1910 ('/%(trunk)s/tagged-on-b2', 'M'),
1913 conv.logs[8].check(sym_log_msg('T',1), (
1914 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
1915 ('/%(tags)s/T/tagged-on-trunk-1.1 '
1916 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
1917 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:5)', 'R'),
1918 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:6)', 'R'),
1922 def pass5_when_to_fill():
1923 "reserve a svn revnum for a fill only when required"
1924 # The conversion will fail if the bug is present, and
1925 # ensure_conversion would raise Failure.
1926 conv = ensure_conversion('pass5-when-to-fill')
1929 class EmptyTrunk(Cvs2SvnTestCase):
1930 "don't break when the trunk is empty"
1932 def __init__(self, **kw):
1933 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
1935 def run(self):
1936 # The conversion will fail if the bug is present, and
1937 # ensure_conversion would raise Failure.
1938 conv = self.ensure_conversion()
1941 def no_spurious_svn_commits():
1942 "ensure that we don't create any spurious commits"
1943 conv = ensure_conversion('phoenix')
1945 # Check spurious commit that could be created in
1946 # SVNCommitCreator._pre_commit()
1948 # (When you add a file on a branch, CVS creates a trunk revision
1949 # in state 'dead'. If the log message of that commit is equal to
1950 # the one that CVS generates, we do not ever create a 'fill'
1951 # SVNCommit for it.)
1953 # and spurious commit that could be created in
1954 # SVNCommitCreator._commit()
1956 # (When you add a file on a branch, CVS creates a trunk revision
1957 # in state 'dead'. If the log message of that commit is equal to
1958 # the one that CVS generates, we do not create a primary SVNCommit
1959 # for it.)
1960 conv.logs[17].check('File added on branch xiphophorus', (
1961 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
1964 # Check to make sure that a commit *is* generated:
1965 # (When you add a file on a branch, CVS creates a trunk revision
1966 # in state 'dead'. If the log message of that commit is NOT equal
1967 # to the one that CVS generates, we create a primary SVNCommit to
1968 # serve as a home for the log message in question.
1969 conv.logs[18].check('file added-on-branch2.txt was initially added on '
1970 + 'branch xiphophorus,\nand this log message was tweaked', ())
1972 # Check spurious commit that could be created in
1973 # SVNCommitCreator._commit_symbols().
1974 conv.logs[19].check('This file was also added on branch xiphophorus,', (
1975 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
1979 class PeerPathPruning(Cvs2SvnTestCase):
1980 "make sure that filling prunes paths correctly"
1982 def __init__(self, **kw):
1983 Cvs2SvnTestCase.__init__(self, 'peer-path-pruning', **kw)
1985 def run(self):
1986 conv = self.ensure_conversion()
1987 conv.logs[6].check(sym_log_msg('BRANCH'), (
1988 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A'),
1989 ('/%(branches)s/BRANCH/bar', 'D'),
1990 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:5)', 'R'),
1994 def invalid_closings_on_trunk():
1995 "verify correct revs are copied to default branches"
1996 # The conversion will fail if the bug is present, and
1997 # ensure_conversion would raise Failure.
1998 conv = ensure_conversion('invalid-closings-on-trunk')
2001 def individual_passes():
2002 "run each pass individually"
2003 conv = ensure_conversion('main')
2004 conv2 = ensure_conversion('main', passbypass=1)
2006 if conv.logs != conv2.logs:
2007 raise Failure()
2010 def resync_bug():
2011 "reveal a big bug in our resync algorithm"
2012 # This will fail if the bug is present
2013 conv = ensure_conversion('resync-bug')
2016 def branch_from_default_branch():
2017 "reveal a bug in our default branch detection code"
2018 conv = ensure_conversion('branch-from-default-branch')
2020 # This revision will be a default branch synchronization only
2021 # if cvs2svn is correctly determining default branch revisions.
2023 # The bug was that cvs2svn was treating revisions on branches off of
2024 # default branches as default branch revisions, resulting in
2025 # incorrectly regarding the branch off of the default branch as a
2026 # non-trunk default branch. Crystal clear? I thought so. See
2027 # issue #42 for more incoherent blathering.
2028 conv.logs[5].check("This commit was generated by cvs2svn", (
2029 ('/%(trunk)s/proj/file.txt '
2030 '(from /%(branches)s/upstream/proj/file.txt:4)', 'R'),
2034 def file_in_attic_too():
2035 "die if a file exists in and out of the attic"
2036 ensure_conversion(
2037 'file-in-attic-too',
2038 error_re=(
2039 r'.*A CVS repository cannot contain both '
2040 r'(.*)' + re.escape(os.sep) + r'(.*) '
2041 + r'and '
2042 r'\1' + re.escape(os.sep) + r'Attic' + re.escape(os.sep) + r'\2'
2047 def retain_file_in_attic_too():
2048 "test --retain-conflicting-attic-files option"
2049 conv = ensure_conversion(
2050 'file-in-attic-too', args=['--retain-conflicting-attic-files'])
2051 if not conv.path_exists('trunk', 'file.txt'):
2052 raise Failure()
2053 if not conv.path_exists('trunk', 'Attic', 'file.txt'):
2054 raise Failure()
2057 def symbolic_name_filling_guide():
2058 "reveal a big bug in our SymbolFillingGuide"
2059 # This will fail if the bug is present
2060 conv = ensure_conversion('symbolic-name-overfill')
2063 # Helpers for tests involving file contents and properties.
2065 class NodeTreeWalkException:
2066 "Exception class for node tree traversals."
2067 pass
2069 def node_for_path(node, path):
2070 "In the tree rooted under SVNTree NODE, return the node at PATH."
2071 if node.name != '__SVN_ROOT_NODE':
2072 raise NodeTreeWalkException()
2073 path = path.strip('/')
2074 components = path.split('/')
2075 for component in components:
2076 node = get_child(node, component)
2077 return node
2079 # Helper for tests involving properties.
2080 def props_for_path(node, path):
2081 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2082 return node_for_path(node, path).props
2085 class EOLMime(Cvs2SvnPropertiesTestCase):
2086 """eol settings and mime types together
2088 The files are as follows:
2090 trunk/foo.txt: no -kb, mime file says nothing.
2091 trunk/foo.xml: no -kb, mime file says text.
2092 trunk/foo.zip: no -kb, mime file says non-text.
2093 trunk/foo.bin: has -kb, mime file says nothing.
2094 trunk/foo.csv: has -kb, mime file says text.
2095 trunk/foo.dbf: has -kb, mime file says non-text.
2098 def __init__(self, args, **kw):
2099 # TODO: It's a bit klugey to construct this path here. But so far
2100 # there's only one test with a mime.types file. If we have more,
2101 # we should abstract this into some helper, which would be located
2102 # near ensure_conversion(). Note that it is a convention of this
2103 # test suite for a mime.types file to be located in the top level
2104 # of the CVS repository to which it applies.
2105 self.mime_path = os.path.join(
2106 test_data_dir, 'eol-mime-cvsrepos', 'mime.types')
2108 Cvs2SvnPropertiesTestCase.__init__(
2109 self, 'eol-mime',
2110 props_to_test=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2111 args=['--mime-types=%s' % self.mime_path] + args,
2112 **kw)
2115 # We do four conversions. Each time, we pass --mime-types=FILE with
2116 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2117 # Thus there's one conversion with neither flag, one with just the
2118 # former, one with just the latter, and one with both.
2121 # Neither --no-default-eol nor --eol-from-mime-type:
2122 eol_mime1 = EOLMime(
2123 variant=1,
2124 args=[],
2125 expected_props=[
2126 ('trunk/foo.txt', [None, None, None]),
2127 ('trunk/foo.xml', [None, 'text/xml', None]),
2128 ('trunk/foo.zip', [None, 'application/zip', None]),
2129 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2130 ('trunk/foo.csv', [None, 'text/csv', None]),
2131 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2135 # Just --no-default-eol, not --eol-from-mime-type:
2136 eol_mime2 = EOLMime(
2137 variant=2,
2138 args=['--default-eol=native'],
2139 expected_props=[
2140 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2141 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2142 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS]),
2143 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2144 ('trunk/foo.csv', [None, 'text/csv', None]),
2145 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2149 # Just --eol-from-mime-type, not --no-default-eol:
2150 eol_mime3 = EOLMime(
2151 variant=3,
2152 args=['--eol-from-mime-type'],
2153 expected_props=[
2154 ('trunk/foo.txt', [None, None, None]),
2155 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2156 ('trunk/foo.zip', [None, 'application/zip', None]),
2157 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2158 ('trunk/foo.csv', [None, 'text/csv', None]),
2159 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2163 # Both --no-default-eol and --eol-from-mime-type:
2164 eol_mime4 = EOLMime(
2165 variant=4,
2166 args=['--eol-from-mime-type', '--default-eol=native'],
2167 expected_props=[
2168 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2169 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2170 ('trunk/foo.zip', [None, 'application/zip', None]),
2171 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2172 ('trunk/foo.csv', [None, 'text/csv', None]),
2173 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2177 cvs_revnums_off = Cvs2SvnPropertiesTestCase(
2178 'eol-mime',
2179 description='test non-setting of cvs2svn:cvs-rev property',
2180 args=[],
2181 props_to_test=['cvs2svn:cvs-rev'],
2182 expected_props=[
2183 ('trunk/foo.txt', [None]),
2184 ('trunk/foo.xml', [None]),
2185 ('trunk/foo.zip', [None]),
2186 ('trunk/foo.bin', [None]),
2187 ('trunk/foo.csv', [None]),
2188 ('trunk/foo.dbf', [None]),
2192 cvs_revnums_on = Cvs2SvnPropertiesTestCase(
2193 'eol-mime',
2194 description='test setting of cvs2svn:cvs-rev property',
2195 args=['--cvs-revnums'],
2196 props_to_test=['cvs2svn:cvs-rev'],
2197 expected_props=[
2198 ('trunk/foo.txt', ['1.2']),
2199 ('trunk/foo.xml', ['1.2']),
2200 ('trunk/foo.zip', ['1.2']),
2201 ('trunk/foo.bin', ['1.2']),
2202 ('trunk/foo.csv', ['1.2']),
2203 ('trunk/foo.dbf', ['1.2']),
2207 keywords = Cvs2SvnPropertiesTestCase(
2208 'keywords',
2209 description='test setting of svn:keywords property among others',
2210 args=['--default-eol=native'],
2211 props_to_test=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2212 expected_props=[
2213 ('trunk/foo.default', [KEYWORDS, 'native', None]),
2214 ('trunk/foo.kkvl', [KEYWORDS, 'native', None]),
2215 ('trunk/foo.kkv', [KEYWORDS, 'native', None]),
2216 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2217 ('trunk/foo.kk', [None, 'native', None]),
2218 ('trunk/foo.ko', [None, 'native', None]),
2219 ('trunk/foo.kv', [None, 'native', None]),
2223 def ignore():
2224 "test setting of svn:ignore property"
2225 conv = ensure_conversion('cvsignore')
2226 wc_tree = conv.get_wc_tree()
2227 topdir_props = props_for_path(wc_tree, 'trunk/proj')
2228 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
2230 if topdir_props['svn:ignore'] != \
2231 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n\n':
2232 raise Failure()
2234 if subdir_props['svn:ignore'] != \
2235 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n\n':
2236 raise Failure()
2239 def requires_cvs():
2240 "test that CVS can still do what RCS can't"
2241 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2242 conv = ensure_conversion('requires-cvs', args=["--use-cvs"])
2244 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
2245 cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read()
2247 if atsign_contents[-1:] == "@":
2248 raise Failure()
2249 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
2250 raise Failure()
2252 if not (conv.logs[21].author == "William Lyon Phelps III" and
2253 conv.logs[20].author == "j random"):
2254 raise Failure()
2257 def questionable_branch_names():
2258 "test that we can handle weird branch names"
2259 conv = ensure_conversion('questionable-symbols')
2260 # If the conversion succeeds, then we're okay. We could check the
2261 # actual branch paths, too, but the main thing is to know that the
2262 # conversion doesn't fail.
2265 def questionable_tag_names():
2266 "test that we can handle weird tag names"
2267 conv = ensure_conversion('questionable-symbols')
2268 conv.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2269 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2271 conv.find_tag_log('TagWith/Backslash_E').check(
2272 sym_log_msg('TagWith/Backslash_E',1),
2274 ('/%(tags)s/TagWith', 'A'),
2275 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2278 conv.find_tag_log('TagWith/Slash_Z').check(
2279 sym_log_msg('TagWith/Slash_Z',1),
2281 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2286 def revision_reorder_bug():
2287 "reveal a bug that reorders file revisions"
2288 conv = ensure_conversion('revision-reorder-bug')
2289 # If the conversion succeeds, then we're okay. We could check the
2290 # actual revisions, too, but the main thing is to know that the
2291 # conversion doesn't fail.
2294 def exclude():
2295 "test that exclude really excludes everything"
2296 conv = ensure_conversion('main', args=['--exclude=.*'])
2297 for log in conv.logs.values():
2298 for item in log.changed_paths.keys():
2299 if item.startswith('/branches/') or item.startswith('/tags/'):
2300 raise Failure()
2303 def vendor_branch_delete_add():
2304 "add trunk file that was deleted on vendor branch"
2305 # This will error if the bug is present
2306 conv = ensure_conversion('vendor-branch-delete-add')
2309 def resync_pass2_pull_forward():
2310 "ensure pass2 doesn't pull rev too far forward"
2311 conv = ensure_conversion('resync-pass2-pull-forward')
2312 # If the conversion succeeds, then we're okay. We could check the
2313 # actual revisions, too, but the main thing is to know that the
2314 # conversion doesn't fail.
2317 def native_eol():
2318 "only LFs for svn:eol-style=native files"
2319 conv = ensure_conversion('native-eol', args=['--default-eol=native'])
2320 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
2321 conv.repos)
2322 # Verify that all files in the dump have LF EOLs. We're actually
2323 # testing the whole dump file, but the dump file itself only uses
2324 # LF EOLs, so we're safe.
2325 for line in lines:
2326 if line[-1] != '\n' or line[:-1].find('\r') != -1:
2327 raise Failure()
2330 def double_fill():
2331 "reveal a bug that created a branch twice"
2332 conv = ensure_conversion('double-fill')
2333 # If the conversion succeeds, then we're okay. We could check the
2334 # actual revisions, too, but the main thing is to know that the
2335 # conversion doesn't fail.
2338 def double_fill2():
2339 "reveal a second bug that created a branch twice"
2340 conv = ensure_conversion('double-fill2')
2341 conv.logs[6].check_msg(sym_log_msg('BRANCH1'))
2342 conv.logs[7].check_msg(sym_log_msg('BRANCH2'))
2343 try:
2344 # This check should fail:
2345 conv.logs[8].check_msg(sym_log_msg('BRANCH2'))
2346 except Failure:
2347 pass
2348 else:
2349 raise Failure('Symbol filled twice in a row')
2352 def resync_pass2_push_backward():
2353 "ensure pass2 doesn't push rev too far backward"
2354 conv = ensure_conversion('resync-pass2-push-backward')
2355 # If the conversion succeeds, then we're okay. We could check the
2356 # actual revisions, too, but the main thing is to know that the
2357 # conversion doesn't fail.
2360 def double_add():
2361 "reveal a bug that added a branch file twice"
2362 conv = ensure_conversion('double-add')
2363 # If the conversion succeeds, then we're okay. We could check the
2364 # actual revisions, too, but the main thing is to know that the
2365 # conversion doesn't fail.
2368 def bogus_branch_copy():
2369 "reveal a bug that copies a branch file wrongly"
2370 conv = ensure_conversion('bogus-branch-copy')
2371 # If the conversion succeeds, then we're okay. We could check the
2372 # actual revisions, too, but the main thing is to know that the
2373 # conversion doesn't fail.
2376 def nested_ttb_directories():
2377 "require error if ttb directories are not disjoint"
2378 opts_list = [
2379 {'trunk' : 'a', 'branches' : 'a',},
2380 {'trunk' : 'a', 'tags' : 'a',},
2381 {'branches' : 'a', 'tags' : 'a',},
2382 # This option conflicts with the default trunk path:
2383 {'branches' : 'trunk',},
2384 # Try some nested directories:
2385 {'trunk' : 'a', 'branches' : 'a/b',},
2386 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2387 {'branches' : 'a', 'tags' : 'a/b',},
2390 for opts in opts_list:
2391 ensure_conversion(
2392 'main', error_re=r'The following paths are not disjoint\:', **opts
2396 class AutoProps(Cvs2SvnPropertiesTestCase):
2397 """Test auto-props.
2399 The files are as follows:
2401 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2402 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2403 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2404 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2405 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2406 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2407 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2408 trunk/foo.UPCASE1: no -kb, no mime type.
2409 trunk/foo.UPCASE2: no -kb, no mime type.
2412 def __init__(self, args, **kw):
2413 ### TODO: It's a bit klugey to construct this path here. See also
2414 ### the comment in eol_mime().
2415 auto_props_path = os.path.join(
2416 test_data_dir, 'eol-mime-cvsrepos', 'auto-props')
2418 Cvs2SvnPropertiesTestCase.__init__(
2419 self, 'eol-mime',
2420 props_to_test=[
2421 'myprop',
2422 'svn:eol-style',
2423 'svn:mime-type',
2424 'svn:keywords',
2425 'svn:executable',
2427 args=[
2428 '--auto-props=%s' % auto_props_path,
2429 '--eol-from-mime-type'
2430 ] + args,
2431 **kw)
2434 auto_props_ignore_case = AutoProps(
2435 description="test auto-props",
2436 args=['--default-eol=native'],
2437 expected_props=[
2438 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS, None]),
2439 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS, None]),
2440 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2441 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2442 ('trunk/foo.bin',
2443 ['bin', None, 'application/octet-stream', None, '']),
2444 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2445 ('trunk/foo.dbf',
2446 ['dbf', None, 'application/what-is-dbf', None, None]),
2447 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS, None]),
2448 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS, None]),
2452 def ctrl_char_in_filename():
2453 "do not allow control characters in filenames"
2455 try:
2456 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
2457 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
2458 if os.path.exists(dstrepos_path):
2459 safe_rmtree(dstrepos_path)
2461 # create repos from existing main repos
2462 shutil.copytree(srcrepos_path, dstrepos_path)
2463 base_path = os.path.join(dstrepos_path, 'single-files')
2464 try:
2465 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2466 os.path.join(base_path, 'two\rquick,v'))
2467 except:
2468 # Operating systems that don't allow control characters in
2469 # filenames will hopefully have thrown an exception; in that
2470 # case, just skip this test.
2471 raise svntest.Skip()
2473 conv = ensure_conversion(
2474 'ctrl-char-filename',
2475 error_re=(r'.*Character .* in filename .* '
2476 r'is not supported by Subversion\.'),
2478 finally:
2479 safe_rmtree(dstrepos_path)
2482 def commit_dependencies():
2483 "interleaved and multi-branch commits to same files"
2484 conv = ensure_conversion("commit-dependencies")
2485 conv.logs[2].check('adding', (
2486 ('/%(trunk)s/interleaved', 'A'),
2487 ('/%(trunk)s/interleaved/file1', 'A'),
2488 ('/%(trunk)s/interleaved/file2', 'A'),
2490 conv.logs[3].check('big commit', (
2491 ('/%(trunk)s/interleaved/file1', 'M'),
2492 ('/%(trunk)s/interleaved/file2', 'M'),
2494 conv.logs[4].check('dependant small commit', (
2495 ('/%(trunk)s/interleaved/file1', 'M'),
2497 conv.logs[5].check('adding', (
2498 ('/%(trunk)s/multi-branch', 'A'),
2499 ('/%(trunk)s/multi-branch/file1', 'A'),
2500 ('/%(trunk)s/multi-branch/file2', 'A'),
2502 conv.logs[6].check(sym_log_msg("branch"), (
2503 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2504 ('/%(branches)s/branch/interleaved', 'D'),
2506 conv.logs[7].check('multi-branch-commit', (
2507 ('/%(trunk)s/multi-branch/file1', 'M'),
2508 ('/%(trunk)s/multi-branch/file2', 'M'),
2509 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2510 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2514 def double_branch_delete():
2515 "fill branches before modifying files on them"
2516 conv = ensure_conversion('double-branch-delete')
2518 # Test for issue #102. The file IMarshalledValue.java is branched,
2519 # deleted, readded on the branch, and then deleted again. If the
2520 # fill for the file on the branch is postponed until after the
2521 # modification, the file will end up live on the branch instead of
2522 # dead! Make sure it happens at the right time.
2524 conv.logs[6].check('JBAS-2436 - Adding LGPL Header2', (
2525 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2528 conv.logs[7].check('JBAS-3025 - Removing dependency', (
2529 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2533 def symbol_mismatches():
2534 "error for conflicting tag/branch"
2536 ensure_conversion(
2537 'symbol-mess',
2538 args=['--symbol-default=strict'],
2539 error_re=r'.*Problems determining how symbols should be converted',
2543 def overlook_symbol_mismatches():
2544 "overlook conflicting tag/branch when --trunk-only"
2546 # This is a test for issue #85.
2548 ensure_conversion('symbol-mess', args=['--trunk-only'])
2551 def force_symbols():
2552 "force symbols to be tags/branches"
2554 conv = ensure_conversion(
2555 'symbol-mess',
2556 args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2557 if conv.path_exists('tags', 'BRANCH') \
2558 or not conv.path_exists('branches', 'BRANCH'):
2559 raise Failure()
2560 if not conv.path_exists('tags', 'TAG') \
2561 or conv.path_exists('branches', 'TAG'):
2562 raise Failure()
2563 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2564 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2565 raise Failure()
2566 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2567 or conv.path_exists('branches', 'MOSTLY_TAG'):
2568 raise Failure()
2571 def commit_blocks_tags():
2572 "commit prevents forced tag"
2574 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2575 ensure_conversion(
2576 'symbol-mess',
2577 args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']),
2578 error_re=(
2579 r'.*The following branches cannot be forced to be tags '
2580 r'because they have commits'
2585 def blocked_excludes():
2586 "error for blocked excludes"
2588 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2589 for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']:
2590 try:
2591 ensure_conversion(
2592 'symbol-mess',
2593 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker]))
2594 raise MissingErrorException()
2595 except Failure:
2596 pass
2599 def unblock_blocked_excludes():
2600 "excluding blocker removes blockage"
2602 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2603 for blocker in ['BRANCH', 'COMMIT']:
2604 ensure_conversion(
2605 'symbol-mess',
2606 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker,
2607 '--exclude=BLOCKING_%s' % blocker]))
2610 def regexp_force_symbols():
2611 "force symbols via regular expressions"
2613 conv = ensure_conversion(
2614 'symbol-mess',
2615 args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2616 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2617 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2618 raise Failure()
2619 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2620 or conv.path_exists('branches', 'MOSTLY_TAG'):
2621 raise Failure()
2624 def heuristic_symbol_default():
2625 "test 'heuristic' symbol default"
2627 conv = ensure_conversion(
2628 'symbol-mess', args=['--symbol-default=heuristic'])
2629 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2630 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2631 raise Failure()
2632 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2633 or conv.path_exists('branches', 'MOSTLY_TAG'):
2634 raise Failure()
2637 def branch_symbol_default():
2638 "test 'branch' symbol default"
2640 conv = ensure_conversion(
2641 'symbol-mess', args=['--symbol-default=branch'])
2642 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2643 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2644 raise Failure()
2645 if conv.path_exists('tags', 'MOSTLY_TAG') \
2646 or not conv.path_exists('branches', 'MOSTLY_TAG'):
2647 raise Failure()
2650 def tag_symbol_default():
2651 "test 'tag' symbol default"
2653 conv = ensure_conversion(
2654 'symbol-mess', args=['--symbol-default=tag'])
2655 if not conv.path_exists('tags', 'MOSTLY_BRANCH') \
2656 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
2657 raise Failure()
2658 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2659 or conv.path_exists('branches', 'MOSTLY_TAG'):
2660 raise Failure()
2663 def symbol_transform():
2664 "test --symbol-transform"
2666 conv = ensure_conversion(
2667 'symbol-mess',
2668 args=[
2669 '--symbol-default=heuristic',
2670 '--symbol-transform=BRANCH:branch',
2671 '--symbol-transform=TAG:tag',
2672 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2674 if not conv.path_exists('branches', 'branch'):
2675 raise Failure()
2676 if not conv.path_exists('tags', 'tag'):
2677 raise Failure()
2678 if not conv.path_exists('branches', 'MOSTLY.BRANCH'):
2679 raise Failure()
2680 if not conv.path_exists('tags', 'MOSTLY.TAG'):
2681 raise Failure()
2684 def write_symbol_info():
2685 "test --write-symbol-info"
2687 expected_lines = [
2688 ['0', '.trunk.',
2689 'trunk', 'trunk', '.'],
2690 ['0', 'BLOCKED_BY_UNNAMED',
2691 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2692 ['0', 'BLOCKING_COMMIT',
2693 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2694 ['0', 'BLOCKED_BY_COMMIT',
2695 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2696 ['0', 'BLOCKING_BRANCH',
2697 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2698 ['0', 'BLOCKED_BY_BRANCH',
2699 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2700 ['0', 'MOSTLY_BRANCH',
2701 '.', '.', '.'],
2702 ['0', 'MOSTLY_TAG',
2703 '.', '.', '.'],
2704 ['0', 'BRANCH_WITH_COMMIT',
2705 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2706 ['0', 'BRANCH',
2707 'branch', 'branches/BRANCH', '.trunk.'],
2708 ['0', 'TAG',
2709 'tag', 'tags/TAG', '.trunk.'],
2710 ['0', 'unlabeled-1.1.12.1.2',
2711 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2713 expected_lines.sort()
2715 symbol_info_file = os.path.join(tmp_dir, 'symbol-mess-symbol-info.txt')
2716 try:
2717 ensure_conversion(
2718 'symbol-mess',
2719 args=[
2720 '--symbol-default=strict',
2721 '--write-symbol-info=%s' % (symbol_info_file,),
2722 '--passes=:CollateSymbolsPass',
2725 raise MissingErrorException()
2726 except Failure:
2727 pass
2728 lines = []
2729 comment_re = re.compile(r'^\s*\#')
2730 for l in open(symbol_info_file, 'r'):
2731 if comment_re.match(l):
2732 continue
2733 lines.append(l.strip().split())
2734 lines.sort()
2735 if lines != expected_lines:
2736 s = ['Symbol info incorrect\n']
2737 differ = Differ()
2738 for diffline in differ.compare(
2739 [' '.join(line) + '\n' for line in expected_lines],
2740 [' '.join(line) + '\n' for line in lines],
2742 s.append(diffline)
2743 raise Failure(''.join(s))
2746 def symbol_hints():
2747 "test --symbol-hints for setting branch/tag"
2749 conv = ensure_conversion(
2750 'symbol-mess', symbol_hints_file='symbol-mess-symbol-hints.txt',
2752 if not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2753 raise Failure()
2754 if not conv.path_exists('tags', 'MOSTLY_TAG'):
2755 raise Failure()
2756 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2757 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2759 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2760 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
2762 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2763 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
2767 def parent_hints():
2768 "test --symbol-hints for setting parent"
2770 conv = ensure_conversion(
2771 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints.txt',
2773 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2774 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
2778 def parent_hints_invalid():
2779 "test --symbol-hints with an invalid parent"
2781 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2782 # this symbol hints file sets the preferred parent to BRANCH
2783 # instead:
2784 conv = ensure_conversion(
2785 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-invalid.txt',
2786 error_re=(
2787 r"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
2792 def parent_hints_wildcards():
2793 "test --symbol-hints wildcards"
2795 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2796 # this symbol hints file sets the preferred parent to BRANCH
2797 # instead:
2798 conv = ensure_conversion(
2799 'symbol-mess',
2800 symbol_hints_file='symbol-mess-parent-hints-wildcards.txt',
2802 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2803 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
2807 def path_hints():
2808 "test --symbol-hints for setting svn paths"
2810 conv = ensure_conversion(
2811 'symbol-mess', symbol_hints_file='symbol-mess-path-hints.txt',
2813 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
2814 ('/trunk', 'A'),
2815 ('/a', 'A'),
2816 ('/a/strange', 'A'),
2817 ('/a/strange/trunk', 'A'),
2818 ('/a/strange/trunk/path', 'A'),
2819 ('/branches', 'A'),
2820 ('/tags', 'A'),
2822 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2823 ('/special', 'A'),
2824 ('/special/tag', 'A'),
2825 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
2827 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2828 ('/special/other', 'A'),
2829 ('/special/other/branch', 'A'),
2830 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
2832 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2833 ('/special/branch', 'A'),
2834 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
2838 def issue_99():
2839 "test problem from issue 99"
2841 conv = ensure_conversion('issue-99')
2844 def issue_100():
2845 "test problem from issue 100"
2847 conv = ensure_conversion('issue-100')
2848 file1 = conv.get_wc('trunk', 'file1.txt')
2849 if file(file1).read() != 'file1.txt<1.2>\n':
2850 raise Failure()
2853 def issue_106():
2854 "test problem from issue 106"
2856 conv = ensure_conversion('issue-106')
2859 def options_option():
2860 "use of the --options option"
2862 conv = ensure_conversion('main', options_file='cvs2svn.options')
2865 def multiproject():
2866 "multiproject conversion"
2868 conv = ensure_conversion(
2869 'main', options_file='cvs2svn-multiproject.options'
2871 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
2872 ('/partial-prune', 'A'),
2873 ('/partial-prune/trunk', 'A'),
2874 ('/partial-prune/branches', 'A'),
2875 ('/partial-prune/tags', 'A'),
2876 ('/partial-prune/releases', 'A'),
2880 def crossproject():
2881 "multiproject conversion with cross-project commits"
2883 conv = ensure_conversion(
2884 'main', options_file='cvs2svn-crossproject.options'
2888 def tag_with_no_revision():
2889 "tag defined but revision is deleted"
2891 conv = ensure_conversion('tag-with-no-revision')
2894 def delete_cvsignore():
2895 "svn:ignore should vanish when .cvsignore does"
2897 # This is issue #81.
2899 conv = ensure_conversion('delete-cvsignore')
2901 wc_tree = conv.get_wc_tree()
2902 props = props_for_path(wc_tree, 'trunk/proj')
2904 if props.has_key('svn:ignore'):
2905 raise Failure()
2908 def repeated_deltatext():
2909 "ignore repeated deltatext blocks with warning"
2911 conv = ensure_conversion('repeated-deltatext')
2912 warning_re = r'.*Deltatext block for revision 1.1 appeared twice'
2913 if not conv.output_found(warning_re):
2914 raise Failure()
2917 def nasty_graphs():
2918 "process some nasty dependency graphs"
2920 # It's not how well the bear can dance, but that the bear can dance
2921 # at all:
2922 conv = ensure_conversion('nasty-graphs')
2925 def tagging_after_delete():
2926 "optimal tag after deleting files"
2928 conv = ensure_conversion('tagging-after-delete')
2930 # tag should be 'clean', no deletes
2931 log = conv.find_tag_log('tag1')
2932 expected = (
2933 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
2935 log.check_changes(expected)
2938 def crossed_branches():
2939 "branches created in inconsistent orders"
2941 conv = ensure_conversion('crossed-branches')
2944 def file_directory_conflict():
2945 "error when filename conflicts with directory name"
2947 conv = ensure_conversion(
2948 'file-directory-conflict',
2949 error_re=r'.*Directory name conflicts with filename',
2953 def attic_directory_conflict():
2954 "error when attic filename conflicts with dirname"
2956 # This tests the problem reported in issue #105.
2958 conv = ensure_conversion(
2959 'attic-directory-conflict',
2960 error_re=r'.*Directory name conflicts with filename',
2964 def internal_co():
2965 "verify that --use-internal-co works"
2967 rcs_conv = ensure_conversion(
2968 'main', args=['--use-rcs', '--default-eol=native'],
2970 conv = ensure_conversion(
2971 'main', args=['--default-eol=native'],
2973 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
2974 raise Failure()
2975 rcs_lines = run_program(
2976 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
2977 rcs_conv.repos)
2978 lines = run_program(
2979 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
2980 conv.repos)
2981 # Compare all lines following the repository UUID:
2982 if lines[3:] != rcs_lines[3:]:
2983 raise Failure()
2986 def internal_co_exclude():
2987 "verify that --use-internal-co --exclude=... works"
2989 rcs_conv = ensure_conversion(
2990 'internal-co',
2991 args=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
2993 conv = ensure_conversion(
2994 'internal-co',
2995 args=['--exclude=BRANCH', '--default-eol=native'],
2997 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
2998 raise Failure()
2999 rcs_lines = run_program(
3000 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3001 rcs_conv.repos)
3002 lines = run_program(
3003 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3004 conv.repos)
3005 # Compare all lines following the repository UUID:
3006 if lines[3:] != rcs_lines[3:]:
3007 raise Failure()
3010 def internal_co_trunk_only():
3011 "verify that --use-internal-co --trunk-only works"
3013 conv = ensure_conversion(
3014 'internal-co',
3015 args=['--trunk-only', '--default-eol=native'],
3017 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3018 raise Failure()
3021 def leftover_revs():
3022 "check for leftover checked-out revisions"
3024 conv = ensure_conversion(
3025 'leftover-revs',
3026 args=['--exclude=BRANCH', '--default-eol=native'],
3028 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3029 raise Failure()
3032 def requires_internal_co():
3033 "test that internal co can do more than RCS"
3034 # See issues 4, 11 for the bugs whose regression we're testing for.
3035 # Unlike in requires_cvs above, issue 29 is not covered.
3036 conv = ensure_conversion('requires-cvs')
3038 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
3040 if atsign_contents[-1:] == "@":
3041 raise Failure()
3043 if not (conv.logs[21].author == "William Lyon Phelps III" and
3044 conv.logs[20].author == "j random"):
3045 raise Failure()
3048 def internal_co_keywords():
3049 "test that internal co handles keywords correctly"
3050 conv_ic = ensure_conversion('internal-co-keywords',
3051 args=["--keywords-off"])
3052 conv_cvs = ensure_conversion('internal-co-keywords',
3053 args=["--use-cvs", "--keywords-off"])
3055 ko_ic = file(conv_ic.get_wc('trunk', 'dir', 'ko.txt')).read()
3056 ko_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'ko.txt')).read()
3057 kk_ic = file(conv_ic.get_wc('trunk', 'dir', 'kk.txt')).read()
3058 kk_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kk.txt')).read()
3059 kv_ic = file(conv_ic.get_wc('trunk', 'dir', 'kv.txt')).read()
3060 kv_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kv.txt')).read()
3062 if ko_ic != ko_cvs:
3063 raise Failure()
3064 if kk_ic != kk_cvs:
3065 raise Failure()
3067 # The date format changed between cvs and co ('/' instead of '-').
3068 # Accept either one:
3069 date_substitution_re = re.compile(r' ([0-9]*)-([0-9]*)-([0-9]*) ')
3070 if kv_ic != kv_cvs \
3071 and date_substitution_re.sub(r' \1/\2/\3 ', kv_ic) != kv_cvs:
3072 raise Failure()
3075 def timestamp_chaos():
3076 "test timestamp adjustments"
3078 conv = ensure_conversion('timestamp-chaos', args=["-v"])
3080 times = [
3081 '2007-01-01 21:00:00', # Initial commit
3082 '2007-01-01 21:00:00', # revision 1.1 of both files
3083 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3084 '2007-01-01 21:00:02', # revision 1.2 of file1.txt, adjusted backwards
3085 '2007-01-01 22:00:00', # revision 1.3 of both files
3087 for i in range(len(times)):
3088 if abs(conv.logs[i + 1].date - time.mktime(svn_strptime(times[i]))) > 0.1:
3089 raise Failure()
3092 def symlinks():
3093 "convert a repository that contains symlinks"
3095 # This is a test for issue #97.
3097 proj = os.path.join(test_data_dir, 'symlinks-cvsrepos', 'proj')
3098 links = [
3100 os.path.join('..', 'file.txt,v'),
3101 os.path.join(proj, 'dir1', 'file.txt,v'),
3104 'dir1',
3105 os.path.join(proj, 'dir2'),
3109 try:
3110 os.symlink
3111 except AttributeError:
3112 # Apparently this OS doesn't support symlinks, so skip test.
3113 raise svntest.Skip()
3115 try:
3116 for (src,dst) in links:
3117 os.symlink(src, dst)
3119 conv = ensure_conversion('symlinks')
3120 conv.logs[2].check('', (
3121 ('/%(trunk)s/proj', 'A'),
3122 ('/%(trunk)s/proj/file.txt', 'A'),
3123 ('/%(trunk)s/proj/dir1', 'A'),
3124 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3125 ('/%(trunk)s/proj/dir2', 'A'),
3126 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3128 finally:
3129 for (src,dst) in links:
3130 os.remove(dst)
3133 def empty_trunk_path():
3134 "allow --trunk to be empty if --trunk-only"
3136 # This is a test for issue #53.
3138 conv = ensure_conversion(
3139 'main', args=['--trunk-only', '--trunk='],
3143 def preferred_parent_cycle():
3144 "handle a cycle in branch parent preferences"
3146 conv = ensure_conversion('preferred-parent-cycle')
3149 def branch_from_empty_dir():
3150 "branch from an empty directory"
3152 conv = ensure_conversion('branch-from-empty-dir')
3155 def trunk_readd():
3156 "add a file on a branch then on trunk"
3158 conv = ensure_conversion('trunk-readd')
3161 def branch_from_deleted_1_1():
3162 "branch from a 1.1 revision that will be deleted"
3164 conv = ensure_conversion('branch-from-deleted-1-1')
3165 conv.logs[5].check('Adding b.txt:1.1.2.1', (
3166 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3168 conv.logs[6].check('Adding b.txt:1.1.4.1', (
3169 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3171 conv.logs[7].check('Adding b.txt:1.2', (
3172 ('/%(trunk)s/proj/b.txt', 'A'),
3175 conv.logs[8].check('Adding c.txt:1.1.2.1', (
3176 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3178 conv.logs[9].check('Adding c.txt:1.1.4.1', (
3179 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3183 def add_on_branch():
3184 "add a file on a branch using newer CVS"
3186 conv = ensure_conversion('add-on-branch')
3187 conv.logs[6].check('Adding b.txt:1.1', (
3188 ('/%(trunk)s/proj/b.txt', 'A'),
3190 conv.logs[7].check('Adding b.txt:1.1.2.2', (
3191 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3193 conv.logs[8].check('Adding c.txt:1.1', (
3194 ('/%(trunk)s/proj/c.txt', 'A'),
3196 conv.logs[9].check('Removing c.txt:1.2', (
3197 ('/%(trunk)s/proj/c.txt', 'D'),
3199 conv.logs[10].check('Adding c.txt:1.2.2.2', (
3200 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3202 conv.logs[11].check('Adding d.txt:1.1', (
3203 ('/%(trunk)s/proj/d.txt', 'A'),
3205 conv.logs[12].check('Adding d.txt:1.1.2.2', (
3206 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3210 def main_git():
3211 "test output in git-fast-import format"
3213 # Note: To test importing into git, do
3215 # ./run-tests <test-number>
3216 # rm -rf .git
3217 # git-init
3218 # cat cvs2svn-tmp/git-{blob,dump}.dat | git-fast-import
3220 # Or, to load the dumpfiles separately:
3222 # cat cvs2svn-tmp/git-blob.dat \
3223 # | git-fast-import --export-marks=cvs2svn-tmp/git-marks.dat
3224 # cat cvs2svn-tmp/git-dump.dat \
3225 # | git-fast-import --import-marks=cvs2svn-tmp/git-marks.dat
3227 # Then use "gitk --all", "git log", etc. to test the contents of the
3228 # repository.
3230 conv = ensure_conversion('main', options_file='cvs2svn-git.options')
3233 def main_git_inline():
3234 "output in git-fast-import format with inline data"
3236 # Note: To test importing into git, do
3238 # ./run-tests <test-number>
3239 # rm -rf .git
3240 # git-init
3241 # cat cvs2svn-tmp/git-dump.dat | git-fast-import
3243 # Then use "gitk --all", "git log", etc. to test the contents of the
3244 # repository.
3246 conv = ensure_conversion('main', options_file='cvs2svn-git-inline.options')
3249 def invalid_symbol():
3250 "a symbol with the incorrect format"
3252 conv = ensure_conversion('invalid-symbol')
3253 if not conv.output_found(
3254 r".*branch 'SYMBOL' references invalid revision 1$"
3256 raise Failure()
3259 def invalid_symbol_ignore():
3260 "ignore a symbol with the incorrect format"
3262 conv = ensure_conversion(
3263 'invalid-symbol', options_file='cvs2svn-ignore.options'
3267 class EOLVariants(Cvs2SvnTestCase):
3268 "handle various --eol-style options"
3270 eol_style_strings = {
3271 'LF' : '\n',
3272 'CR' : '\r',
3273 'CRLF' : '\r\n',
3274 'native' : '\n',
3277 def __init__(self, eol_style):
3278 self.eol_style = eol_style
3279 self.dumpfile = 'eol-variants-%s.dump' % (self.eol_style,)
3280 Cvs2SvnTestCase.__init__(
3281 self, 'eol-variants', variant=self.eol_style,
3282 dumpfile=self.dumpfile,
3283 args=[
3284 '--default-eol=%s' % (self.eol_style,),
3288 def run(self):
3289 conv = self.ensure_conversion()
3290 dump_contents = open(conv.dumpfile, 'rb').read()
3291 expected_text = self.eol_style_strings[self.eol_style].join(
3292 ['line 1', 'line 2', '\n\n']
3294 if not dump_contents.endswith(expected_text):
3295 raise Failure()
3298 def no_revs_file():
3299 "handle a file with no revisions (issue #80)"
3301 conv = ensure_conversion('no-revs-file')
3304 def mirror_keyerror_test():
3305 "a case that gave KeyError in SVNRepositoryMirror"
3307 conv = ensure_conversion('mirror-keyerror')
3310 def exclude_ntdb_test():
3311 "exclude a non-trunk default branch"
3313 symbol_info_file = os.path.join(tmp_dir, 'exclude-ntdb-symbol-info.txt')
3314 conv = ensure_conversion(
3315 'exclude-ntdb',
3316 args=[
3317 '--write-symbol-info=%s' % (symbol_info_file,),
3318 '--exclude=branch3',
3319 '--exclude=tag3',
3320 '--exclude=vendortag3',
3321 '--exclude=vendorbranch',
3326 def mirror_keyerror2_test():
3327 "a case that gave KeyError in RepositoryMirror"
3329 conv = ensure_conversion('mirror-keyerror2')
3332 def mirror_keyerror3_test():
3333 "a case that gave KeyError in RepositoryMirror"
3335 conv = ensure_conversion('mirror-keyerror3')
3338 def add_cvsignore_to_branch_test():
3339 "check adding .cvsignore to an existing branch"
3341 # This a test for issue #122.
3343 conv = ensure_conversion('add-cvsignore-to-branch')
3344 wc_tree = conv.get_wc_tree()
3345 trunk_props = props_for_path(wc_tree, 'trunk/dir')
3346 if trunk_props['svn:ignore'] != '*.o\n\n':
3347 raise Failure()
3349 branch_props = props_for_path(wc_tree, 'branches/BRANCH/dir')
3350 if branch_props['svn:ignore'] != '*.o\n\n':
3351 raise Failure()
3354 ########################################################################
3355 # Run the tests
3357 # list all tests here, starting with None:
3358 test_list = [
3359 None,
3360 # 1:
3361 show_usage,
3362 attr_exec,
3363 space_fname,
3364 two_quick,
3365 PruneWithCare(),
3366 PruneWithCare(variant=1, trunk='a', branches='b', tags='c'),
3367 PruneWithCare(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3368 PruneWithCare(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3369 interleaved_commits,
3370 # 10:
3371 simple_commits,
3372 SimpleTags(),
3373 SimpleTags(variant=1, trunk='a', branches='b', tags='c'),
3374 SimpleTags(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3375 SimpleTags(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3376 simple_branch_commits,
3377 mixed_time_tag,
3378 mixed_time_branch_with_added_file,
3379 mixed_commit,
3380 split_time_branch,
3381 # 20:
3382 bogus_tag,
3383 overlapping_branch,
3384 PhoenixBranch(),
3385 PhoenixBranch(variant=1, trunk='a/1', branches='b/1', tags='c/1'),
3386 ctrl_char_in_log,
3387 overdead,
3388 NoTrunkPrune(),
3389 NoTrunkPrune(variant=1, trunk='a', branches='b', tags='c'),
3390 NoTrunkPrune(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3391 NoTrunkPrune(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3392 # 30:
3393 double_delete,
3394 split_branch,
3395 resync_misgroups,
3396 TaggedBranchAndTrunk(),
3397 TaggedBranchAndTrunk(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3398 enroot_race,
3399 enroot_race_obo,
3400 BranchDeleteFirst(),
3401 BranchDeleteFirst(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3402 nonascii_filenames,
3403 # 40:
3404 UnicodeAuthor(
3405 warning_expected=1),
3406 UnicodeAuthor(
3407 warning_expected=0,
3408 variant='encoding', args=['--encoding=utf_8']),
3409 UnicodeAuthor(
3410 warning_expected=0,
3411 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
3412 UnicodeLog(
3413 warning_expected=1),
3414 UnicodeLog(
3415 warning_expected=0,
3416 variant='encoding', args=['--encoding=utf_8']),
3417 UnicodeLog(
3418 warning_expected=0,
3419 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
3420 vendor_branch_sameness,
3421 vendor_branch_trunk_only,
3422 default_branches,
3423 default_branches_trunk_only,
3424 # 50:
3425 default_branch_and_1_2,
3426 compose_tag_three_sources,
3427 pass5_when_to_fill,
3428 PeerPathPruning(),
3429 PeerPathPruning(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3430 EmptyTrunk(),
3431 EmptyTrunk(variant=1, trunk='a', branches='b', tags='c'),
3432 EmptyTrunk(variant=2, trunk='a/1', branches='a/2', tags='a/3'),
3433 no_spurious_svn_commits,
3434 invalid_closings_on_trunk,
3435 # 60:
3436 individual_passes,
3437 resync_bug,
3438 branch_from_default_branch,
3439 file_in_attic_too,
3440 retain_file_in_attic_too,
3441 symbolic_name_filling_guide,
3442 eol_mime1,
3443 eol_mime2,
3444 eol_mime3,
3445 eol_mime4,
3446 # 70:
3447 cvs_revnums_off,
3448 cvs_revnums_on,
3449 keywords,
3450 ignore,
3451 requires_cvs,
3452 questionable_branch_names,
3453 questionable_tag_names,
3454 revision_reorder_bug,
3455 exclude,
3456 vendor_branch_delete_add,
3457 # 80:
3458 resync_pass2_pull_forward,
3459 native_eol,
3460 double_fill,
3461 XFail(double_fill2),
3462 resync_pass2_push_backward,
3463 double_add,
3464 bogus_branch_copy,
3465 nested_ttb_directories,
3466 auto_props_ignore_case,
3467 ctrl_char_in_filename,
3468 # 90:
3469 commit_dependencies,
3470 show_help_passes,
3471 multiple_tags,
3472 multiply_defined_symbols,
3473 multiply_defined_symbols_renamed,
3474 multiply_defined_symbols_ignored,
3475 repeatedly_defined_symbols,
3476 double_branch_delete,
3477 symbol_mismatches,
3478 overlook_symbol_mismatches,
3479 # 100:
3480 force_symbols,
3481 commit_blocks_tags,
3482 blocked_excludes,
3483 unblock_blocked_excludes,
3484 regexp_force_symbols,
3485 heuristic_symbol_default,
3486 branch_symbol_default,
3487 tag_symbol_default,
3488 symbol_transform,
3489 write_symbol_info,
3490 # 110:
3491 symbol_hints,
3492 parent_hints,
3493 parent_hints_invalid,
3494 parent_hints_wildcards,
3495 path_hints,
3496 issue_99,
3497 issue_100,
3498 issue_106,
3499 options_option,
3500 multiproject,
3501 # 120:
3502 crossproject,
3503 tag_with_no_revision,
3504 delete_cvsignore,
3505 repeated_deltatext,
3506 nasty_graphs,
3507 XFail(tagging_after_delete),
3508 crossed_branches,
3509 file_directory_conflict,
3510 attic_directory_conflict,
3511 internal_co,
3512 # 130:
3513 internal_co_exclude,
3514 internal_co_trunk_only,
3515 internal_co_keywords,
3516 leftover_revs,
3517 requires_internal_co,
3518 timestamp_chaos,
3519 symlinks,
3520 empty_trunk_path,
3521 preferred_parent_cycle,
3522 branch_from_empty_dir,
3523 # 140:
3524 trunk_readd,
3525 branch_from_deleted_1_1,
3526 add_on_branch,
3527 XFail(main_git),
3528 XFail(main_git_inline),
3529 invalid_symbol,
3530 invalid_symbol_ignore,
3531 EOLVariants('LF'),
3532 EOLVariants('CR'),
3533 EOLVariants('CRLF'),
3534 # 150:
3535 EOLVariants('native'),
3536 no_revs_file,
3537 mirror_keyerror_test,
3538 exclude_ntdb_test,
3539 mirror_keyerror2_test,
3540 mirror_keyerror3_test,
3541 XFail(add_cvsignore_to_branch_test),
3544 if __name__ == '__main__':
3546 # Configure the environment for reproducable output from svn, etc.
3547 # I have no idea if this works on Windows too.
3548 os.environ["LC_ALL"] = "C"
3549 os.environ["TZ"] = "UTC"
3551 # The Subversion test suite code assumes it's being invoked from
3552 # within a working copy of the Subversion sources, and tries to use
3553 # the binaries in that tree. Since the cvs2svn tree never contains
3554 # a Subversion build, we just use the system's installed binaries.
3555 svntest.main.svn_binary = 'svn'
3556 svntest.main.svnlook_binary = 'svnlook'
3557 svntest.main.svnadmin_binary = 'svnadmin'
3558 svntest.main.svnversion_binary = 'svnversion'
3560 run_tests(test_list)
3561 # NOTREACHED
3564 ### End of file.