Avoid sorting a single file.
[cvs2svn.git] / run-tests.py
blob0389b79d57be2b45b0caa6ef8720b0647e754596
1 #!/usr/bin/env python
3 # run_tests.py: test suite for cvs2svn
5 # Usage: run_tests.py [-v | --verbose] [list | <num>]
7 # Options:
8 # -v, --verbose
9 # enable verbose output
11 # Arguments (at most one argument is allowed):
12 # list
13 # If the word "list" is passed as an argument, the list of
14 # available tests is printed (but no tests are run).
16 # <num>
17 # If a number is passed as an argument, then only the test
18 # with that number is run.
20 # If no argument is specified, then all tests are run.
22 # Subversion is a tool for revision control.
23 # See http://subversion.tigris.org for more information.
25 # ====================================================================
26 # Copyright (c) 2000-2009 CollabNet. All rights reserved.
28 # This software is licensed as described in the file COPYING, which
29 # you should have received as part of this distribution. The terms
30 # are also available at http://subversion.tigris.org/license-1.html.
31 # If newer versions of this license are posted there, you may use a
32 # newer version instead, at your option.
34 ######################################################################
36 # General modules
37 import sys
38 import shutil
39 import stat
40 import re
41 import os
42 import time
43 import os.path
44 import locale
45 import textwrap
46 import calendar
47 import types
48 try:
49 from hashlib import md5
50 except ImportError:
51 from md5 import md5
52 from difflib import Differ
54 # Make sure that a supported version of Python is being used:
55 if not (0x02040000 <= sys.hexversion < 0x03000000):
56 sys.stderr.write(
57 'error: Python 2, version 2.4 or higher required.\n'
59 sys.exit(1)
61 # This script needs to run in the correct directory. Make sure we're there.
62 if not (os.path.exists('cvs2svn') and os.path.exists('test-data')):
63 sys.stderr.write("error: I need to be run in the directory containing "
64 "'cvs2svn' and 'test-data'.\n")
65 sys.exit(1)
67 # Load the Subversion test framework.
68 import svntest
69 from svntest import Failure
70 from svntest.main import safe_rmtree
71 from svntest.testcase import TestCase
72 from svntest.testcase import XFail
74 # Test if Mercurial >= 1.1 is available.
75 try:
76 from mercurial import context
77 context.memctx
78 have_hg = True
79 except (ImportError, AttributeError):
80 have_hg = False
82 cvs2svn = os.path.abspath('cvs2svn')
83 cvs2git = os.path.abspath('cvs2git')
84 cvs2hg = os.path.abspath('cvs2hg')
86 # We use the installed svn and svnlook binaries, instead of using
87 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
88 # behavior -- or even existence -- of local builds shouldn't affect
89 # the cvs2svn test suite.
90 svn_binary = 'svn'
91 svnlook_binary = 'svnlook'
92 svnadmin_binary = 'svnadmin'
93 svnversion_binary = 'svnversion'
95 test_data_dir = 'test-data'
96 tmp_dir = 'cvs2svn-tmp'
99 #----------------------------------------------------------------------
100 # Helpers.
101 #----------------------------------------------------------------------
104 # The value to expect for svn:keywords if it is set:
105 KEYWORDS = 'Author Date Id Revision'
108 class RunProgramException(Failure):
109 pass
112 class MissingErrorException(Failure):
113 def __init__(self, error_re):
114 Failure.__init__(
115 self, "Test failed because no error matched '%s'" % (error_re,)
119 def run_program(program, error_re, *varargs):
120 """Run PROGRAM with VARARGS, return stdout as a list of lines.
122 If there is any stderr and ERROR_RE is None, raise
123 RunProgramException, and print the stderr lines if
124 svntest.main.options.verbose is true.
126 If ERROR_RE is not None, it is a string regular expression that must
127 match some line of stderr. If it fails to match, raise
128 MissingErrorExpection."""
130 # FIXME: exit_code is currently ignored.
131 exit_code, out, err = svntest.main.run_command(program, 1, 0, *varargs)
133 if error_re:
134 # Specified error expected on stderr.
135 if not err:
136 raise MissingErrorException(error_re)
137 else:
138 for line in err:
139 if re.match(error_re, line):
140 return out
141 raise MissingErrorException(error_re)
142 else:
143 # No stderr allowed.
144 if err:
145 if svntest.main.options.verbose:
146 print '\n%s said:\n' % program
147 for line in err:
148 print ' ' + line,
149 print
150 raise RunProgramException()
152 return out
155 def run_script(script, error_re, *varargs):
156 """Run Python script SCRIPT with VARARGS, returning stdout as a list
157 of lines.
159 If there is any stderr and ERROR_RE is None, raise
160 RunProgramException, and print the stderr lines if
161 svntest.main.options.verbose is true.
163 If ERROR_RE is not None, it is a string regular expression that must
164 match some line of stderr. If it fails to match, raise
165 MissingErrorException."""
167 # Use the same python that is running this script
168 return run_program(sys.executable, error_re, script, *varargs)
169 # On Windows, for an unknown reason, the cmd.exe process invoked by
170 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
171 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
172 # this. Therefore, the redirection of the output to the .s-revs file fails.
173 # We no longer use the problematic invocation on any system, but this
174 # comment remains to warn about this problem.
177 def run_svn(*varargs):
178 """Run svn with VARARGS; return stdout as a list of lines.
179 If there is any stderr, raise RunProgramException, and print the
180 stderr lines if svntest.main.options.verbose is true."""
181 return run_program(svn_binary, None, *varargs)
184 def repos_to_url(path_to_svn_repos):
185 """This does what you think it does."""
186 rpath = os.path.abspath(path_to_svn_repos)
187 if rpath[0] != '/':
188 rpath = '/' + rpath
189 return 'file://%s' % rpath.replace(os.sep, '/')
192 def svn_strptime(timestr):
193 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
196 class Log:
197 def __init__(self, revision, author, date, symbols):
198 self.revision = revision
199 self.author = author
201 # Internally, we represent the date as seconds since epoch (UTC).
202 # Since standard subversion log output shows dates in localtime
204 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
206 # and time.mktime() converts from localtime, it all works out very
207 # happily.
208 self.date = time.mktime(svn_strptime(date[0:19]))
210 # The following symbols are used for string interpolation when
211 # checking paths:
212 self.symbols = symbols
214 # The changed paths will be accumulated later, as log data is read.
215 # Keys here are paths such as '/trunk/foo/bar', values are letter
216 # codes such as 'M', 'A', and 'D'.
217 self.changed_paths = { }
219 # The msg will be accumulated later, as log data is read.
220 self.msg = ''
222 def absorb_changed_paths(self, out):
223 'Read changed paths from OUT into self, until no more.'
224 while 1:
225 line = out.readline()
226 if len(line) == 1: return
227 line = line[:-1]
228 op_portion = line[3:4]
229 path_portion = line[5:]
230 # If we're running on Windows we get backslashes instead of
231 # forward slashes.
232 path_portion = path_portion.replace('\\', '/')
233 # # We could parse out history information, but currently we
234 # # just leave it in the path portion because that's how some
235 # # tests expect it.
237 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
238 # if m:
239 # path_portion = m.group(1)
240 self.changed_paths[path_portion] = op_portion
242 def __cmp__(self, other):
243 return cmp(self.revision, other.revision) or \
244 cmp(self.author, other.author) or cmp(self.date, other.date) or \
245 cmp(self.changed_paths, other.changed_paths) or \
246 cmp(self.msg, other.msg)
248 def get_path_op(self, path):
249 """Return the operator for the change involving PATH.
251 PATH is allowed to include string interpolation directives (e.g.,
252 '%(trunk)s'), which are interpolated against self.symbols. Return
253 None if there is no record for PATH."""
254 return self.changed_paths.get(path % self.symbols)
256 def check_msg(self, msg):
257 """Verify that this Log's message starts with the specified MSG."""
258 if self.msg.find(msg) != 0:
259 raise Failure(
260 "Revision %d log message was:\n%s\n\n"
261 "It should have begun with:\n%s\n\n"
262 % (self.revision, self.msg, msg,)
265 def check_change(self, path, op):
266 """Verify that this Log includes a change for PATH with operator OP.
268 PATH is allowed to include string interpolation directives (e.g.,
269 '%(trunk)s'), which are interpolated against self.symbols."""
271 path = path % self.symbols
272 found_op = self.changed_paths.get(path, None)
273 if found_op is None:
274 raise Failure(
275 "Revision %d does not include change for path %s "
276 "(it should have been %s).\n"
277 % (self.revision, path, op,)
279 if found_op != op:
280 raise Failure(
281 "Revision %d path %s had op %s (it should have been %s)\n"
282 % (self.revision, path, found_op, op,)
285 def check_changes(self, changed_paths):
286 """Verify that this Log has precisely the CHANGED_PATHS specified.
288 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
289 strings are allowed to include string interpolation directives
290 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
292 cp = {}
293 for (path, op) in changed_paths:
294 cp[path % self.symbols] = op
296 if self.changed_paths != cp:
297 raise Failure(
298 "Revision %d changed paths list was:\n%s\n\n"
299 "It should have been:\n%s\n\n"
300 % (self.revision, self.changed_paths, cp,)
303 def check(self, msg, changed_paths):
304 """Verify that this Log has the MSG and CHANGED_PATHS specified.
306 Convenience function to check two things at once. MSG is passed
307 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
309 self.check_msg(msg)
310 self.check_changes(changed_paths)
313 def parse_log(svn_repos, symbols):
314 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
316 Initialize the Logs' symbols with SYMBOLS."""
318 class LineFeeder:
319 'Make a list of lines behave like an open file handle.'
320 def __init__(self, lines):
321 self.lines = lines
322 def readline(self):
323 if len(self.lines) > 0:
324 return self.lines.pop(0)
325 else:
326 return None
328 def absorb_message_body(out, num_lines, log):
329 """Read NUM_LINES of log message body from OUT into Log item LOG."""
331 for i in range(num_lines):
332 log.msg += out.readline()
334 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
335 '(?P<author>[^\|]+) \| '
336 '(?P<date>[^\|]+) '
337 '\| (?P<lines>[0-9]+) (line|lines)$')
339 log_separator = '-' * 72
341 logs = { }
343 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
345 while 1:
346 this_log = None
347 line = out.readline()
348 if not line: break
349 line = line[:-1]
351 if line.find(log_separator) == 0:
352 line = out.readline()
353 if not line: break
354 line = line[:-1]
355 m = log_start_re.match(line)
356 if m:
357 this_log = Log(
358 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
359 line = out.readline()
360 if not line.find('Changed paths:') == 0:
361 print 'unexpected log output (missing changed paths)'
362 print "Line: '%s'" % line
363 sys.exit(1)
364 this_log.absorb_changed_paths(out)
365 absorb_message_body(out, int(m.group('lines')), this_log)
366 logs[this_log.revision] = this_log
367 elif len(line) == 0:
368 break # We've reached the end of the log output.
369 else:
370 print 'unexpected log output (missing revision line)'
371 print "Line: '%s'" % line
372 sys.exit(1)
373 else:
374 print 'unexpected log output (missing log separator)'
375 print "Line: '%s'" % line
376 sys.exit(1)
378 return logs
381 def erase(path):
382 """Unconditionally remove PATH and its subtree, if any. PATH may be
383 non-existent, a file or symlink, or a directory."""
384 if os.path.isdir(path):
385 safe_rmtree(path)
386 elif os.path.exists(path):
387 os.remove(path)
390 log_msg_text_wrapper = textwrap.TextWrapper(width=76, break_long_words=False)
392 def sym_log_msg(symbolic_name, is_tag=None):
393 """Return the expected log message for a cvs2svn-synthesized revision
394 creating branch or tag SYMBOLIC_NAME."""
396 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
397 if is_tag:
398 type = 'tag'
399 else:
400 type = 'branch'
402 return log_msg_text_wrapper.fill(
403 "This commit was manufactured by cvs2svn to create %s '%s'."
404 % (type, symbolic_name)
408 def make_conversion_id(
409 name, args, passbypass, options_file=None, symbol_hints_file=None
411 """Create an identifying tag for a conversion.
413 The return value can also be used as part of a filesystem path.
415 NAME is the name of the CVS repository.
417 ARGS are the extra arguments to be passed to cvs2svn.
419 PASSBYPASS is a boolean indicating whether the conversion is to be
420 run one pass at a time.
422 If OPTIONS_FILE is specified, it is an options file that will be
423 used for the conversion.
425 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
426 will be used for the conversion.
428 The 1-to-1 mapping between cvs2svn command parameters and
429 conversion_ids allows us to avoid running the same conversion more
430 than once, when multiple tests use exactly the same conversion."""
432 conv_id = name
434 args = args[:]
436 if passbypass:
437 args.append('--passbypass')
439 if symbol_hints_file is not None:
440 args.append('--symbol-hints=%s' % (symbol_hints_file,))
442 # There are some characters that are forbidden in filenames, and
443 # there is a limit on the total length of a path to a file. So use
444 # a hash of the parameters rather than concatenating the parameters
445 # into a string.
446 if args:
447 conv_id += "-" + md5('\0'.join(args)).hexdigest()
449 # Some options-file based tests rely on knowing the paths to which
450 # the repository should be written, so we handle that option as a
451 # predictable string:
452 if options_file is not None:
453 conv_id += '--options=%s' % (options_file,)
455 return conv_id
458 class Conversion:
459 """A record of a cvs2svn conversion.
461 Fields:
463 conv_id -- the conversion id for this Conversion.
465 name -- a one-word name indicating the involved repositories.
467 dumpfile -- the name of the SVN dumpfile created by the conversion
468 (if the DUMPFILE constructor argument was used); otherwise,
469 None.
471 repos -- the path to the svn repository. Unset if DUMPFILE was
472 specified.
474 logs -- a dictionary of Log instances, as returned by parse_log().
475 Unset if DUMPFILE was specified.
477 symbols -- a dictionary of symbols used for string interpolation
478 in path names.
480 stdout -- a list of lines written by cvs2svn to stdout
482 _wc -- the basename of the svn working copy (within tmp_dir).
483 Unset if DUMPFILE was specified.
485 _wc_path -- the path to the svn working copy, if it has already
486 been created; otherwise, None. (The working copy is created
487 lazily when get_wc() is called.) Unset if DUMPFILE was
488 specified.
490 _wc_tree -- the tree built from the svn working copy, if it has
491 already been created; otherwise, None. The tree is created
492 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
493 specified.
495 _svnrepos -- the basename of the svn repository (within tmp_dir).
496 Unset if DUMPFILE was specified."""
498 # The number of the last cvs2svn pass (determined lazily by
499 # get_last_pass()).
500 last_pass = None
502 @classmethod
503 def get_last_pass(cls):
504 """Return the number of cvs2svn's last pass."""
506 if cls.last_pass is None:
507 out = run_script(cvs2svn, None, '--help-passes')
508 cls.last_pass = int(out[-1].split()[0])
509 return cls.last_pass
511 def __init__(
512 self, conv_id, name, error_re, passbypass, symbols, args,
513 options_file=None, symbol_hints_file=None, dumpfile=None,
515 self.conv_id = conv_id
516 self.name = name
517 self.symbols = symbols
518 if not os.path.isdir(tmp_dir):
519 os.mkdir(tmp_dir)
521 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
523 if dumpfile:
524 self.dumpfile = os.path.join(tmp_dir, dumpfile)
525 # Clean up from any previous invocations of this script.
526 erase(self.dumpfile)
527 else:
528 self.dumpfile = None
529 self.repos = os.path.join(tmp_dir, '%s-svnrepos' % self.conv_id)
530 self._wc = os.path.join(tmp_dir, '%s-wc' % self.conv_id)
531 self._wc_path = None
532 self._wc_tree = None
534 # Clean up from any previous invocations of this script.
535 erase(self.repos)
536 erase(self._wc)
538 args = list(args)
539 if options_file:
540 self.options_file = os.path.join(cvsrepos, options_file)
541 args.extend([
542 '--options=%s' % self.options_file,
544 assert not symbol_hints_file
545 else:
546 self.options_file = None
547 if tmp_dir != 'cvs2svn-tmp':
548 # Only include this argument if it differs from cvs2svn's default:
549 args.extend([
550 '--tmpdir=%s' % tmp_dir,
553 if symbol_hints_file:
554 self.symbol_hints_file = os.path.join(cvsrepos, symbol_hints_file)
555 args.extend([
556 '--symbol-hints=%s' % self.symbol_hints_file,
559 if self.dumpfile:
560 args.extend(['--dumpfile=%s' % (self.dumpfile,)])
561 else:
562 args.extend(['-s', self.repos])
563 args.extend([cvsrepos])
565 if passbypass:
566 self.stdout = []
567 for p in range(1, self.get_last_pass() + 1):
568 self.stdout += run_script(cvs2svn, error_re, '-p', str(p), *args)
569 else:
570 self.stdout = run_script(cvs2svn, error_re, *args)
572 if self.dumpfile:
573 if not os.path.isfile(self.dumpfile):
574 raise Failure(
575 "Dumpfile not created: '%s'"
576 % os.path.join(os.getcwd(), self.dumpfile)
578 else:
579 if os.path.isdir(self.repos):
580 self.logs = parse_log(self.repos, self.symbols)
581 elif error_re is None:
582 raise Failure(
583 "Repository not created: '%s'"
584 % os.path.join(os.getcwd(), self.repos)
587 def output_found(self, pattern):
588 """Return True if PATTERN matches any line in self.stdout.
590 PATTERN is a regular expression pattern as a string.
593 pattern_re = re.compile(pattern)
595 for line in self.stdout:
596 if pattern_re.match(line):
597 # We found the pattern that we were looking for.
598 return 1
599 else:
600 return 0
602 def find_tag_log(self, tagname):
603 """Search LOGS for a log message containing 'TAGNAME' and return the
604 log in which it was found."""
605 for i in xrange(len(self.logs), 0, -1):
606 if self.logs[i].msg.find("'"+tagname+"'") != -1:
607 return self.logs[i]
608 raise ValueError("Tag %s not found in logs" % tagname)
610 def get_wc(self, *args):
611 """Return the path to the svn working copy, or a path within the WC.
613 If a working copy has not been created yet, create it now.
615 If ARGS are specified, then they should be strings that form
616 fragments of a path within the WC. They are joined using
617 os.path.join() and appended to the WC path."""
619 if self._wc_path is None:
620 run_svn('co', repos_to_url(self.repos), self._wc)
621 self._wc_path = self._wc
622 return os.path.join(self._wc_path, *args)
624 def get_wc_tree(self):
625 if self._wc_tree is None:
626 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
627 return self._wc_tree
629 def path_exists(self, *args):
630 """Return True if the specified path exists within the repository.
632 (The strings in ARGS are first joined into a path using
633 os.path.join().)"""
635 return os.path.exists(self.get_wc(*args))
637 def check_props(self, keys, checks):
638 """Helper function for checking lots of properties. For a list of
639 files in the conversion, check that the values of the properties
640 listed in KEYS agree with those listed in CHECKS. CHECKS is a
641 list of tuples: [ (filename, [value, value, ...]), ...], where the
642 values are listed in the same order as the key names are listed in
643 KEYS."""
645 for (file, values) in checks:
646 assert len(values) == len(keys)
647 props = props_for_path(self.get_wc_tree(), file)
648 for i in range(len(keys)):
649 if props.get(keys[i]) != values[i]:
650 raise Failure(
651 "File %s has property %s set to \"%s\" "
652 "(it should have been \"%s\").\n"
653 % (file, keys[i], props.get(keys[i]), values[i],)
657 class GitConversion:
658 """A record of a cvs2svn conversion.
660 Fields:
662 name -- a one-word name indicating the CVS repository to be converted.
664 stdout -- a list of lines written by cvs2svn to stdout."""
666 def __init__(self, name, error_re, args, options_file=None):
667 self.name = name
668 if not os.path.isdir(tmp_dir):
669 os.mkdir(tmp_dir)
671 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
673 args = list(args)
674 if options_file:
675 self.options_file = os.path.join(cvsrepos, options_file)
676 args.extend([
677 '--options=%s' % self.options_file,
679 else:
680 self.options_file = None
682 self.stdout = run_script(cvs2git, error_re, *args)
685 # Cache of conversions that have already been done. Keys are conv_id;
686 # values are Conversion instances.
687 already_converted = { }
689 def ensure_conversion(
690 name, error_re=None, passbypass=None,
691 trunk=None, branches=None, tags=None,
692 args=None, options_file=None, symbol_hints_file=None, dumpfile=None,
694 """Convert CVS repository NAME to Subversion, but only if it has not
695 been converted before by this invocation of this script. If it has
696 been converted before, return the Conversion object from the
697 previous invocation.
699 If no error, return a Conversion instance.
701 If ERROR_RE is a string, it is a regular expression expected to
702 match some line of stderr printed by the conversion. If there is an
703 error and ERROR_RE is not set, then raise Failure.
705 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
706 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
708 NAME is just one word. For example, 'main' would mean to convert
709 './test-data/main-cvsrepos', and after the conversion, the resulting
710 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
711 a checked out head working copy in './cvs2svn-tmp/main-wc'.
713 Any other options to pass to cvs2svn should be in ARGS, each element
714 being one option, e.g., '--trunk-only'. If the option takes an
715 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
716 are passed to cvs2svn in the order that they appear in ARGS.
718 If OPTIONS_FILE is specified, then it should be the name of a file
719 within the main directory of the cvs repository associated with this
720 test. It is passed to cvs2svn using the --options option (which
721 suppresses some other options that are incompatible with --options).
723 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
724 file within the main directory of the cvs repository associated with
725 this test. It is passed to cvs2svn using the --symbol-hints option.
727 If DUMPFILE is specified, then it is the name of a dumpfile within
728 the temporary directory to which the conversion output should be
729 written."""
731 if args is None:
732 args = []
733 else:
734 args = list(args)
736 if trunk is None:
737 trunk = 'trunk'
738 else:
739 args.append('--trunk=%s' % (trunk,))
741 if branches is None:
742 branches = 'branches'
743 else:
744 args.append('--branches=%s' % (branches,))
746 if tags is None:
747 tags = 'tags'
748 else:
749 args.append('--tags=%s' % (tags,))
751 conv_id = make_conversion_id(
752 name, args, passbypass, options_file, symbol_hints_file
755 if conv_id not in already_converted:
756 try:
757 # Run the conversion and store the result for the rest of this
758 # session:
759 already_converted[conv_id] = Conversion(
760 conv_id, name, error_re, passbypass,
761 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
762 args, options_file, symbol_hints_file, dumpfile,
764 except Failure:
765 # Remember the failure so that a future attempt to run this conversion
766 # does not bother to retry, but fails immediately.
767 already_converted[conv_id] = None
768 raise
770 conv = already_converted[conv_id]
771 if conv is None:
772 raise Failure()
773 return conv
776 class Cvs2SvnTestFunction(TestCase):
777 """A TestCase based on a naked Python function object.
779 FUNC should be a function that returns None on success and throws an
780 svntest.Failure exception on failure. It should have a brief
781 docstring describing what it does (and fulfilling certain
782 conditions). FUNC must take no arguments.
784 This class is almost identical to svntest.testcase.FunctionTestCase,
785 except that the test function does not require a sandbox and does
786 not accept any parameter (not even sandbox=None).
788 This class can be used as an annotation on a Python function.
792 def __init__(self, func):
793 # it better be a function that accepts no parameters and has a
794 # docstring on it.
795 assert isinstance(func, types.FunctionType)
797 name = func.func_name
799 assert func.func_code.co_argcount == 0, \
800 '%s must not take any arguments' % name
802 doc = func.__doc__.strip()
803 assert doc, '%s must have a docstring' % name
805 # enforce stylistic guidelines for the function docstrings:
806 # - no longer than 50 characters
807 # - should not end in a period
808 # - should not be capitalized
809 assert len(doc) <= 50, \
810 "%s's docstring must be 50 characters or less" % name
811 assert doc[-1] != '.', \
812 "%s's docstring should not end in a period" % name
813 assert doc[0].lower() == doc[0], \
814 "%s's docstring should not be capitalized" % name
816 TestCase.__init__(self, doc=doc)
817 self.func = func
819 def get_function_name(self):
820 return self.func.func_name
822 def get_sandbox_name(self):
823 return None
825 def run(self, sandbox):
826 return self.func()
829 class Cvs2HgTestFunction(Cvs2SvnTestFunction):
830 """Same as Cvs2SvnTestFunction, but for test cases that should be
831 skipped if Mercurial is not available.
833 def run(self, sandbox):
834 if not have_hg:
835 raise svntest.Skip()
836 else:
837 return self.func()
840 class Cvs2SvnTestCase(TestCase):
841 def __init__(
842 self, name, doc=None, variant=None,
843 error_re=None, passbypass=None,
844 trunk=None, branches=None, tags=None,
845 args=None,
846 options_file=None, symbol_hints_file=None, dumpfile=None,
848 self.name = name
850 if doc is None:
851 # By default, use the first line of the class docstring as the
852 # doc:
853 doc = self.__doc__.splitlines()[0]
855 if variant is not None:
856 # Modify doc to show the variant. Trim doc first if necessary
857 # to stay within the 50-character limit.
858 suffix = '...variant %s' % (variant,)
859 doc = doc[:50 - len(suffix)] + suffix
861 TestCase.__init__(self, doc=doc)
863 self.error_re = error_re
864 self.passbypass = passbypass
865 self.trunk = trunk
866 self.branches = branches
867 self.tags = tags
868 self.args = args
869 self.options_file = options_file
870 self.symbol_hints_file = symbol_hints_file
871 self.dumpfile = dumpfile
873 def ensure_conversion(self):
874 return ensure_conversion(
875 self.name,
876 error_re=self.error_re, passbypass=self.passbypass,
877 trunk=self.trunk, branches=self.branches, tags=self.tags,
878 args=self.args,
879 options_file=self.options_file,
880 symbol_hints_file=self.symbol_hints_file,
881 dumpfile=self.dumpfile,
884 def get_sandbox_name(self):
885 return None
888 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase):
889 """Test properties resulting from a conversion."""
891 def __init__(self, name, props_to_test, expected_props, **kw):
892 """Initialize an instance of Cvs2SvnPropertiesTestCase.
894 NAME is the name of the test, passed to Cvs2SvnTestCase.
895 PROPS_TO_TEST is a list of the names of svn properties that should
896 be tested. EXPECTED_PROPS is a list of tuples [(filename,
897 [value,...])], where the second item in each tuple is a list of
898 values expected for the properties listed in PROPS_TO_TEST for the
899 specified filename. If a property must *not* be set, then its
900 value should be listed as None."""
902 Cvs2SvnTestCase.__init__(self, name, **kw)
903 self.props_to_test = props_to_test
904 self.expected_props = expected_props
906 def run(self, sbox):
907 conv = self.ensure_conversion()
908 conv.check_props(self.props_to_test, self.expected_props)
911 #----------------------------------------------------------------------
912 # Tests.
913 #----------------------------------------------------------------------
916 @Cvs2SvnTestFunction
917 def show_usage():
918 "cvs2svn with no arguments shows usage"
919 out = run_script(cvs2svn, None)
920 if (len(out) > 2 and out[0].find('ERROR:') == 0
921 and out[1].find('DBM module')):
922 print 'cvs2svn cannot execute due to lack of proper DBM module.'
923 print 'Exiting without running any further tests.'
924 sys.exit(1)
925 if out[0].find('Usage:') < 0:
926 raise Failure('Basic cvs2svn invocation failed.')
929 @Cvs2SvnTestFunction
930 def cvs2svn_manpage():
931 "generate a manpage for cvs2svn"
932 out = run_script(cvs2svn, None, '--man')
935 @Cvs2SvnTestFunction
936 def cvs2git_manpage():
937 "generate a manpage for cvs2git"
938 out = run_script(cvs2git, None, '--man')
941 @Cvs2HgTestFunction
942 def cvs2hg_manpage():
943 "generate a manpage for cvs2hg"
944 out = run_script(cvs2hg, None, '--man')
947 @Cvs2SvnTestFunction
948 def show_help_passes():
949 "cvs2svn --help-passes shows pass information"
950 out = run_script(cvs2svn, None, '--help-passes')
951 if out[0].find('PASSES') < 0:
952 raise Failure('cvs2svn --help-passes failed.')
955 @Cvs2SvnTestFunction
956 def attr_exec():
957 "detection of the executable flag"
958 if sys.platform == 'win32':
959 raise svntest.Skip()
960 conv = ensure_conversion('main')
961 st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec'))
962 if not st.st_mode & stat.S_IXUSR:
963 raise Failure()
966 @Cvs2SvnTestFunction
967 def space_fname():
968 "conversion of filename with a space"
969 conv = ensure_conversion('main')
970 if not conv.path_exists('trunk', 'single-files', 'space fname'):
971 raise Failure()
974 @Cvs2SvnTestFunction
975 def two_quick():
976 "two commits in quick succession"
977 conv = ensure_conversion('main')
978 logs = parse_log(
979 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
980 if len(logs) != 2:
981 raise Failure()
984 class PruneWithCare(Cvs2SvnTestCase):
985 "prune, but never too much"
987 def __init__(self, **kw):
988 Cvs2SvnTestCase.__init__(self, 'main', **kw)
990 def run(self, sbox):
991 # Robert Pluim encountered this lovely one while converting the
992 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
993 # repository (see issue #1302). Step 4 is the doozy:
995 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
996 # revision 2: adds trunk/blah/NEWS
997 # revision 3: deletes trunk/blah/cookie
998 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
999 # revision 5: does nothing
1001 # After fixing cvs2svn, the sequence (correctly) looks like this:
1003 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
1004 # revision 2: adds trunk/blah/NEWS
1005 # revision 3: deletes trunk/blah/cookie
1006 # revision 4: does nothing [because trunk/blah/cookie already deleted]
1007 # revision 5: deletes blah
1009 # The difference is in 4 and 5. In revision 4, it's not correct to
1010 # prune blah/, because NEWS is still in there, so revision 4 does
1011 # nothing now. But when we delete NEWS in 5, that should bubble up
1012 # and prune blah/ instead.
1014 # ### Note that empty revisions like 4 are probably going to become
1015 # ### at least optional, if not banished entirely from cvs2svn's
1016 # ### output. Hmmm, or they may stick around, with an extra
1017 # ### revision property explaining what happened. Need to think
1018 # ### about that. In some sense, it's a bug in Subversion itself,
1019 # ### that such revisions don't show up in 'svn log' output.
1021 # In the test below, 'trunk/full-prune/first' represents
1022 # cookie, and 'trunk/full-prune/second' represents NEWS.
1024 conv = self.ensure_conversion()
1026 # Confirm that revision 4 removes '/trunk/full-prune/first',
1027 # and that revision 6 removes '/trunk/full-prune'.
1029 # Also confirm similar things about '/full-prune-reappear/...',
1030 # which is similar, except that later on it reappears, restored
1031 # from pruneland, because a file gets added to it.
1033 # And finally, a similar thing for '/partial-prune/...', except that
1034 # in its case, a permanent file on the top level prevents the
1035 # pruning from going farther than the subdirectory containing first
1036 # and second.
1038 for path in ('full-prune/first',
1039 'full-prune-reappear/sub/first',
1040 'partial-prune/sub/first'):
1041 conv.logs[5].check_change('/%(trunk)s/' + path, 'D')
1043 for path in ('full-prune',
1044 'full-prune-reappear',
1045 'partial-prune/sub'):
1046 conv.logs[7].check_change('/%(trunk)s/' + path, 'D')
1048 for path in ('full-prune-reappear',
1049 'full-prune-reappear/appears-later'):
1050 conv.logs[33].check_change('/%(trunk)s/' + path, 'A')
1053 @Cvs2SvnTestFunction
1054 def interleaved_commits():
1055 "two interleaved trunk commits, different log msgs"
1056 # See test-data/main-cvsrepos/proj/README.
1057 conv = ensure_conversion('main')
1059 # The initial import.
1060 rev = 26
1061 conv.logs[rev].check('Initial import.', (
1062 ('/%(trunk)s/interleaved', 'A'),
1063 ('/%(trunk)s/interleaved/1', 'A'),
1064 ('/%(trunk)s/interleaved/2', 'A'),
1065 ('/%(trunk)s/interleaved/3', 'A'),
1066 ('/%(trunk)s/interleaved/4', 'A'),
1067 ('/%(trunk)s/interleaved/5', 'A'),
1068 ('/%(trunk)s/interleaved/a', 'A'),
1069 ('/%(trunk)s/interleaved/b', 'A'),
1070 ('/%(trunk)s/interleaved/c', 'A'),
1071 ('/%(trunk)s/interleaved/d', 'A'),
1072 ('/%(trunk)s/interleaved/e', 'A'),
1075 def check_letters(rev):
1076 """Check if REV is the rev where only letters were committed."""
1078 conv.logs[rev].check('Committing letters only.', (
1079 ('/%(trunk)s/interleaved/a', 'M'),
1080 ('/%(trunk)s/interleaved/b', 'M'),
1081 ('/%(trunk)s/interleaved/c', 'M'),
1082 ('/%(trunk)s/interleaved/d', 'M'),
1083 ('/%(trunk)s/interleaved/e', 'M'),
1086 def check_numbers(rev):
1087 """Check if REV is the rev where only numbers were committed."""
1089 conv.logs[rev].check('Committing numbers only.', (
1090 ('/%(trunk)s/interleaved/1', 'M'),
1091 ('/%(trunk)s/interleaved/2', 'M'),
1092 ('/%(trunk)s/interleaved/3', 'M'),
1093 ('/%(trunk)s/interleaved/4', 'M'),
1094 ('/%(trunk)s/interleaved/5', 'M'),
1097 # One of the commits was letters only, the other was numbers only.
1098 # But they happened "simultaneously", so we don't assume anything
1099 # about which commit appeared first, so we just try both ways.
1100 rev += 1
1101 try:
1102 check_letters(rev)
1103 check_numbers(rev + 1)
1104 except Failure:
1105 check_numbers(rev)
1106 check_letters(rev + 1)
1109 @Cvs2SvnTestFunction
1110 def simple_commits():
1111 "simple trunk commits"
1112 # See test-data/main-cvsrepos/proj/README.
1113 conv = ensure_conversion('main')
1115 # The initial import.
1116 conv.logs[13].check('Initial import.', (
1117 ('/%(trunk)s/proj', 'A'),
1118 ('/%(trunk)s/proj/default', 'A'),
1119 ('/%(trunk)s/proj/sub1', 'A'),
1120 ('/%(trunk)s/proj/sub1/default', 'A'),
1121 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1122 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1123 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1124 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1125 ('/%(trunk)s/proj/sub2', 'A'),
1126 ('/%(trunk)s/proj/sub2/default', 'A'),
1127 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1128 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1129 ('/%(trunk)s/proj/sub3', 'A'),
1130 ('/%(trunk)s/proj/sub3/default', 'A'),
1133 # The first commit.
1134 conv.logs[18].check('First commit to proj, affecting two files.', (
1135 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1136 ('/%(trunk)s/proj/sub3/default', 'M'),
1139 # The second commit.
1140 conv.logs[19].check('Second commit to proj, affecting all 7 files.', (
1141 ('/%(trunk)s/proj/default', 'M'),
1142 ('/%(trunk)s/proj/sub1/default', 'M'),
1143 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1144 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1145 ('/%(trunk)s/proj/sub2/default', 'M'),
1146 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1147 ('/%(trunk)s/proj/sub3/default', 'M')
1151 class SimpleTags(Cvs2SvnTestCase):
1152 "simple tags and branches, no commits"
1154 def __init__(self, **kw):
1155 # See test-data/main-cvsrepos/proj/README.
1156 Cvs2SvnTestCase.__init__(self, 'main', **kw)
1158 def run(self, sbox):
1159 conv = self.ensure_conversion()
1161 # Verify the copy source for the tags we are about to check
1162 # No need to verify the copyfrom revision, as simple_commits did that
1163 conv.logs[13].check('Initial import.', (
1164 ('/%(trunk)s/proj', 'A'),
1165 ('/%(trunk)s/proj/default', 'A'),
1166 ('/%(trunk)s/proj/sub1', 'A'),
1167 ('/%(trunk)s/proj/sub1/default', 'A'),
1168 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1169 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1170 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1171 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1172 ('/%(trunk)s/proj/sub2', 'A'),
1173 ('/%(trunk)s/proj/sub2/default', 'A'),
1174 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1175 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1176 ('/%(trunk)s/proj/sub3', 'A'),
1177 ('/%(trunk)s/proj/sub3/default', 'A'),
1180 fromstr = ' (from /%(branches)s/B_FROM_INITIALS:14)'
1182 # Tag on rev 1.1.1.1 of all files in proj
1183 conv.logs[14].check(sym_log_msg('B_FROM_INITIALS'), (
1184 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1185 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1186 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1189 # The same, as a tag
1190 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
1191 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1192 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr, 'A'),
1195 # Tag on rev 1.1.1.1 of all files in proj, except one
1196 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1197 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1198 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr, 'A'),
1199 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1202 # The same, as a branch
1203 conv.logs[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1204 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr, 'A'),
1205 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1209 @Cvs2SvnTestFunction
1210 def simple_branch_commits():
1211 "simple branch commits"
1212 # See test-data/main-cvsrepos/proj/README.
1213 conv = ensure_conversion('main')
1215 conv.logs[23].check('Modify three files, on branch B_MIXED.', (
1216 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1217 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1218 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1222 @Cvs2SvnTestFunction
1223 def mixed_time_tag():
1224 "mixed-time tag"
1225 # See test-data/main-cvsrepos/proj/README.
1226 conv = ensure_conversion('main')
1228 log = conv.find_tag_log('T_MIXED')
1229 log.check_changes((
1230 ('/%(tags)s/T_MIXED (from /%(branches)s/B_MIXED:20)', 'A'),
1234 @Cvs2SvnTestFunction
1235 def mixed_time_branch_with_added_file():
1236 "mixed-time branch, and a file added to the branch"
1237 # See test-data/main-cvsrepos/proj/README.
1238 conv = ensure_conversion('main')
1240 # A branch from the same place as T_MIXED in the previous test,
1241 # plus a file added directly to the branch
1242 conv.logs[20].check(sym_log_msg('B_MIXED'), (
1243 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1244 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1245 ('/%(branches)s/B_MIXED/single-files', 'D'),
1246 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1247 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1248 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1251 conv.logs[22].check('Add a file on branch B_MIXED.', (
1252 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1256 @Cvs2SvnTestFunction
1257 def mixed_commit():
1258 "a commit affecting both trunk and a branch"
1259 # See test-data/main-cvsrepos/proj/README.
1260 conv = ensure_conversion('main')
1262 conv.logs[24].check(
1263 'A single commit affecting one file on branch B_MIXED '
1264 'and one on trunk.', (
1265 ('/%(trunk)s/proj/sub2/default', 'M'),
1266 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1270 @Cvs2SvnTestFunction
1271 def split_time_branch():
1272 "branch some trunk files, and later branch the rest"
1273 # See test-data/main-cvsrepos/proj/README.
1274 conv = ensure_conversion('main')
1276 # First change on the branch, creating it
1277 conv.logs[25].check(sym_log_msg('B_SPLIT'), (
1278 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1279 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1280 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1281 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1284 conv.logs[29].check('First change on branch B_SPLIT.', (
1285 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1286 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1287 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1288 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1289 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1292 # A trunk commit for the file which was not branched
1293 conv.logs[30].check('A trunk change to sub1/subsubB/default. '
1294 'This was committed about an', (
1295 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1298 # Add the file not already branched to the branch, with modification:w
1299 conv.logs[31].check(sym_log_msg('B_SPLIT'), (
1300 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1301 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1304 conv.logs[32].check('This change affects sub3/default and '
1305 'sub1/subsubB/default, on branch', (
1306 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1307 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1311 @Cvs2SvnTestFunction
1312 def multiple_tags():
1313 "multiple tags referring to same revision"
1314 conv = ensure_conversion('main')
1315 if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1316 raise Failure()
1317 if not conv.path_exists(
1318 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1319 raise Failure()
1322 @Cvs2SvnTestFunction
1323 def multiply_defined_symbols():
1324 "multiple definitions of symbol names"
1326 # We can only check one line of the error output at a time, so test
1327 # twice. (The conversion only have to be done once because the
1328 # results are cached.)
1329 conv = ensure_conversion(
1330 'multiply-defined-symbols',
1331 error_re=(
1332 r"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1333 r"1\.2\.4 1\.2\.2"
1336 conv = ensure_conversion(
1337 'multiply-defined-symbols',
1338 error_re=(
1339 r"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1340 r"1\.2 1\.1"
1345 @Cvs2SvnTestFunction
1346 def multiply_defined_symbols_renamed():
1347 "rename multiply defined symbols"
1349 conv = ensure_conversion(
1350 'multiply-defined-symbols',
1351 options_file='cvs2svn-rename.options',
1355 @Cvs2SvnTestFunction
1356 def multiply_defined_symbols_ignored():
1357 "ignore multiply defined symbols"
1359 conv = ensure_conversion(
1360 'multiply-defined-symbols',
1361 options_file='cvs2svn-ignore.options',
1365 @Cvs2SvnTestFunction
1366 def repeatedly_defined_symbols():
1367 "multiple identical definitions of symbol names"
1369 # If a symbol is defined multiple times but has the same value each
1370 # time, that should not be an error.
1372 conv = ensure_conversion('repeatedly-defined-symbols')
1375 @Cvs2SvnTestFunction
1376 def bogus_tag():
1377 "conversion of invalid symbolic names"
1378 conv = ensure_conversion('bogus-tag')
1381 @Cvs2SvnTestFunction
1382 def overlapping_branch():
1383 "ignore a file with a branch with two names"
1384 conv = ensure_conversion('overlapping-branch')
1386 if not conv.output_found('.*cannot also have name \'vendorB\''):
1387 raise Failure()
1389 conv.logs[2].check('imported', (
1390 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1391 ('/%(trunk)s/overlapping-branch', 'A'),
1394 if len(conv.logs) != 2:
1395 raise Failure()
1398 class PhoenixBranch(Cvs2SvnTestCase):
1399 "convert a branch file rooted in a 'dead' revision"
1401 def __init__(self, **kw):
1402 Cvs2SvnTestCase.__init__(self, 'phoenix', **kw)
1404 def run(self, sbox):
1405 conv = self.ensure_conversion()
1406 conv.logs[8].check('This file was supplied by Jack Moffitt', (
1407 ('/%(branches)s/volsung_20010721', 'A'),
1408 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1410 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1411 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1415 ###TODO: We check for 4 changed paths here to accomodate creating tags
1416 ###and branches in rev 1, but that will change, so this will
1417 ###eventually change back.
1418 @Cvs2SvnTestFunction
1419 def ctrl_char_in_log():
1420 "handle a control char in a log message"
1421 # This was issue #1106.
1422 rev = 2
1423 conv = ensure_conversion('ctrl-char-in-log')
1424 conv.logs[rev].check_changes((
1425 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1427 if conv.logs[rev].msg.find('\x04') < 0:
1428 raise Failure(
1429 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1432 @Cvs2SvnTestFunction
1433 def overdead():
1434 "handle tags rooted in a redeleted revision"
1435 conv = ensure_conversion('overdead')
1438 class NoTrunkPrune(Cvs2SvnTestCase):
1439 "ensure that trunk doesn't get pruned"
1441 def __init__(self, **kw):
1442 Cvs2SvnTestCase.__init__(self, 'overdead', **kw)
1444 def run(self, sbox):
1445 conv = self.ensure_conversion()
1446 for rev in conv.logs.keys():
1447 rev_logs = conv.logs[rev]
1448 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1449 raise Failure()
1452 @Cvs2SvnTestFunction
1453 def double_delete():
1454 "file deleted twice, in the root of the repository"
1455 # This really tests several things: how we handle a file that's
1456 # removed (state 'dead') in two successive revisions; how we
1457 # handle a file in the root of the repository (there were some
1458 # bugs in cvs2svn's svn path construction for top-level files); and
1459 # the --no-prune option.
1460 conv = ensure_conversion(
1461 'double-delete', args=['--trunk-only', '--no-prune'])
1463 path = '/%(trunk)s/twice-removed'
1464 rev = 2
1465 conv.logs[rev].check('Updated CVS', (
1466 (path, 'A'),
1468 conv.logs[rev + 1].check('Remove this file for the first time.', (
1469 (path, 'D'),
1471 conv.logs[rev + 2].check('Remove this file for the second time,', (
1475 @Cvs2SvnTestFunction
1476 def split_branch():
1477 "branch created from both trunk and another branch"
1478 # See test-data/split-branch-cvsrepos/README.
1480 # The conversion will fail if the bug is present, and
1481 # ensure_conversion will raise Failure.
1482 conv = ensure_conversion('split-branch')
1485 @Cvs2SvnTestFunction
1486 def resync_misgroups():
1487 "resyncing should not misorder commit groups"
1488 # See test-data/resync-misgroups-cvsrepos/README.
1490 # The conversion will fail if the bug is present, and
1491 # ensure_conversion will raise Failure.
1492 conv = ensure_conversion('resync-misgroups')
1495 class TaggedBranchAndTrunk(Cvs2SvnTestCase):
1496 "allow tags with mixed trunk and branch sources"
1498 def __init__(self, **kw):
1499 Cvs2SvnTestCase.__init__(self, 'tagged-branch-n-trunk', **kw)
1501 def run(self, sbox):
1502 conv = self.ensure_conversion()
1504 tags = conv.symbols.get('tags', 'tags')
1506 a_path = conv.get_wc(tags, 'some-tag', 'a.txt')
1507 b_path = conv.get_wc(tags, 'some-tag', 'b.txt')
1508 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1509 raise Failure()
1510 if (open(a_path, 'r').read().find('1.24') == -1) \
1511 or (open(b_path, 'r').read().find('1.5') == -1):
1512 raise Failure()
1515 @Cvs2SvnTestFunction
1516 def enroot_race():
1517 "never use the rev-in-progress as a copy source"
1519 # See issue #1427 and r8544.
1520 conv = ensure_conversion('enroot-race')
1521 rev = 6
1522 conv.logs[rev].check_changes((
1523 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1524 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1525 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1527 conv.logs[rev + 1].check_changes((
1528 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1529 ('/%(trunk)s/proj/a.txt', 'M'),
1530 ('/%(trunk)s/proj/b.txt', 'M'),
1534 @Cvs2SvnTestFunction
1535 def enroot_race_obo():
1536 "do use the last completed rev as a copy source"
1537 conv = ensure_conversion('enroot-race-obo')
1538 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1539 if not len(conv.logs) == 3:
1540 raise Failure()
1543 class BranchDeleteFirst(Cvs2SvnTestCase):
1544 "correctly handle deletion as initial branch action"
1546 def __init__(self, **kw):
1547 Cvs2SvnTestCase.__init__(self, 'branch-delete-first', **kw)
1549 def run(self, sbox):
1550 # See test-data/branch-delete-first-cvsrepos/README.
1552 # The conversion will fail if the bug is present, and
1553 # ensure_conversion would raise Failure.
1554 conv = self.ensure_conversion()
1556 branches = conv.symbols.get('branches', 'branches')
1558 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1559 if conv.path_exists(branches, 'branch-1', 'file'):
1560 raise Failure()
1561 if conv.path_exists(branches, 'branch-2', 'file'):
1562 raise Failure()
1563 if not conv.path_exists(branches, 'branch-3', 'file'):
1564 raise Failure()
1567 @Cvs2SvnTestFunction
1568 def nonascii_filenames():
1569 "non ascii files converted incorrectly"
1570 # see issue #1255
1572 # on a en_US.iso-8859-1 machine this test fails with
1573 # svn: Can't recode ...
1575 # as described in the issue
1577 # on a en_US.UTF-8 machine this test fails with
1578 # svn: Malformed XML ...
1580 # which means at least it fails. Unfortunately it won't fail
1581 # with the same error...
1583 # mangle current locale settings so we know we're not running
1584 # a UTF-8 locale (which does not exhibit this problem)
1585 current_locale = locale.getlocale()
1586 new_locale = 'en_US.ISO8859-1'
1587 locale_changed = None
1589 # From http://docs.python.org/lib/module-sys.html
1591 # getfilesystemencoding():
1593 # Return the name of the encoding used to convert Unicode filenames
1594 # into system file names, or None if the system default encoding is
1595 # used. The result value depends on the operating system:
1597 # - On Windows 9x, the encoding is ``mbcs''.
1598 # - On Mac OS X, the encoding is ``utf-8''.
1599 # - On Unix, the encoding is the user's preference according to the
1600 # result of nl_langinfo(CODESET), or None if the
1601 # nl_langinfo(CODESET) failed.
1602 # - On Windows NT+, file names are Unicode natively, so no conversion is
1603 # performed.
1605 # So we're going to skip this test on Mac OS X for now.
1606 if sys.platform == "darwin":
1607 raise svntest.Skip()
1609 try:
1610 # change locale to non-UTF-8 locale to generate latin1 names
1611 locale.setlocale(locale.LC_ALL, # this might be too broad?
1612 new_locale)
1613 locale_changed = 1
1614 except locale.Error:
1615 raise svntest.Skip()
1617 try:
1618 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
1619 dstrepos_path = os.path.join(test_data_dir,'non-ascii-cvsrepos')
1620 if not os.path.exists(dstrepos_path):
1621 # create repos from existing main repos
1622 shutil.copytree(srcrepos_path, dstrepos_path)
1623 base_path = os.path.join(dstrepos_path, 'single-files')
1624 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1625 os.path.join(base_path, 'two\366uick,v'))
1626 new_path = os.path.join(dstrepos_path, 'single\366files')
1627 os.rename(base_path, new_path)
1629 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1630 finally:
1631 if locale_changed:
1632 locale.setlocale(locale.LC_ALL, current_locale)
1633 safe_rmtree(dstrepos_path)
1636 class UnicodeTest(Cvs2SvnTestCase):
1637 "metadata contains Unicode"
1639 warning_pattern = r'ERROR\: There were warnings converting .* messages'
1641 def __init__(self, name, warning_expected, **kw):
1642 if warning_expected:
1643 error_re = self.warning_pattern
1644 else:
1645 error_re = None
1647 Cvs2SvnTestCase.__init__(self, name, error_re=error_re, **kw)
1648 self.warning_expected = warning_expected
1650 def run(self, sbox):
1651 try:
1652 # ensure the availability of the "utf_8" encoding:
1653 u'a'.encode('utf_8').decode('utf_8')
1654 except LookupError:
1655 raise svntest.Skip()
1657 self.ensure_conversion()
1660 class UnicodeAuthor(UnicodeTest):
1661 "author name contains Unicode"
1663 def __init__(self, warning_expected, **kw):
1664 UnicodeTest.__init__(self, 'unicode-author', warning_expected, **kw)
1667 class UnicodeLog(UnicodeTest):
1668 "log message contains Unicode"
1670 def __init__(self, warning_expected, **kw):
1671 UnicodeTest.__init__(self, 'unicode-log', warning_expected, **kw)
1674 @Cvs2SvnTestFunction
1675 def vendor_branch_sameness():
1676 "avoid spurious changes for initial revs"
1677 conv = ensure_conversion(
1678 'vendor-branch-sameness', args=['--keep-trivial-imports']
1681 # The following files are in this repository:
1683 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1684 # the same contents, the file's default branch is 1.1.1,
1685 # and both revisions are in state 'Exp'.
1687 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1688 # 1.1 (the addition of a line of text).
1690 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1692 # d.txt: This file was created by 'cvs add' instead of import, so
1693 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1694 # The timestamp on the add is exactly the same as for the
1695 # imports of the other files.
1697 # e.txt: Like a.txt, except that the log message for revision 1.1
1698 # is not the standard import log message.
1700 # (Aside from e.txt, the log messages for the same revisions are the
1701 # same in all files.)
1703 # We expect that only a.txt is recognized as an import whose 1.1
1704 # revision can be omitted. The other files should be added on trunk
1705 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1706 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1707 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1708 # and c.txt should be 'D'eleted.
1710 rev = 2
1711 conv.logs[rev].check('Initial revision', (
1712 ('/%(trunk)s/proj', 'A'),
1713 ('/%(trunk)s/proj/b.txt', 'A'),
1714 ('/%(trunk)s/proj/c.txt', 'A'),
1715 ('/%(trunk)s/proj/d.txt', 'A'),
1718 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1719 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1720 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1723 conv.logs[rev + 2].check('First vendor branch revision.', (
1724 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1725 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1726 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1729 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1730 'to compensate for changes in r4,', (
1731 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1732 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1733 ('/%(trunk)s/proj/c.txt', 'D'),
1736 rev = 7
1737 conv.logs[rev].check('This log message is not the standard', (
1738 ('/%(trunk)s/proj/e.txt', 'A'),
1741 conv.logs[rev + 2].check('First vendor branch revision', (
1742 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1745 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1746 'to compensate for changes in r9,', (
1747 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1751 @Cvs2SvnTestFunction
1752 def vendor_branch_trunk_only():
1753 "handle vendor branches with --trunk-only"
1754 conv = ensure_conversion('vendor-branch-sameness', args=['--trunk-only'])
1756 rev = 2
1757 conv.logs[rev].check('Initial revision', (
1758 ('/%(trunk)s/proj', 'A'),
1759 ('/%(trunk)s/proj/b.txt', 'A'),
1760 ('/%(trunk)s/proj/c.txt', 'A'),
1761 ('/%(trunk)s/proj/d.txt', 'A'),
1764 conv.logs[rev + 1].check('First vendor branch revision', (
1765 ('/%(trunk)s/proj/a.txt', 'A'),
1766 ('/%(trunk)s/proj/b.txt', 'M'),
1767 ('/%(trunk)s/proj/c.txt', 'D'),
1770 conv.logs[rev + 2].check('This log message is not the standard', (
1771 ('/%(trunk)s/proj/e.txt', 'A'),
1774 conv.logs[rev + 3].check('First vendor branch revision', (
1775 ('/%(trunk)s/proj/e.txt', 'M'),
1779 @Cvs2SvnTestFunction
1780 def default_branches():
1781 "handle default branches correctly"
1782 conv = ensure_conversion('default-branches')
1784 # There are seven files in the repository:
1786 # a.txt:
1787 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1788 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1789 # committed (thus losing the default branch "1.1.1"), then
1790 # 1.1.1.4 was imported. All vendor import release tags are
1791 # still present.
1793 # b.txt:
1794 # Like a.txt, but without rev 1.2.
1796 # c.txt:
1797 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1799 # d.txt:
1800 # Same as the previous two, but 1.1.1 branch is unlabeled.
1802 # e.txt:
1803 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1805 # deleted-on-vendor-branch.txt,v:
1806 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1808 # added-then-imported.txt,v:
1809 # Added with 'cvs add' to create 1.1, then imported with
1810 # completely different contents to create 1.1.1.1, therefore
1811 # never had a default branch.
1814 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1815 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1816 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1817 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1818 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1819 ('/%(branches)s/vbranchA', 'A'),
1820 ('/%(branches)s/vbranchA/proj', 'A'),
1821 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1822 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1823 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1824 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1827 conv.logs[3].check("This commit was generated by cvs2svn "
1828 "to compensate for changes in r2,", (
1829 ('/%(trunk)s/proj', 'A'),
1830 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1831 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1832 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1833 ('/%(trunk)s/proj/d.txt '
1834 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1835 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1836 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1837 ('/%(trunk)s/proj/e.txt '
1838 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1841 conv.logs[4].check(sym_log_msg('vtag-1',1), (
1842 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1843 ('/%(tags)s/vtag-1/proj/d.txt '
1844 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1847 conv.logs[5].check("Import (vbranchA, vtag-2).", (
1848 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1849 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1850 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1851 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1852 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1853 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1856 conv.logs[6].check("This commit was generated by cvs2svn "
1857 "to compensate for changes in r5,", (
1858 ('/%(trunk)s/proj/a.txt '
1859 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1860 ('/%(trunk)s/proj/b.txt '
1861 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1862 ('/%(trunk)s/proj/c.txt '
1863 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1864 ('/%(trunk)s/proj/d.txt '
1865 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1866 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1867 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1868 'R'),
1869 ('/%(trunk)s/proj/e.txt '
1870 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1873 conv.logs[7].check(sym_log_msg('vtag-2',1), (
1874 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1875 ('/%(tags)s/vtag-2/proj/d.txt '
1876 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1879 conv.logs[8].check("Import (vbranchA, vtag-3).", (
1880 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1881 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1882 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1883 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1884 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1885 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1888 conv.logs[9].check("This commit was generated by cvs2svn "
1889 "to compensate for changes in r8,", (
1890 ('/%(trunk)s/proj/a.txt '
1891 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1892 ('/%(trunk)s/proj/b.txt '
1893 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1894 ('/%(trunk)s/proj/c.txt '
1895 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1896 ('/%(trunk)s/proj/d.txt '
1897 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1898 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1899 ('/%(trunk)s/proj/e.txt '
1900 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1903 conv.logs[10].check(sym_log_msg('vtag-3',1), (
1904 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1905 ('/%(tags)s/vtag-3/proj/d.txt '
1906 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1907 ('/%(tags)s/vtag-3/proj/e.txt '
1908 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1911 conv.logs[11].check("First regular commit, to a.txt, on vtag-3.", (
1912 ('/%(trunk)s/proj/a.txt', 'M'),
1915 conv.logs[12].check("Add a file to the working copy.", (
1916 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1919 conv.logs[13].check(sym_log_msg('vbranchA'), (
1920 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1921 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1924 conv.logs[14].check("Import (vbranchA, vtag-4).", (
1925 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1926 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1927 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1928 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1929 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1930 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1931 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1934 conv.logs[15].check("This commit was generated by cvs2svn "
1935 "to compensate for changes in r14,", (
1936 ('/%(trunk)s/proj/b.txt '
1937 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1938 ('/%(trunk)s/proj/c.txt '
1939 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1940 ('/%(trunk)s/proj/d.txt '
1941 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1942 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1943 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1944 'A'),
1945 ('/%(trunk)s/proj/e.txt '
1946 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1949 conv.logs[16].check(sym_log_msg('vtag-4',1), (
1950 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1951 ('/%(tags)s/vtag-4/proj/d.txt '
1952 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
1956 @Cvs2SvnTestFunction
1957 def default_branches_trunk_only():
1958 "handle default branches with --trunk-only"
1960 conv = ensure_conversion('default-branches', args=['--trunk-only'])
1962 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1963 ('/%(trunk)s/proj', 'A'),
1964 ('/%(trunk)s/proj/a.txt', 'A'),
1965 ('/%(trunk)s/proj/b.txt', 'A'),
1966 ('/%(trunk)s/proj/c.txt', 'A'),
1967 ('/%(trunk)s/proj/d.txt', 'A'),
1968 ('/%(trunk)s/proj/e.txt', 'A'),
1969 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1972 conv.logs[3].check("Import (vbranchA, vtag-2).", (
1973 ('/%(trunk)s/proj/a.txt', 'M'),
1974 ('/%(trunk)s/proj/b.txt', 'M'),
1975 ('/%(trunk)s/proj/c.txt', 'M'),
1976 ('/%(trunk)s/proj/d.txt', 'M'),
1977 ('/%(trunk)s/proj/e.txt', 'M'),
1978 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
1981 conv.logs[4].check("Import (vbranchA, vtag-3).", (
1982 ('/%(trunk)s/proj/a.txt', 'M'),
1983 ('/%(trunk)s/proj/b.txt', 'M'),
1984 ('/%(trunk)s/proj/c.txt', 'M'),
1985 ('/%(trunk)s/proj/d.txt', 'M'),
1986 ('/%(trunk)s/proj/e.txt', 'M'),
1987 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1990 conv.logs[5].check("First regular commit, to a.txt, on vtag-3.", (
1991 ('/%(trunk)s/proj/a.txt', 'M'),
1994 conv.logs[6].check("Add a file to the working copy.", (
1995 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1998 conv.logs[7].check("Import (vbranchA, vtag-4).", (
1999 ('/%(trunk)s/proj/b.txt', 'M'),
2000 ('/%(trunk)s/proj/c.txt', 'M'),
2001 ('/%(trunk)s/proj/d.txt', 'M'),
2002 ('/%(trunk)s/proj/e.txt', 'M'),
2003 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2007 @Cvs2SvnTestFunction
2008 def default_branch_and_1_2():
2009 "do not allow 1.2 revision with default branch"
2011 conv = ensure_conversion(
2012 'default-branch-and-1-2',
2013 error_re=(
2014 r'.*File \'.*\' has default branch=1\.1\.1 but also a revision 1\.2'
2019 @Cvs2SvnTestFunction
2020 def compose_tag_three_sources():
2021 "compose a tag from three sources"
2022 conv = ensure_conversion('compose-tag-three-sources')
2024 conv.logs[2].check("Add on trunk", (
2025 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
2026 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
2027 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
2028 ('/%(trunk)s/tagged-on-b1', 'A'),
2029 ('/%(trunk)s/tagged-on-b2', 'A'),
2032 conv.logs[3].check(sym_log_msg('b1'), (
2033 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
2036 conv.logs[4].check(sym_log_msg('b2'), (
2037 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
2040 conv.logs[5].check("Commit on branch b1", (
2041 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
2042 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
2043 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
2044 ('/%(branches)s/b1/tagged-on-b1', 'M'),
2045 ('/%(branches)s/b1/tagged-on-b2', 'M'),
2048 conv.logs[6].check("Commit on branch b2", (
2049 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
2050 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
2051 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
2052 ('/%(branches)s/b2/tagged-on-b1', 'M'),
2053 ('/%(branches)s/b2/tagged-on-b2', 'M'),
2056 conv.logs[7].check("Commit again on trunk", (
2057 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
2058 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
2059 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
2060 ('/%(trunk)s/tagged-on-b1', 'M'),
2061 ('/%(trunk)s/tagged-on-b2', 'M'),
2064 conv.logs[8].check(sym_log_msg('T',1), (
2065 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
2066 ('/%(tags)s/T/tagged-on-trunk-1.1 '
2067 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
2068 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:5)', 'R'),
2069 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:6)', 'R'),
2073 @Cvs2SvnTestFunction
2074 def pass5_when_to_fill():
2075 "reserve a svn revnum for a fill only when required"
2076 # The conversion will fail if the bug is present, and
2077 # ensure_conversion would raise Failure.
2078 conv = ensure_conversion('pass5-when-to-fill')
2081 class EmptyTrunk(Cvs2SvnTestCase):
2082 "don't break when the trunk is empty"
2084 def __init__(self, **kw):
2085 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
2087 def run(self, sbox):
2088 # The conversion will fail if the bug is present, and
2089 # ensure_conversion would raise Failure.
2090 conv = self.ensure_conversion()
2093 @Cvs2SvnTestFunction
2094 def no_spurious_svn_commits():
2095 "ensure that we don't create any spurious commits"
2096 conv = ensure_conversion('phoenix')
2098 # Check spurious commit that could be created in
2099 # SVNCommitCreator._pre_commit()
2101 # (When you add a file on a branch, CVS creates a trunk revision
2102 # in state 'dead'. If the log message of that commit is equal to
2103 # the one that CVS generates, we do not ever create a 'fill'
2104 # SVNCommit for it.)
2106 # and spurious commit that could be created in
2107 # SVNCommitCreator._commit()
2109 # (When you add a file on a branch, CVS creates a trunk revision
2110 # in state 'dead'. If the log message of that commit is equal to
2111 # the one that CVS generates, we do not create a primary SVNCommit
2112 # for it.)
2113 conv.logs[17].check('File added on branch xiphophorus', (
2114 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
2117 # Check to make sure that a commit *is* generated:
2118 # (When you add a file on a branch, CVS creates a trunk revision
2119 # in state 'dead'. If the log message of that commit is NOT equal
2120 # to the one that CVS generates, we create a primary SVNCommit to
2121 # serve as a home for the log message in question.
2122 conv.logs[18].check('file added-on-branch2.txt was initially added on '
2123 + 'branch xiphophorus,\nand this log message was tweaked', ())
2125 # Check spurious commit that could be created in
2126 # SVNCommitCreator._commit_symbols().
2127 conv.logs[19].check('This file was also added on branch xiphophorus,', (
2128 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
2132 class PeerPathPruning(Cvs2SvnTestCase):
2133 "make sure that filling prunes paths correctly"
2135 def __init__(self, **kw):
2136 Cvs2SvnTestCase.__init__(self, 'peer-path-pruning', **kw)
2138 def run(self, sbox):
2139 conv = self.ensure_conversion()
2140 conv.logs[6].check(sym_log_msg('BRANCH'), (
2141 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A'),
2142 ('/%(branches)s/BRANCH/bar', 'D'),
2143 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:5)', 'R'),
2147 @Cvs2SvnTestFunction
2148 def invalid_closings_on_trunk():
2149 "verify correct revs are copied to default branches"
2150 # The conversion will fail if the bug is present, and
2151 # ensure_conversion would raise Failure.
2152 conv = ensure_conversion('invalid-closings-on-trunk')
2155 @Cvs2SvnTestFunction
2156 def individual_passes():
2157 "run each pass individually"
2158 conv = ensure_conversion('main')
2159 conv2 = ensure_conversion('main', passbypass=1)
2161 if conv.logs != conv2.logs:
2162 raise Failure()
2165 @Cvs2SvnTestFunction
2166 def resync_bug():
2167 "reveal a big bug in our resync algorithm"
2168 # This will fail if the bug is present
2169 conv = ensure_conversion('resync-bug')
2172 @Cvs2SvnTestFunction
2173 def branch_from_default_branch():
2174 "reveal a bug in our default branch detection code"
2175 conv = ensure_conversion('branch-from-default-branch')
2177 # This revision will be a default branch synchronization only
2178 # if cvs2svn is correctly determining default branch revisions.
2180 # The bug was that cvs2svn was treating revisions on branches off of
2181 # default branches as default branch revisions, resulting in
2182 # incorrectly regarding the branch off of the default branch as a
2183 # non-trunk default branch. Crystal clear? I thought so. See
2184 # issue #42 for more incoherent blathering.
2185 conv.logs[5].check("This commit was generated by cvs2svn", (
2186 ('/%(trunk)s/proj/file.txt '
2187 '(from /%(branches)s/upstream/proj/file.txt:4)', 'R'),
2191 @Cvs2SvnTestFunction
2192 def file_in_attic_too():
2193 "die if a file exists in and out of the attic"
2194 ensure_conversion(
2195 'file-in-attic-too',
2196 error_re=(
2197 r'.*A CVS repository cannot contain both '
2198 r'(.*)' + re.escape(os.sep) + r'(.*) '
2199 + r'and '
2200 r'\1' + re.escape(os.sep) + r'Attic' + re.escape(os.sep) + r'\2'
2205 @Cvs2SvnTestFunction
2206 def retain_file_in_attic_too():
2207 "test --retain-conflicting-attic-files option"
2208 conv = ensure_conversion(
2209 'file-in-attic-too', args=['--retain-conflicting-attic-files'])
2210 if not conv.path_exists('trunk', 'file.txt'):
2211 raise Failure()
2212 if not conv.path_exists('trunk', 'Attic', 'file.txt'):
2213 raise Failure()
2216 @Cvs2SvnTestFunction
2217 def symbolic_name_filling_guide():
2218 "reveal a big bug in our SymbolFillingGuide"
2219 # This will fail if the bug is present
2220 conv = ensure_conversion('symbolic-name-overfill')
2223 # Helpers for tests involving file contents and properties.
2225 class NodeTreeWalkException:
2226 "Exception class for node tree traversals."
2227 pass
2229 def node_for_path(node, path):
2230 "In the tree rooted under SVNTree NODE, return the node at PATH."
2231 if node.name != '__SVN_ROOT_NODE':
2232 raise NodeTreeWalkException()
2233 path = path.strip('/')
2234 components = path.split('/')
2235 for component in components:
2236 node = svntest.tree.get_child(node, component)
2237 return node
2239 # Helper for tests involving properties.
2240 def props_for_path(node, path):
2241 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2242 return node_for_path(node, path).props
2245 class EOLMime(Cvs2SvnPropertiesTestCase):
2246 """eol settings and mime types together
2248 The files are as follows:
2250 trunk/foo.txt: no -kb, mime file says nothing.
2251 trunk/foo.xml: no -kb, mime file says text.
2252 trunk/foo.zip: no -kb, mime file says non-text.
2253 trunk/foo.bin: has -kb, mime file says nothing.
2254 trunk/foo.csv: has -kb, mime file says text.
2255 trunk/foo.dbf: has -kb, mime file says non-text.
2258 def __init__(self, args, **kw):
2259 # TODO: It's a bit klugey to construct this path here. But so far
2260 # there's only one test with a mime.types file. If we have more,
2261 # we should abstract this into some helper, which would be located
2262 # near ensure_conversion(). Note that it is a convention of this
2263 # test suite for a mime.types file to be located in the top level
2264 # of the CVS repository to which it applies.
2265 self.mime_path = os.path.join(
2266 test_data_dir, 'eol-mime-cvsrepos', 'mime.types')
2268 Cvs2SvnPropertiesTestCase.__init__(
2269 self, 'eol-mime',
2270 props_to_test=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2271 args=['--mime-types=%s' % self.mime_path] + args,
2272 **kw)
2275 # We do four conversions. Each time, we pass --mime-types=FILE with
2276 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2277 # Thus there's one conversion with neither flag, one with just the
2278 # former, one with just the latter, and one with both.
2281 # Neither --no-default-eol nor --eol-from-mime-type:
2282 eol_mime1 = EOLMime(
2283 variant=1,
2284 args=[],
2285 expected_props=[
2286 ('trunk/foo.txt', [None, None, None]),
2287 ('trunk/foo.xml', [None, 'text/xml', None]),
2288 ('trunk/foo.zip', [None, 'application/zip', None]),
2289 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2290 ('trunk/foo.csv', [None, 'text/csv', None]),
2291 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2295 # Just --no-default-eol, not --eol-from-mime-type:
2296 eol_mime2 = EOLMime(
2297 variant=2,
2298 args=['--default-eol=native'],
2299 expected_props=[
2300 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2301 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2302 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS]),
2303 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2304 ('trunk/foo.csv', [None, 'text/csv', None]),
2305 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2309 # Just --eol-from-mime-type, not --no-default-eol:
2310 eol_mime3 = EOLMime(
2311 variant=3,
2312 args=['--eol-from-mime-type'],
2313 expected_props=[
2314 ('trunk/foo.txt', [None, None, None]),
2315 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2316 ('trunk/foo.zip', [None, 'application/zip', None]),
2317 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2318 ('trunk/foo.csv', [None, 'text/csv', None]),
2319 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2323 # Both --no-default-eol and --eol-from-mime-type:
2324 eol_mime4 = EOLMime(
2325 variant=4,
2326 args=['--eol-from-mime-type', '--default-eol=native'],
2327 expected_props=[
2328 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2329 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2330 ('trunk/foo.zip', [None, 'application/zip', None]),
2331 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2332 ('trunk/foo.csv', [None, 'text/csv', None]),
2333 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2337 cvs_revnums_off = Cvs2SvnPropertiesTestCase(
2338 'eol-mime',
2339 doc='test non-setting of cvs2svn:cvs-rev property',
2340 args=[],
2341 props_to_test=['cvs2svn:cvs-rev'],
2342 expected_props=[
2343 ('trunk/foo.txt', [None]),
2344 ('trunk/foo.xml', [None]),
2345 ('trunk/foo.zip', [None]),
2346 ('trunk/foo.bin', [None]),
2347 ('trunk/foo.csv', [None]),
2348 ('trunk/foo.dbf', [None]),
2352 cvs_revnums_on = Cvs2SvnPropertiesTestCase(
2353 'eol-mime',
2354 doc='test setting of cvs2svn:cvs-rev property',
2355 args=['--cvs-revnums'],
2356 props_to_test=['cvs2svn:cvs-rev'],
2357 expected_props=[
2358 ('trunk/foo.txt', ['1.2']),
2359 ('trunk/foo.xml', ['1.2']),
2360 ('trunk/foo.zip', ['1.2']),
2361 ('trunk/foo.bin', ['1.2']),
2362 ('trunk/foo.csv', ['1.2']),
2363 ('trunk/foo.dbf', ['1.2']),
2367 keywords = Cvs2SvnPropertiesTestCase(
2368 'keywords',
2369 doc='test setting of svn:keywords property among others',
2370 args=['--default-eol=native'],
2371 props_to_test=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2372 expected_props=[
2373 ('trunk/foo.default', [KEYWORDS, 'native', None]),
2374 ('trunk/foo.kkvl', [KEYWORDS, 'native', None]),
2375 ('trunk/foo.kkv', [KEYWORDS, 'native', None]),
2376 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2377 ('trunk/foo.kk', [None, 'native', None]),
2378 ('trunk/foo.ko', [None, 'native', None]),
2379 ('trunk/foo.kv', [None, 'native', None]),
2383 @Cvs2SvnTestFunction
2384 def ignore():
2385 "test setting of svn:ignore property"
2386 conv = ensure_conversion('cvsignore')
2387 wc_tree = conv.get_wc_tree()
2388 topdir_props = props_for_path(wc_tree, 'trunk/proj')
2389 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
2391 if topdir_props['svn:ignore'] != \
2392 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2393 raise Failure()
2395 if subdir_props['svn:ignore'] != \
2396 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2397 raise Failure()
2400 @Cvs2SvnTestFunction
2401 def requires_cvs():
2402 "test that CVS can still do what RCS can't"
2403 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2404 conv = ensure_conversion('requires-cvs', args=["--use-cvs"])
2406 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
2407 cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read()
2409 if atsign_contents[-1:] == "@":
2410 raise Failure()
2411 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
2412 raise Failure()
2414 if not (conv.logs[21].author == "William Lyon Phelps III" and
2415 conv.logs[20].author == "j random"):
2416 raise Failure()
2419 @Cvs2SvnTestFunction
2420 def questionable_branch_names():
2421 "test that we can handle weird branch names"
2422 conv = ensure_conversion('questionable-symbols')
2423 # If the conversion succeeds, then we're okay. We could check the
2424 # actual branch paths, too, but the main thing is to know that the
2425 # conversion doesn't fail.
2428 @Cvs2SvnTestFunction
2429 def questionable_tag_names():
2430 "test that we can handle weird tag names"
2431 conv = ensure_conversion('questionable-symbols')
2432 conv.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2433 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2435 conv.find_tag_log('TagWith/Backslash_E').check(
2436 sym_log_msg('TagWith/Backslash_E',1),
2438 ('/%(tags)s/TagWith', 'A'),
2439 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2442 conv.find_tag_log('TagWith/Slash_Z').check(
2443 sym_log_msg('TagWith/Slash_Z',1),
2445 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2450 @Cvs2SvnTestFunction
2451 def revision_reorder_bug():
2452 "reveal a bug that reorders file revisions"
2453 conv = ensure_conversion('revision-reorder-bug')
2454 # If the conversion succeeds, then we're okay. We could check the
2455 # actual revisions, too, but the main thing is to know that the
2456 # conversion doesn't fail.
2459 @Cvs2SvnTestFunction
2460 def exclude():
2461 "test that exclude really excludes everything"
2462 conv = ensure_conversion('main', args=['--exclude=.*'])
2463 for log in conv.logs.values():
2464 for item in log.changed_paths.keys():
2465 if item.startswith('/branches/') or item.startswith('/tags/'):
2466 raise Failure()
2469 @Cvs2SvnTestFunction
2470 def vendor_branch_delete_add():
2471 "add trunk file that was deleted on vendor branch"
2472 # This will error if the bug is present
2473 conv = ensure_conversion('vendor-branch-delete-add')
2476 @Cvs2SvnTestFunction
2477 def resync_pass2_pull_forward():
2478 "ensure pass2 doesn't pull rev too far forward"
2479 conv = ensure_conversion('resync-pass2-pull-forward')
2480 # If the conversion succeeds, then we're okay. We could check the
2481 # actual revisions, too, but the main thing is to know that the
2482 # conversion doesn't fail.
2485 @Cvs2SvnTestFunction
2486 def native_eol():
2487 "only LFs for svn:eol-style=native files"
2488 conv = ensure_conversion('native-eol', args=['--default-eol=native'])
2489 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
2490 conv.repos)
2491 # Verify that all files in the dump have LF EOLs. We're actually
2492 # testing the whole dump file, but the dump file itself only uses
2493 # LF EOLs, so we're safe.
2494 for line in lines:
2495 if line[-1] != '\n' or line[:-1].find('\r') != -1:
2496 raise Failure()
2499 @Cvs2SvnTestFunction
2500 def double_fill():
2501 "reveal a bug that created a branch twice"
2502 conv = ensure_conversion('double-fill')
2503 # If the conversion succeeds, then we're okay. We could check the
2504 # actual revisions, too, but the main thing is to know that the
2505 # conversion doesn't fail.
2508 @Cvs2SvnTestFunction
2509 def double_fill2():
2510 "reveal a second bug that created a branch twice"
2511 conv = ensure_conversion('double-fill2')
2512 conv.logs[6].check_msg(sym_log_msg('BRANCH1'))
2513 conv.logs[7].check_msg(sym_log_msg('BRANCH2'))
2514 try:
2515 # This check should fail:
2516 conv.logs[8].check_msg(sym_log_msg('BRANCH2'))
2517 except Failure:
2518 pass
2519 else:
2520 raise Failure('Symbol filled twice in a row')
2523 @Cvs2SvnTestFunction
2524 def resync_pass2_push_backward():
2525 "ensure pass2 doesn't push rev too far backward"
2526 conv = ensure_conversion('resync-pass2-push-backward')
2527 # If the conversion succeeds, then we're okay. We could check the
2528 # actual revisions, too, but the main thing is to know that the
2529 # conversion doesn't fail.
2532 @Cvs2SvnTestFunction
2533 def double_add():
2534 "reveal a bug that added a branch file twice"
2535 conv = ensure_conversion('double-add')
2536 # If the conversion succeeds, then we're okay. We could check the
2537 # actual revisions, too, but the main thing is to know that the
2538 # conversion doesn't fail.
2541 @Cvs2SvnTestFunction
2542 def bogus_branch_copy():
2543 "reveal a bug that copies a branch file wrongly"
2544 conv = ensure_conversion('bogus-branch-copy')
2545 # If the conversion succeeds, then we're okay. We could check the
2546 # actual revisions, too, but the main thing is to know that the
2547 # conversion doesn't fail.
2550 @Cvs2SvnTestFunction
2551 def nested_ttb_directories():
2552 "require error if ttb directories are not disjoint"
2553 opts_list = [
2554 {'trunk' : 'a', 'branches' : 'a',},
2555 {'trunk' : 'a', 'tags' : 'a',},
2556 {'branches' : 'a', 'tags' : 'a',},
2557 # This option conflicts with the default trunk path:
2558 {'branches' : 'trunk',},
2559 # Try some nested directories:
2560 {'trunk' : 'a', 'branches' : 'a/b',},
2561 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2562 {'branches' : 'a', 'tags' : 'a/b',},
2565 for opts in opts_list:
2566 ensure_conversion(
2567 'main', error_re=r'The following paths are not disjoint\:', **opts
2571 class AutoProps(Cvs2SvnPropertiesTestCase):
2572 """Test auto-props.
2574 The files are as follows:
2576 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2577 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2578 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2579 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2580 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2581 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2582 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2583 trunk/foo.UPCASE1: no -kb, no mime type.
2584 trunk/foo.UPCASE2: no -kb, no mime type.
2587 def __init__(self, args, **kw):
2588 ### TODO: It's a bit klugey to construct this path here. See also
2589 ### the comment in eol_mime().
2590 auto_props_path = os.path.join(
2591 test_data_dir, 'eol-mime-cvsrepos', 'auto-props')
2593 Cvs2SvnPropertiesTestCase.__init__(
2594 self, 'eol-mime',
2595 props_to_test=[
2596 'myprop',
2597 'svn:eol-style',
2598 'svn:mime-type',
2599 'svn:keywords',
2600 'svn:executable',
2602 args=[
2603 '--auto-props=%s' % auto_props_path,
2604 '--eol-from-mime-type'
2605 ] + args,
2606 **kw)
2609 auto_props_ignore_case = AutoProps(
2610 doc="test auto-props",
2611 args=['--default-eol=native'],
2612 expected_props=[
2613 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS, None]),
2614 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS, None]),
2615 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2616 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2617 ('trunk/foo.bin',
2618 ['bin', None, 'application/octet-stream', None, '']),
2619 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2620 ('trunk/foo.dbf',
2621 ['dbf', None, 'application/what-is-dbf', None, None]),
2622 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS, None]),
2623 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS, None]),
2627 @Cvs2SvnTestFunction
2628 def ctrl_char_in_filename():
2629 "do not allow control characters in filenames"
2631 try:
2632 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
2633 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
2634 if os.path.exists(dstrepos_path):
2635 safe_rmtree(dstrepos_path)
2637 # create repos from existing main repos
2638 shutil.copytree(srcrepos_path, dstrepos_path)
2639 base_path = os.path.join(dstrepos_path, 'single-files')
2640 try:
2641 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2642 os.path.join(base_path, 'two\rquick,v'))
2643 except:
2644 # Operating systems that don't allow control characters in
2645 # filenames will hopefully have thrown an exception; in that
2646 # case, just skip this test.
2647 raise svntest.Skip()
2649 conv = ensure_conversion(
2650 'ctrl-char-filename',
2651 error_re=(r'.*Character .* in filename .* '
2652 r'is not supported by Subversion\.'),
2654 finally:
2655 safe_rmtree(dstrepos_path)
2658 @Cvs2SvnTestFunction
2659 def commit_dependencies():
2660 "interleaved and multi-branch commits to same files"
2661 conv = ensure_conversion("commit-dependencies")
2662 conv.logs[2].check('adding', (
2663 ('/%(trunk)s/interleaved', 'A'),
2664 ('/%(trunk)s/interleaved/file1', 'A'),
2665 ('/%(trunk)s/interleaved/file2', 'A'),
2667 conv.logs[3].check('big commit', (
2668 ('/%(trunk)s/interleaved/file1', 'M'),
2669 ('/%(trunk)s/interleaved/file2', 'M'),
2671 conv.logs[4].check('dependant small commit', (
2672 ('/%(trunk)s/interleaved/file1', 'M'),
2674 conv.logs[5].check('adding', (
2675 ('/%(trunk)s/multi-branch', 'A'),
2676 ('/%(trunk)s/multi-branch/file1', 'A'),
2677 ('/%(trunk)s/multi-branch/file2', 'A'),
2679 conv.logs[6].check(sym_log_msg("branch"), (
2680 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2681 ('/%(branches)s/branch/interleaved', 'D'),
2683 conv.logs[7].check('multi-branch-commit', (
2684 ('/%(trunk)s/multi-branch/file1', 'M'),
2685 ('/%(trunk)s/multi-branch/file2', 'M'),
2686 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2687 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2691 @Cvs2SvnTestFunction
2692 def double_branch_delete():
2693 "fill branches before modifying files on them"
2694 conv = ensure_conversion('double-branch-delete')
2696 # Test for issue #102. The file IMarshalledValue.java is branched,
2697 # deleted, readded on the branch, and then deleted again. If the
2698 # fill for the file on the branch is postponed until after the
2699 # modification, the file will end up live on the branch instead of
2700 # dead! Make sure it happens at the right time.
2702 conv.logs[6].check('JBAS-2436 - Adding LGPL Header2', (
2703 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2706 conv.logs[7].check('JBAS-3025 - Removing dependency', (
2707 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2711 @Cvs2SvnTestFunction
2712 def symbol_mismatches():
2713 "error for conflicting tag/branch"
2715 ensure_conversion(
2716 'symbol-mess',
2717 args=['--symbol-default=strict'],
2718 error_re=r'.*Problems determining how symbols should be converted',
2722 @Cvs2SvnTestFunction
2723 def overlook_symbol_mismatches():
2724 "overlook conflicting tag/branch when --trunk-only"
2726 # This is a test for issue #85.
2728 ensure_conversion('symbol-mess', args=['--trunk-only'])
2731 @Cvs2SvnTestFunction
2732 def force_symbols():
2733 "force symbols to be tags/branches"
2735 conv = ensure_conversion(
2736 'symbol-mess',
2737 args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2738 if conv.path_exists('tags', 'BRANCH') \
2739 or not conv.path_exists('branches', 'BRANCH'):
2740 raise Failure()
2741 if not conv.path_exists('tags', 'TAG') \
2742 or conv.path_exists('branches', 'TAG'):
2743 raise Failure()
2744 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2745 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2746 raise Failure()
2747 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2748 or conv.path_exists('branches', 'MOSTLY_TAG'):
2749 raise Failure()
2752 @Cvs2SvnTestFunction
2753 def commit_blocks_tags():
2754 "commit prevents forced tag"
2756 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2757 ensure_conversion(
2758 'symbol-mess',
2759 args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']),
2760 error_re=(
2761 r'.*The following branches cannot be forced to be tags '
2762 r'because they have commits'
2767 @Cvs2SvnTestFunction
2768 def blocked_excludes():
2769 "error for blocked excludes"
2771 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2772 for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']:
2773 try:
2774 ensure_conversion(
2775 'symbol-mess',
2776 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker]))
2777 raise MissingErrorException()
2778 except Failure:
2779 pass
2782 @Cvs2SvnTestFunction
2783 def unblock_blocked_excludes():
2784 "excluding blocker removes blockage"
2786 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2787 for blocker in ['BRANCH', 'COMMIT']:
2788 ensure_conversion(
2789 'symbol-mess',
2790 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker,
2791 '--exclude=BLOCKING_%s' % blocker]))
2794 @Cvs2SvnTestFunction
2795 def regexp_force_symbols():
2796 "force symbols via regular expressions"
2798 conv = ensure_conversion(
2799 'symbol-mess',
2800 args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2801 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2802 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2803 raise Failure()
2804 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2805 or conv.path_exists('branches', 'MOSTLY_TAG'):
2806 raise Failure()
2809 @Cvs2SvnTestFunction
2810 def heuristic_symbol_default():
2811 "test 'heuristic' symbol default"
2813 conv = ensure_conversion(
2814 'symbol-mess', args=['--symbol-default=heuristic'])
2815 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2816 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2817 raise Failure()
2818 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2819 or conv.path_exists('branches', 'MOSTLY_TAG'):
2820 raise Failure()
2823 @Cvs2SvnTestFunction
2824 def branch_symbol_default():
2825 "test 'branch' symbol default"
2827 conv = ensure_conversion(
2828 'symbol-mess', args=['--symbol-default=branch'])
2829 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2830 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2831 raise Failure()
2832 if conv.path_exists('tags', 'MOSTLY_TAG') \
2833 or not conv.path_exists('branches', 'MOSTLY_TAG'):
2834 raise Failure()
2837 @Cvs2SvnTestFunction
2838 def tag_symbol_default():
2839 "test 'tag' symbol default"
2841 conv = ensure_conversion(
2842 'symbol-mess', args=['--symbol-default=tag'])
2843 if not conv.path_exists('tags', 'MOSTLY_BRANCH') \
2844 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
2845 raise Failure()
2846 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2847 or conv.path_exists('branches', 'MOSTLY_TAG'):
2848 raise Failure()
2851 @Cvs2SvnTestFunction
2852 def symbol_transform():
2853 "test --symbol-transform"
2855 conv = ensure_conversion(
2856 'symbol-mess',
2857 args=[
2858 '--symbol-default=heuristic',
2859 '--symbol-transform=BRANCH:branch',
2860 '--symbol-transform=TAG:tag',
2861 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2863 if not conv.path_exists('branches', 'branch'):
2864 raise Failure()
2865 if not conv.path_exists('tags', 'tag'):
2866 raise Failure()
2867 if not conv.path_exists('branches', 'MOSTLY.BRANCH'):
2868 raise Failure()
2869 if not conv.path_exists('tags', 'MOSTLY.TAG'):
2870 raise Failure()
2873 @Cvs2SvnTestFunction
2874 def write_symbol_info():
2875 "test --write-symbol-info"
2877 expected_lines = [
2878 ['0', '.trunk.',
2879 'trunk', 'trunk', '.'],
2880 ['0', 'BLOCKED_BY_UNNAMED',
2881 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2882 ['0', 'BLOCKING_COMMIT',
2883 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2884 ['0', 'BLOCKED_BY_COMMIT',
2885 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2886 ['0', 'BLOCKING_BRANCH',
2887 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2888 ['0', 'BLOCKED_BY_BRANCH',
2889 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2890 ['0', 'MOSTLY_BRANCH',
2891 '.', '.', '.'],
2892 ['0', 'MOSTLY_TAG',
2893 '.', '.', '.'],
2894 ['0', 'BRANCH_WITH_COMMIT',
2895 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2896 ['0', 'BRANCH',
2897 'branch', 'branches/BRANCH', '.trunk.'],
2898 ['0', 'TAG',
2899 'tag', 'tags/TAG', '.trunk.'],
2900 ['0', 'unlabeled-1.1.12.1.2',
2901 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2903 expected_lines.sort()
2905 symbol_info_file = os.path.join(tmp_dir, 'symbol-mess-symbol-info.txt')
2906 try:
2907 ensure_conversion(
2908 'symbol-mess',
2909 args=[
2910 '--symbol-default=strict',
2911 '--write-symbol-info=%s' % (symbol_info_file,),
2912 '--passes=:CollateSymbolsPass',
2915 raise MissingErrorException()
2916 except Failure:
2917 pass
2918 lines = []
2919 comment_re = re.compile(r'^\s*\#')
2920 for l in open(symbol_info_file, 'r'):
2921 if comment_re.match(l):
2922 continue
2923 lines.append(l.strip().split())
2924 lines.sort()
2925 if lines != expected_lines:
2926 s = ['Symbol info incorrect\n']
2927 differ = Differ()
2928 for diffline in differ.compare(
2929 [' '.join(line) + '\n' for line in expected_lines],
2930 [' '.join(line) + '\n' for line in lines],
2932 s.append(diffline)
2933 raise Failure(''.join(s))
2936 @Cvs2SvnTestFunction
2937 def symbol_hints():
2938 "test --symbol-hints for setting branch/tag"
2940 conv = ensure_conversion(
2941 'symbol-mess', symbol_hints_file='symbol-mess-symbol-hints.txt',
2943 if not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2944 raise Failure()
2945 if not conv.path_exists('tags', 'MOSTLY_TAG'):
2946 raise Failure()
2947 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2948 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2950 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2951 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
2953 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2954 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
2958 @Cvs2SvnTestFunction
2959 def parent_hints():
2960 "test --symbol-hints for setting parent"
2962 conv = ensure_conversion(
2963 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints.txt',
2965 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2966 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
2970 @Cvs2SvnTestFunction
2971 def parent_hints_invalid():
2972 "test --symbol-hints with an invalid parent"
2974 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2975 # this symbol hints file sets the preferred parent to BRANCH
2976 # instead:
2977 conv = ensure_conversion(
2978 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-invalid.txt',
2979 error_re=(
2980 r"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
2985 @Cvs2SvnTestFunction
2986 def parent_hints_wildcards():
2987 "test --symbol-hints wildcards"
2989 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2990 # this symbol hints file sets the preferred parent to BRANCH
2991 # instead:
2992 conv = ensure_conversion(
2993 'symbol-mess',
2994 symbol_hints_file='symbol-mess-parent-hints-wildcards.txt',
2996 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2997 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3001 @Cvs2SvnTestFunction
3002 def path_hints():
3003 "test --symbol-hints for setting svn paths"
3005 conv = ensure_conversion(
3006 'symbol-mess', symbol_hints_file='symbol-mess-path-hints.txt',
3008 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3009 ('/trunk', 'A'),
3010 ('/a', 'A'),
3011 ('/a/strange', 'A'),
3012 ('/a/strange/trunk', 'A'),
3013 ('/a/strange/trunk/path', 'A'),
3014 ('/branches', 'A'),
3015 ('/tags', 'A'),
3017 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3018 ('/special', 'A'),
3019 ('/special/tag', 'A'),
3020 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
3022 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3023 ('/special/other', 'A'),
3024 ('/special/other/branch', 'A'),
3025 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
3027 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3028 ('/special/branch', 'A'),
3029 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
3033 @Cvs2SvnTestFunction
3034 def issue_99():
3035 "test problem from issue 99"
3037 conv = ensure_conversion('issue-99')
3040 @Cvs2SvnTestFunction
3041 def issue_100():
3042 "test problem from issue 100"
3044 conv = ensure_conversion('issue-100')
3045 file1 = conv.get_wc('trunk', 'file1.txt')
3046 if file(file1).read() != 'file1.txt<1.2>\n':
3047 raise Failure()
3050 @Cvs2SvnTestFunction
3051 def issue_106():
3052 "test problem from issue 106"
3054 conv = ensure_conversion('issue-106')
3057 @Cvs2SvnTestFunction
3058 def options_option():
3059 "use of the --options option"
3061 conv = ensure_conversion('main', options_file='cvs2svn.options')
3064 @Cvs2SvnTestFunction
3065 def multiproject():
3066 "multiproject conversion"
3068 conv = ensure_conversion(
3069 'main', options_file='cvs2svn-multiproject.options'
3071 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3072 ('/partial-prune', 'A'),
3073 ('/partial-prune/trunk', 'A'),
3074 ('/partial-prune/branches', 'A'),
3075 ('/partial-prune/tags', 'A'),
3076 ('/partial-prune/releases', 'A'),
3080 @Cvs2SvnTestFunction
3081 def crossproject():
3082 "multiproject conversion with cross-project commits"
3084 conv = ensure_conversion(
3085 'main', options_file='cvs2svn-crossproject.options'
3089 @Cvs2SvnTestFunction
3090 def tag_with_no_revision():
3091 "tag defined but revision is deleted"
3093 conv = ensure_conversion('tag-with-no-revision')
3096 @Cvs2SvnTestFunction
3097 def delete_cvsignore():
3098 "svn:ignore should vanish when .cvsignore does"
3100 # This is issue #81.
3102 conv = ensure_conversion('delete-cvsignore')
3104 wc_tree = conv.get_wc_tree()
3105 props = props_for_path(wc_tree, 'trunk/proj')
3107 if props.has_key('svn:ignore'):
3108 raise Failure()
3111 @Cvs2SvnTestFunction
3112 def repeated_deltatext():
3113 "ignore repeated deltatext blocks with warning"
3115 conv = ensure_conversion('repeated-deltatext')
3116 warning_re = r'.*Deltatext block for revision 1.1 appeared twice'
3117 if not conv.output_found(warning_re):
3118 raise Failure()
3121 @Cvs2SvnTestFunction
3122 def nasty_graphs():
3123 "process some nasty dependency graphs"
3125 # It's not how well the bear can dance, but that the bear can dance
3126 # at all:
3127 conv = ensure_conversion('nasty-graphs')
3130 @Cvs2SvnTestFunction
3131 def tagging_after_delete():
3132 "optimal tag after deleting files"
3134 conv = ensure_conversion('tagging-after-delete')
3136 # tag should be 'clean', no deletes
3137 log = conv.find_tag_log('tag1')
3138 expected = (
3139 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
3141 log.check_changes(expected)
3144 @Cvs2SvnTestFunction
3145 def crossed_branches():
3146 "branches created in inconsistent orders"
3148 conv = ensure_conversion('crossed-branches')
3151 @Cvs2SvnTestFunction
3152 def file_directory_conflict():
3153 "error when filename conflicts with directory name"
3155 conv = ensure_conversion(
3156 'file-directory-conflict',
3157 error_re=r'.*Directory name conflicts with filename',
3161 @Cvs2SvnTestFunction
3162 def attic_directory_conflict():
3163 "error when attic filename conflicts with dirname"
3165 # This tests the problem reported in issue #105.
3167 conv = ensure_conversion(
3168 'attic-directory-conflict',
3169 error_re=r'.*Directory name conflicts with filename',
3173 @Cvs2SvnTestFunction
3174 def internal_co():
3175 "verify that --use-internal-co works"
3177 rcs_conv = ensure_conversion(
3178 'main', args=['--use-rcs', '--default-eol=native'],
3180 conv = ensure_conversion(
3181 'main', args=['--default-eol=native'],
3183 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3184 raise Failure()
3185 rcs_lines = run_program(
3186 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3187 rcs_conv.repos)
3188 lines = run_program(
3189 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3190 conv.repos)
3191 # Compare all lines following the repository UUID:
3192 if lines[3:] != rcs_lines[3:]:
3193 raise Failure()
3196 @Cvs2SvnTestFunction
3197 def internal_co_exclude():
3198 "verify that --use-internal-co --exclude=... works"
3200 rcs_conv = ensure_conversion(
3201 'internal-co',
3202 args=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3204 conv = ensure_conversion(
3205 'internal-co',
3206 args=['--exclude=BRANCH', '--default-eol=native'],
3208 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3209 raise Failure()
3210 rcs_lines = run_program(
3211 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3212 rcs_conv.repos)
3213 lines = run_program(
3214 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3215 conv.repos)
3216 # Compare all lines following the repository UUID:
3217 if lines[3:] != rcs_lines[3:]:
3218 raise Failure()
3221 @Cvs2SvnTestFunction
3222 def internal_co_trunk_only():
3223 "verify that --use-internal-co --trunk-only works"
3225 conv = ensure_conversion(
3226 'internal-co',
3227 args=['--trunk-only', '--default-eol=native'],
3229 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3230 raise Failure()
3233 @Cvs2SvnTestFunction
3234 def leftover_revs():
3235 "check for leftover checked-out revisions"
3237 conv = ensure_conversion(
3238 'leftover-revs',
3239 args=['--exclude=BRANCH', '--default-eol=native'],
3241 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3242 raise Failure()
3245 @Cvs2SvnTestFunction
3246 def requires_internal_co():
3247 "test that internal co can do more than RCS"
3248 # See issues 4, 11 for the bugs whose regression we're testing for.
3249 # Unlike in requires_cvs above, issue 29 is not covered.
3250 conv = ensure_conversion('requires-cvs')
3252 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
3254 if atsign_contents[-1:] == "@":
3255 raise Failure()
3257 if not (conv.logs[21].author == "William Lyon Phelps III" and
3258 conv.logs[20].author == "j random"):
3259 raise Failure()
3262 @Cvs2SvnTestFunction
3263 def internal_co_keywords():
3264 "test that internal co handles keywords correctly"
3265 conv_ic = ensure_conversion('internal-co-keywords',
3266 args=["--keywords-off"])
3267 conv_cvs = ensure_conversion('internal-co-keywords',
3268 args=["--use-cvs", "--keywords-off"])
3270 ko_ic = file(conv_ic.get_wc('trunk', 'dir', 'ko.txt')).read()
3271 ko_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'ko.txt')).read()
3272 kk_ic = file(conv_ic.get_wc('trunk', 'dir', 'kk.txt')).read()
3273 kk_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kk.txt')).read()
3274 kv_ic = file(conv_ic.get_wc('trunk', 'dir', 'kv.txt')).read()
3275 kv_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kv.txt')).read()
3277 if ko_ic != ko_cvs:
3278 raise Failure()
3279 if kk_ic != kk_cvs:
3280 raise Failure()
3282 # The date format changed between cvs and co ('/' instead of '-').
3283 # Accept either one:
3284 date_substitution_re = re.compile(r' ([0-9]*)-([0-9]*)-([0-9]*) ')
3285 if kv_ic != kv_cvs \
3286 and date_substitution_re.sub(r' \1/\2/\3 ', kv_ic) != kv_cvs:
3287 raise Failure()
3290 @Cvs2SvnTestFunction
3291 def timestamp_chaos():
3292 "test timestamp adjustments"
3294 conv = ensure_conversion('timestamp-chaos', args=["-v"])
3296 # The times are expressed here in UTC:
3297 times = [
3298 '2007-01-01 21:00:00', # Initial commit
3299 '2007-01-01 21:00:00', # revision 1.1 of both files
3300 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3301 '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards
3302 '2007-01-01 22:00:00', # revision 1.3 of both files
3305 # Convert the times to seconds since the epoch, in UTC:
3306 times = [calendar.timegm(svn_strptime(t)) for t in times]
3308 for i in range(len(times)):
3309 if abs(conv.logs[i + 1].date - times[i]) > 0.1:
3310 raise Failure()
3313 @Cvs2SvnTestFunction
3314 def symlinks():
3315 "convert a repository that contains symlinks"
3317 # This is a test for issue #97.
3319 proj = os.path.join(test_data_dir, 'symlinks-cvsrepos', 'proj')
3320 links = [
3322 os.path.join('..', 'file.txt,v'),
3323 os.path.join(proj, 'dir1', 'file.txt,v'),
3326 'dir1',
3327 os.path.join(proj, 'dir2'),
3331 try:
3332 os.symlink
3333 except AttributeError:
3334 # Apparently this OS doesn't support symlinks, so skip test.
3335 raise svntest.Skip()
3337 try:
3338 for (src,dst) in links:
3339 os.symlink(src, dst)
3341 conv = ensure_conversion('symlinks')
3342 conv.logs[2].check('', (
3343 ('/%(trunk)s/proj', 'A'),
3344 ('/%(trunk)s/proj/file.txt', 'A'),
3345 ('/%(trunk)s/proj/dir1', 'A'),
3346 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3347 ('/%(trunk)s/proj/dir2', 'A'),
3348 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3350 finally:
3351 for (src,dst) in links:
3352 os.remove(dst)
3355 @Cvs2SvnTestFunction
3356 def empty_trunk_path():
3357 "allow --trunk to be empty if --trunk-only"
3359 # This is a test for issue #53.
3361 conv = ensure_conversion(
3362 'main', args=['--trunk-only', '--trunk='],
3366 @Cvs2SvnTestFunction
3367 def preferred_parent_cycle():
3368 "handle a cycle in branch parent preferences"
3370 conv = ensure_conversion('preferred-parent-cycle')
3373 @Cvs2SvnTestFunction
3374 def branch_from_empty_dir():
3375 "branch from an empty directory"
3377 conv = ensure_conversion('branch-from-empty-dir')
3380 @Cvs2SvnTestFunction
3381 def trunk_readd():
3382 "add a file on a branch then on trunk"
3384 conv = ensure_conversion('trunk-readd')
3387 @Cvs2SvnTestFunction
3388 def branch_from_deleted_1_1():
3389 "branch from a 1.1 revision that will be deleted"
3391 conv = ensure_conversion('branch-from-deleted-1-1')
3392 conv.logs[5].check('Adding b.txt:1.1.2.1', (
3393 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3395 conv.logs[6].check('Adding b.txt:1.1.4.1', (
3396 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3398 conv.logs[7].check('Adding b.txt:1.2', (
3399 ('/%(trunk)s/proj/b.txt', 'A'),
3402 conv.logs[8].check('Adding c.txt:1.1.2.1', (
3403 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3405 conv.logs[9].check('Adding c.txt:1.1.4.1', (
3406 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3410 @Cvs2SvnTestFunction
3411 def add_on_branch():
3412 "add a file on a branch using newer CVS"
3414 conv = ensure_conversion('add-on-branch')
3415 conv.logs[6].check('Adding b.txt:1.1', (
3416 ('/%(trunk)s/proj/b.txt', 'A'),
3418 conv.logs[7].check('Adding b.txt:1.1.2.2', (
3419 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3421 conv.logs[8].check('Adding c.txt:1.1', (
3422 ('/%(trunk)s/proj/c.txt', 'A'),
3424 conv.logs[9].check('Removing c.txt:1.2', (
3425 ('/%(trunk)s/proj/c.txt', 'D'),
3427 conv.logs[10].check('Adding c.txt:1.2.2.2', (
3428 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3430 conv.logs[11].check('Adding d.txt:1.1', (
3431 ('/%(trunk)s/proj/d.txt', 'A'),
3433 conv.logs[12].check('Adding d.txt:1.1.2.2', (
3434 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3438 @Cvs2SvnTestFunction
3439 def main_git():
3440 "test output in git-fast-import format"
3442 # Note: To test importing into git, do
3444 # ./run-tests <test-number>
3445 # rm -rf .git
3446 # git-init
3447 # cat cvs2svn-tmp/{blobfile,dumpfile}.out | git fast-import
3449 # Or, to load the dumpfiles separately:
3451 # cat cvs2svn-tmp/git-blob.dat \
3452 # | git fast-import --export-marks=cvs2svn-tmp/git-marks.dat
3453 # cat cvs2svn-tmp/git-dump.dat \
3454 # | git fast-import --import-marks=cvs2svn-tmp/git-marks.dat
3456 # Then use "gitk --all", "git log", etc. to test the contents of the
3457 # repository.
3459 # We don't have the infrastructure to check that the resulting git
3460 # repository is correct, so we just check that the conversion runs
3461 # to completion:
3462 conv = GitConversion('main', None, [
3463 '--blobfile=cvs2svn-tmp/blobfile.out',
3464 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3465 '--username=cvs2git',
3466 'test-data/main-cvsrepos',
3470 @Cvs2SvnTestFunction
3471 def main_git2():
3472 "test cvs2git --use-external-blob-generator option"
3474 # See comment in main_git() for more information.
3476 conv = GitConversion('main', None, [
3477 '--use-external-blob-generator',
3478 '--blobfile=cvs2svn-tmp/blobfile.out',
3479 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3480 '--username=cvs2git',
3481 'test-data/main-cvsrepos',
3485 @Cvs2SvnTestFunction
3486 def git_options():
3487 "test cvs2git using options file"
3489 conv = GitConversion('main', None, [], options_file='cvs2git.options')
3492 @Cvs2SvnTestFunction
3493 def main_hg():
3494 "output in git-fast-import format with inline data"
3496 # The output should be suitable for import by Mercurial.
3498 # We don't have the infrastructure to check that the resulting
3499 # Mercurial repository is correct, so we just check that the
3500 # conversion runs to completion:
3501 conv = GitConversion('main', None, [], options_file='cvs2hg.options')
3504 @Cvs2SvnTestFunction
3505 def invalid_symbol():
3506 "a symbol with the incorrect format"
3508 conv = ensure_conversion('invalid-symbol')
3509 if not conv.output_found(
3510 r".*branch 'SYMBOL' references invalid revision 1$"
3512 raise Failure()
3515 @Cvs2SvnTestFunction
3516 def invalid_symbol_ignore():
3517 "ignore a symbol using a SymbolMapper"
3519 conv = ensure_conversion(
3520 'invalid-symbol', options_file='cvs2svn-ignore.options'
3524 @Cvs2SvnTestFunction
3525 def invalid_symbol_ignore2():
3526 "ignore a symbol using an IgnoreSymbolTransform"
3528 conv = ensure_conversion(
3529 'invalid-symbol', options_file='cvs2svn-ignore2.options'
3533 class EOLVariants(Cvs2SvnTestCase):
3534 "handle various --eol-style options"
3536 eol_style_strings = {
3537 'LF' : '\n',
3538 'CR' : '\r',
3539 'CRLF' : '\r\n',
3540 'native' : '\n',
3543 def __init__(self, eol_style):
3544 self.eol_style = eol_style
3545 self.dumpfile = 'eol-variants-%s.dump' % (self.eol_style,)
3546 Cvs2SvnTestCase.__init__(
3547 self, 'eol-variants', variant=self.eol_style,
3548 dumpfile=self.dumpfile,
3549 args=[
3550 '--default-eol=%s' % (self.eol_style,),
3554 def run(self, sbox):
3555 conv = self.ensure_conversion()
3556 dump_contents = open(conv.dumpfile, 'rb').read()
3557 expected_text = self.eol_style_strings[self.eol_style].join(
3558 ['line 1', 'line 2', '\n\n']
3560 if not dump_contents.endswith(expected_text):
3561 raise Failure()
3564 @Cvs2SvnTestFunction
3565 def no_revs_file():
3566 "handle a file with no revisions (issue #80)"
3568 conv = ensure_conversion('no-revs-file')
3571 @Cvs2SvnTestFunction
3572 def mirror_keyerror_test():
3573 "a case that gave KeyError in SVNRepositoryMirror"
3575 conv = ensure_conversion('mirror-keyerror')
3578 @Cvs2SvnTestFunction
3579 def exclude_ntdb_test():
3580 "exclude a non-trunk default branch"
3582 symbol_info_file = os.path.join(tmp_dir, 'exclude-ntdb-symbol-info.txt')
3583 conv = ensure_conversion(
3584 'exclude-ntdb',
3585 args=[
3586 '--write-symbol-info=%s' % (symbol_info_file,),
3587 '--exclude=branch3',
3588 '--exclude=tag3',
3589 '--exclude=vendortag3',
3590 '--exclude=vendorbranch',
3595 @Cvs2SvnTestFunction
3596 def mirror_keyerror2_test():
3597 "a case that gave KeyError in RepositoryMirror"
3599 conv = ensure_conversion('mirror-keyerror2')
3602 @Cvs2SvnTestFunction
3603 def mirror_keyerror3_test():
3604 "a case that gave KeyError in RepositoryMirror"
3606 conv = ensure_conversion('mirror-keyerror3')
3609 @Cvs2SvnTestFunction
3610 def add_cvsignore_to_branch_test():
3611 "check adding .cvsignore to an existing branch"
3613 # This a test for issue #122.
3615 conv = ensure_conversion('add-cvsignore-to-branch')
3616 wc_tree = conv.get_wc_tree()
3617 trunk_props = props_for_path(wc_tree, 'trunk/dir')
3618 if trunk_props['svn:ignore'] != '*.o\n\n':
3619 raise Failure()
3621 branch_props = props_for_path(wc_tree, 'branches/BRANCH/dir')
3622 if branch_props['svn:ignore'] != '*.o\n\n':
3623 raise Failure()
3626 @Cvs2SvnTestFunction
3627 def missing_deltatext():
3628 "a revision's deltatext is missing"
3630 # This is a type of RCS file corruption that has been observed.
3631 conv = ensure_conversion(
3632 'missing-deltatext',
3633 error_re=(
3634 r"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3639 @Cvs2SvnTestFunction
3640 def transform_unlabeled_branch_name():
3641 "transform name of unlabeled branch"
3643 conv = ensure_conversion(
3644 'unlabeled-branch',
3645 args=[
3646 '--symbol-transform=unlabeled-1.1.4:BRANCH2',
3651 @Cvs2SvnTestFunction
3652 def ignore_unlabeled_branch():
3653 "ignoring an unlabeled branch is not allowed"
3655 conv = ensure_conversion(
3656 'unlabeled-branch',
3657 options_file='cvs2svn-ignore.options',
3658 error_re=(
3659 r"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' "
3660 r"in \'.*\' contains commits"
3665 @Cvs2SvnTestFunction
3666 def unlabeled_branch_name_collision():
3667 "transform branch to same name as unlabeled branch"
3669 conv = ensure_conversion(
3670 'unlabeled-branch',
3671 args=[
3672 '--symbol-transform=unlabeled-1.1.4:BRANCH',
3674 error_re=(
3675 r"ERROR\: Symbol name \'BRANCH\' is already used"
3680 @Cvs2SvnTestFunction
3681 def collision_with_unlabeled_branch_name():
3682 "transform unlabeled branch to same name as branch"
3684 conv = ensure_conversion(
3685 'unlabeled-branch',
3686 args=[
3687 '--symbol-transform=BRANCH:unlabeled-1.1.4',
3689 error_re=(
3690 r"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used"
3695 @Cvs2SvnTestFunction
3696 def many_deletes():
3697 "a repo with many removable dead revisions"
3699 conv = ensure_conversion('many-deletes')
3700 conv.logs[5].check('Add files on BRANCH', (
3701 ('/%(branches)s/BRANCH/proj/b.txt', 'A'),
3703 conv.logs[6].check('Add files on BRANCH2', (
3704 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3705 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3706 ('/%(branches)s/BRANCH2/proj/d.txt', 'A'),
3710 cvs_description = Cvs2SvnPropertiesTestCase(
3711 'main',
3712 doc='test handling of CVS file descriptions',
3713 props_to_test=['cvs:description'],
3714 expected_props=[
3715 ('trunk/proj/default', ['This is an example file description.']),
3716 ('trunk/proj/sub1/default', [None]),
3720 @Cvs2SvnTestFunction
3721 def include_empty_directories():
3722 "test --include-empty-directories option"
3724 conv = ensure_conversion(
3725 'empty-directories', args=['--include-empty-directories'],
3727 conv.logs[1].check('Standard project directories', (
3728 ('/%(trunk)s', 'A'),
3729 ('/%(branches)s', 'A'),
3730 ('/%(tags)s', 'A'),
3731 ('/%(trunk)s/root-empty-directory', 'A'),
3732 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3734 conv.logs[3].check('Add b.txt.', (
3735 ('/%(trunk)s/direct', 'A'),
3736 ('/%(trunk)s/direct/b.txt', 'A'),
3737 ('/%(trunk)s/direct/empty-directory', 'A'),
3738 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3740 conv.logs[4].check('Add c.txt.', (
3741 ('/%(trunk)s/indirect', 'A'),
3742 ('/%(trunk)s/indirect/subdirectory', 'A'),
3743 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3744 ('/%(trunk)s/indirect/empty-directory', 'A'),
3745 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3747 conv.logs[5].check('Remove b.txt', (
3748 ('/%(trunk)s/direct', 'D'),
3750 conv.logs[6].check('Remove c.txt', (
3751 ('/%(trunk)s/indirect', 'D'),
3753 conv.logs[7].check('Re-add b.txt.', (
3754 ('/%(trunk)s/direct', 'A'),
3755 ('/%(trunk)s/direct/b.txt', 'A'),
3756 ('/%(trunk)s/direct/empty-directory', 'A'),
3757 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3759 conv.logs[8].check('Re-add c.txt.', (
3760 ('/%(trunk)s/indirect', 'A'),
3761 ('/%(trunk)s/indirect/subdirectory', 'A'),
3762 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3763 ('/%(trunk)s/indirect/empty-directory', 'A'),
3764 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3766 conv.logs[9].check('This commit was manufactured', (
3767 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3769 conv.logs[10].check('This commit was manufactured', (
3770 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3772 conv.logs[11].check('Import d.txt.', (
3773 ('/%(branches)s/VENDORBRANCH', 'A'),
3774 ('/%(branches)s/VENDORBRANCH/import', 'A'),
3775 ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'),
3776 ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'),
3777 ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory',
3778 'A'),
3779 ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'),
3780 ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory',
3781 'A'),
3783 conv.logs[12].check('This commit was generated', (
3784 ('/%(trunk)s/import', 'A'),
3785 ('/%(trunk)s/import/d.txt '
3786 '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'),
3787 ('/%(trunk)s/import/empty-directory', 'A'),
3788 ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'),
3792 @Cvs2SvnTestFunction
3793 def include_empty_directories_no_prune():
3794 "test --include-empty-directories with --no-prune"
3796 conv = ensure_conversion(
3797 'empty-directories', args=['--include-empty-directories', '--no-prune'],
3799 conv.logs[1].check('Standard project directories', (
3800 ('/%(trunk)s', 'A'),
3801 ('/%(branches)s', 'A'),
3802 ('/%(tags)s', 'A'),
3803 ('/%(trunk)s/root-empty-directory', 'A'),
3804 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3806 conv.logs[3].check('Add b.txt.', (
3807 ('/%(trunk)s/direct', 'A'),
3808 ('/%(trunk)s/direct/b.txt', 'A'),
3809 ('/%(trunk)s/direct/empty-directory', 'A'),
3810 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3812 conv.logs[4].check('Add c.txt.', (
3813 ('/%(trunk)s/indirect', 'A'),
3814 ('/%(trunk)s/indirect/subdirectory', 'A'),
3815 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3816 ('/%(trunk)s/indirect/empty-directory', 'A'),
3817 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3819 conv.logs[5].check('Remove b.txt', (
3820 ('/%(trunk)s/direct/b.txt', 'D'),
3822 conv.logs[6].check('Remove c.txt', (
3823 ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'),
3825 conv.logs[7].check('Re-add b.txt.', (
3826 ('/%(trunk)s/direct/b.txt', 'A'),
3828 conv.logs[8].check('Re-add c.txt.', (
3829 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3831 conv.logs[9].check('This commit was manufactured', (
3832 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3834 conv.logs[10].check('This commit was manufactured', (
3835 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3839 @Cvs2SvnTestFunction
3840 def exclude_symbol_default():
3841 "test 'exclude' symbol default"
3843 conv = ensure_conversion(
3844 'symbol-mess', args=['--symbol-default=exclude'])
3845 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
3846 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
3847 raise Failure()
3848 if conv.path_exists('tags', 'MOSTLY_TAG') \
3849 or conv.path_exists('branches', 'MOSTLY_TAG'):
3850 raise Failure()
3853 @Cvs2SvnTestFunction
3854 def add_on_branch2():
3855 "another add-on-branch test case"
3857 conv = ensure_conversion('add-on-branch2')
3858 if len(conv.logs) != 2:
3859 raise Failure()
3860 conv.logs[2].check('add file on branch', (
3861 ('/%(branches)s/BRANCH', 'A'),
3862 ('/%(branches)s/BRANCH/file1', 'A'),
3866 ########################################################################
3867 # Run the tests
3869 # list all tests here, starting with None:
3870 test_list = [
3871 None,
3872 # 1:
3873 show_usage,
3874 cvs2svn_manpage,
3875 cvs2git_manpage,
3876 XFail(cvs2hg_manpage),
3877 attr_exec,
3878 space_fname,
3879 two_quick,
3880 PruneWithCare(),
3881 PruneWithCare(variant=1, trunk='a', branches='b', tags='c'),
3882 # 10:
3883 PruneWithCare(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3884 PruneWithCare(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3885 interleaved_commits,
3886 simple_commits,
3887 SimpleTags(),
3888 SimpleTags(variant=1, trunk='a', branches='b', tags='c'),
3889 SimpleTags(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3890 SimpleTags(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3891 simple_branch_commits,
3892 mixed_time_tag,
3893 # 20:
3894 mixed_time_branch_with_added_file,
3895 mixed_commit,
3896 split_time_branch,
3897 bogus_tag,
3898 overlapping_branch,
3899 PhoenixBranch(),
3900 PhoenixBranch(variant=1, trunk='a/1', branches='b/1', tags='c/1'),
3901 ctrl_char_in_log,
3902 overdead,
3903 NoTrunkPrune(),
3904 # 30:
3905 NoTrunkPrune(variant=1, trunk='a', branches='b', tags='c'),
3906 NoTrunkPrune(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3907 NoTrunkPrune(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3908 double_delete,
3909 split_branch,
3910 resync_misgroups,
3911 TaggedBranchAndTrunk(),
3912 TaggedBranchAndTrunk(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3913 enroot_race,
3914 enroot_race_obo,
3915 # 40:
3916 BranchDeleteFirst(),
3917 BranchDeleteFirst(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3918 nonascii_filenames,
3919 UnicodeAuthor(
3920 warning_expected=1),
3921 UnicodeAuthor(
3922 warning_expected=0,
3923 variant='encoding', args=['--encoding=utf_8']),
3924 UnicodeAuthor(
3925 warning_expected=0,
3926 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
3927 UnicodeLog(
3928 warning_expected=1),
3929 UnicodeLog(
3930 warning_expected=0,
3931 variant='encoding', args=['--encoding=utf_8']),
3932 UnicodeLog(
3933 warning_expected=0,
3934 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
3935 vendor_branch_sameness,
3936 # 50:
3937 vendor_branch_trunk_only,
3938 default_branches,
3939 default_branches_trunk_only,
3940 default_branch_and_1_2,
3941 compose_tag_three_sources,
3942 pass5_when_to_fill,
3943 PeerPathPruning(),
3944 PeerPathPruning(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3945 EmptyTrunk(),
3946 EmptyTrunk(variant=1, trunk='a', branches='b', tags='c'),
3947 # 60:
3948 EmptyTrunk(variant=2, trunk='a/1', branches='a/2', tags='a/3'),
3949 no_spurious_svn_commits,
3950 invalid_closings_on_trunk,
3951 individual_passes,
3952 resync_bug,
3953 branch_from_default_branch,
3954 file_in_attic_too,
3955 retain_file_in_attic_too,
3956 symbolic_name_filling_guide,
3957 eol_mime1,
3958 # 70:
3959 eol_mime2,
3960 eol_mime3,
3961 eol_mime4,
3962 cvs_revnums_off,
3963 cvs_revnums_on,
3964 keywords,
3965 ignore,
3966 requires_cvs,
3967 questionable_branch_names,
3968 questionable_tag_names,
3969 # 80:
3970 revision_reorder_bug,
3971 exclude,
3972 vendor_branch_delete_add,
3973 resync_pass2_pull_forward,
3974 native_eol,
3975 double_fill,
3976 XFail(double_fill2),
3977 resync_pass2_push_backward,
3978 double_add,
3979 bogus_branch_copy,
3980 # 90:
3981 nested_ttb_directories,
3982 auto_props_ignore_case,
3983 ctrl_char_in_filename,
3984 commit_dependencies,
3985 show_help_passes,
3986 multiple_tags,
3987 multiply_defined_symbols,
3988 multiply_defined_symbols_renamed,
3989 multiply_defined_symbols_ignored,
3990 repeatedly_defined_symbols,
3991 # 100:
3992 double_branch_delete,
3993 symbol_mismatches,
3994 overlook_symbol_mismatches,
3995 force_symbols,
3996 commit_blocks_tags,
3997 blocked_excludes,
3998 unblock_blocked_excludes,
3999 regexp_force_symbols,
4000 heuristic_symbol_default,
4001 branch_symbol_default,
4002 # 110:
4003 tag_symbol_default,
4004 symbol_transform,
4005 write_symbol_info,
4006 symbol_hints,
4007 parent_hints,
4008 parent_hints_invalid,
4009 parent_hints_wildcards,
4010 path_hints,
4011 issue_99,
4012 issue_100,
4013 # 120:
4014 issue_106,
4015 options_option,
4016 multiproject,
4017 crossproject,
4018 tag_with_no_revision,
4019 delete_cvsignore,
4020 repeated_deltatext,
4021 nasty_graphs,
4022 XFail(tagging_after_delete),
4023 crossed_branches,
4024 # 130:
4025 file_directory_conflict,
4026 attic_directory_conflict,
4027 internal_co,
4028 internal_co_exclude,
4029 internal_co_trunk_only,
4030 internal_co_keywords,
4031 leftover_revs,
4032 requires_internal_co,
4033 timestamp_chaos,
4034 symlinks,
4035 # 140:
4036 empty_trunk_path,
4037 preferred_parent_cycle,
4038 branch_from_empty_dir,
4039 trunk_readd,
4040 branch_from_deleted_1_1,
4041 add_on_branch,
4042 main_git,
4043 main_git2,
4044 git_options,
4045 main_hg,
4046 # 150:
4047 invalid_symbol,
4048 invalid_symbol_ignore,
4049 invalid_symbol_ignore2,
4050 EOLVariants('LF'),
4051 EOLVariants('CR'),
4052 EOLVariants('CRLF'),
4053 EOLVariants('native'),
4054 no_revs_file,
4055 mirror_keyerror_test,
4056 exclude_ntdb_test,
4057 # 160:
4058 mirror_keyerror2_test,
4059 mirror_keyerror3_test,
4060 XFail(add_cvsignore_to_branch_test),
4061 missing_deltatext,
4062 transform_unlabeled_branch_name,
4063 ignore_unlabeled_branch,
4064 unlabeled_branch_name_collision,
4065 collision_with_unlabeled_branch_name,
4066 many_deletes,
4067 cvs_description,
4068 # 170:
4069 include_empty_directories,
4070 include_empty_directories_no_prune,
4071 exclude_symbol_default,
4072 add_on_branch2,
4075 if __name__ == '__main__':
4077 # Configure the environment for reproducable output from svn, etc.
4078 os.environ["LC_ALL"] = "C"
4080 # Unfortunately, there is no way under Windows to make Subversion
4081 # think that the local time zone is UTC, so we just work in the
4082 # local time zone.
4084 # The Subversion test suite code assumes it's being invoked from
4085 # within a working copy of the Subversion sources, and tries to use
4086 # the binaries in that tree. Since the cvs2svn tree never contains
4087 # a Subversion build, we just use the system's installed binaries.
4088 svntest.main.svn_binary = svn_binary
4089 svntest.main.svnlook_binary = svnlook_binary
4090 svntest.main.svnadmin_binary = svnadmin_binary
4091 svntest.main.svnversion_binary = svnversion_binary
4093 svntest.main.run_tests(test_list)
4094 # NOTREACHED
4097 ### End of file.