Change SVNBinaryFileKeywordsPropertySetter into a FilePropertySetter.
[cvs2svn.git] / run-tests.py
blobf7b6ceec3e14e3d6078be674605e78a501acc260
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 run_command
71 from svntest.main import run_tests
72 from svntest.main import safe_rmtree
73 from svntest.testcase import TestCase
74 from svntest.testcase import Skip
75 from svntest.testcase import XFail
76 from svntest.tree import build_tree_from_wc
77 from svntest.tree import get_child
79 # Test if Mercurial >= 1.1 is available.
80 try:
81 from mercurial import context
82 context.memctx
83 have_hg = True
84 except (ImportError, AttributeError):
85 have_hg = False
87 cvs2svn = os.path.abspath('cvs2svn')
88 cvs2git = os.path.abspath('cvs2git')
89 cvs2hg = os.path.abspath('cvs2hg')
91 # We use the installed svn and svnlook binaries, instead of using
92 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
93 # behavior -- or even existence -- of local builds shouldn't affect
94 # the cvs2svn test suite.
95 svn_binary = 'svn'
96 svnlook_binary = 'svnlook'
97 svnadmin_binary = 'svnadmin'
98 svnversion_binary = 'svnversion'
100 test_data_dir = 'test-data'
101 tmp_dir = 'cvs2svn-tmp'
104 #----------------------------------------------------------------------
105 # Helpers.
106 #----------------------------------------------------------------------
109 # The value to expect for svn:keywords if it is set:
110 KEYWORDS = 'Author Date Id Revision'
113 class RunProgramException(Failure):
114 pass
117 class MissingErrorException(Failure):
118 def __init__(self, error_re):
119 Failure.__init__(
120 self, "Test failed because no error matched '%s'" % (error_re,)
124 def run_program(program, error_re, *varargs):
125 """Run PROGRAM with VARARGS, return stdout as a list of lines.
127 If there is any stderr and ERROR_RE is None, raise
128 RunProgramException, and print the stderr lines if
129 svntest.main.verbose_mode is true.
131 If ERROR_RE is not None, it is a string regular expression that must
132 match some line of stderr. If it fails to match, raise
133 MissingErrorExpection."""
135 # FIXME: exit_code is currently ignored.
136 exit_code, out, err = run_command(program, 1, 0, *varargs)
138 if error_re:
139 # Specified error expected on stderr.
140 if not err:
141 raise MissingErrorException(error_re)
142 else:
143 for line in err:
144 if re.match(error_re, line):
145 return out
146 raise MissingErrorException(error_re)
147 else:
148 # No stderr allowed.
149 if err:
150 if svntest.main.verbose_mode:
151 print '\n%s said:\n' % program
152 for line in err:
153 print ' ' + line,
154 print
155 raise RunProgramException()
157 return out
160 def run_script(script, error_re, *varargs):
161 """Run Python script SCRIPT with VARARGS, returning stdout as a list
162 of lines.
164 If there is any stderr and ERROR_RE is None, raise
165 RunProgramException, and print the stderr lines if
166 svntest.main.verbose_mode is true.
168 If ERROR_RE is not None, it is a string regular expression that must
169 match some line of stderr. If it fails to match, raise
170 MissingErrorException."""
172 # Use the same python that is running this script
173 return run_program(sys.executable, error_re, script, *varargs)
174 # On Windows, for an unknown reason, the cmd.exe process invoked by
175 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
176 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
177 # this. Therefore, the redirection of the output to the .s-revs file fails.
178 # We no longer use the problematic invocation on any system, but this
179 # comment remains to warn about this problem.
182 def run_svn(*varargs):
183 """Run svn with VARARGS; return stdout as a list of lines.
184 If there is any stderr, raise RunProgramException, and print the
185 stderr lines if svntest.main.verbose_mode is true."""
186 return run_program(svn_binary, None, *varargs)
189 def repos_to_url(path_to_svn_repos):
190 """This does what you think it does."""
191 rpath = os.path.abspath(path_to_svn_repos)
192 if rpath[0] != '/':
193 rpath = '/' + rpath
194 return 'file://%s' % rpath.replace(os.sep, '/')
197 def svn_strptime(timestr):
198 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
201 class Log:
202 def __init__(self, revision, author, date, symbols):
203 self.revision = revision
204 self.author = author
206 # Internally, we represent the date as seconds since epoch (UTC).
207 # Since standard subversion log output shows dates in localtime
209 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
211 # and time.mktime() converts from localtime, it all works out very
212 # happily.
213 self.date = time.mktime(svn_strptime(date[0:19]))
215 # The following symbols are used for string interpolation when
216 # checking paths:
217 self.symbols = symbols
219 # The changed paths will be accumulated later, as log data is read.
220 # Keys here are paths such as '/trunk/foo/bar', values are letter
221 # codes such as 'M', 'A', and 'D'.
222 self.changed_paths = { }
224 # The msg will be accumulated later, as log data is read.
225 self.msg = ''
227 def absorb_changed_paths(self, out):
228 'Read changed paths from OUT into self, until no more.'
229 while 1:
230 line = out.readline()
231 if len(line) == 1: return
232 line = line[:-1]
233 op_portion = line[3:4]
234 path_portion = line[5:]
235 # If we're running on Windows we get backslashes instead of
236 # forward slashes.
237 path_portion = path_portion.replace('\\', '/')
238 # # We could parse out history information, but currently we
239 # # just leave it in the path portion because that's how some
240 # # tests expect it.
242 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
243 # if m:
244 # path_portion = m.group(1)
245 self.changed_paths[path_portion] = op_portion
247 def __cmp__(self, other):
248 return cmp(self.revision, other.revision) or \
249 cmp(self.author, other.author) or cmp(self.date, other.date) or \
250 cmp(self.changed_paths, other.changed_paths) or \
251 cmp(self.msg, other.msg)
253 def get_path_op(self, path):
254 """Return the operator for the change involving PATH.
256 PATH is allowed to include string interpolation directives (e.g.,
257 '%(trunk)s'), which are interpolated against self.symbols. Return
258 None if there is no record for PATH."""
259 return self.changed_paths.get(path % self.symbols)
261 def check_msg(self, msg):
262 """Verify that this Log's message starts with the specified MSG."""
263 if self.msg.find(msg) != 0:
264 raise Failure(
265 "Revision %d log message was:\n%s\n\n"
266 "It should have begun with:\n%s\n\n"
267 % (self.revision, self.msg, msg,)
270 def check_change(self, path, op):
271 """Verify that this Log includes a change for PATH with operator OP.
273 PATH is allowed to include string interpolation directives (e.g.,
274 '%(trunk)s'), which are interpolated against self.symbols."""
276 path = path % self.symbols
277 found_op = self.changed_paths.get(path, None)
278 if found_op is None:
279 raise Failure(
280 "Revision %d does not include change for path %s "
281 "(it should have been %s).\n"
282 % (self.revision, path, op,)
284 if found_op != op:
285 raise Failure(
286 "Revision %d path %s had op %s (it should have been %s)\n"
287 % (self.revision, path, found_op, op,)
290 def check_changes(self, changed_paths):
291 """Verify that this Log has precisely the CHANGED_PATHS specified.
293 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
294 strings are allowed to include string interpolation directives
295 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
297 cp = {}
298 for (path, op) in changed_paths:
299 cp[path % self.symbols] = op
301 if self.changed_paths != cp:
302 raise Failure(
303 "Revision %d changed paths list was:\n%s\n\n"
304 "It should have been:\n%s\n\n"
305 % (self.revision, self.changed_paths, cp,)
308 def check(self, msg, changed_paths):
309 """Verify that this Log has the MSG and CHANGED_PATHS specified.
311 Convenience function to check two things at once. MSG is passed
312 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
314 self.check_msg(msg)
315 self.check_changes(changed_paths)
318 def parse_log(svn_repos, symbols):
319 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
321 Initialize the Logs' symbols with SYMBOLS."""
323 class LineFeeder:
324 'Make a list of lines behave like an open file handle.'
325 def __init__(self, lines):
326 self.lines = lines
327 def readline(self):
328 if len(self.lines) > 0:
329 return self.lines.pop(0)
330 else:
331 return None
333 def absorb_message_body(out, num_lines, log):
334 """Read NUM_LINES of log message body from OUT into Log item LOG."""
336 for i in range(num_lines):
337 log.msg += out.readline()
339 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
340 '(?P<author>[^\|]+) \| '
341 '(?P<date>[^\|]+) '
342 '\| (?P<lines>[0-9]+) (line|lines)$')
344 log_separator = '-' * 72
346 logs = { }
348 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
350 while 1:
351 this_log = None
352 line = out.readline()
353 if not line: break
354 line = line[:-1]
356 if line.find(log_separator) == 0:
357 line = out.readline()
358 if not line: break
359 line = line[:-1]
360 m = log_start_re.match(line)
361 if m:
362 this_log = Log(
363 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
364 line = out.readline()
365 if not line.find('Changed paths:') == 0:
366 print 'unexpected log output (missing changed paths)'
367 print "Line: '%s'" % line
368 sys.exit(1)
369 this_log.absorb_changed_paths(out)
370 absorb_message_body(out, int(m.group('lines')), this_log)
371 logs[this_log.revision] = this_log
372 elif len(line) == 0:
373 break # We've reached the end of the log output.
374 else:
375 print 'unexpected log output (missing revision line)'
376 print "Line: '%s'" % line
377 sys.exit(1)
378 else:
379 print 'unexpected log output (missing log separator)'
380 print "Line: '%s'" % line
381 sys.exit(1)
383 return logs
386 def erase(path):
387 """Unconditionally remove PATH and its subtree, if any. PATH may be
388 non-existent, a file or symlink, or a directory."""
389 if os.path.isdir(path):
390 safe_rmtree(path)
391 elif os.path.exists(path):
392 os.remove(path)
395 log_msg_text_wrapper = textwrap.TextWrapper(width=76, break_long_words=False)
397 def sym_log_msg(symbolic_name, is_tag=None):
398 """Return the expected log message for a cvs2svn-synthesized revision
399 creating branch or tag SYMBOLIC_NAME."""
401 # This reproduces the logic in SVNSymbolCommit.get_log_msg().
402 if is_tag:
403 type = 'tag'
404 else:
405 type = 'branch'
407 return log_msg_text_wrapper.fill(
408 "This commit was manufactured by cvs2svn to create %s '%s'."
409 % (type, symbolic_name)
413 def make_conversion_id(
414 name, args, passbypass, options_file=None, symbol_hints_file=None
416 """Create an identifying tag for a conversion.
418 The return value can also be used as part of a filesystem path.
420 NAME is the name of the CVS repository.
422 ARGS are the extra arguments to be passed to cvs2svn.
424 PASSBYPASS is a boolean indicating whether the conversion is to be
425 run one pass at a time.
427 If OPTIONS_FILE is specified, it is an options file that will be
428 used for the conversion.
430 If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that
431 will be used for the conversion.
433 The 1-to-1 mapping between cvs2svn command parameters and
434 conversion_ids allows us to avoid running the same conversion more
435 than once, when multiple tests use exactly the same conversion."""
437 conv_id = name
439 args = args[:]
441 if passbypass:
442 args.append('--passbypass')
444 if symbol_hints_file is not None:
445 args.append('--symbol-hints=%s' % (symbol_hints_file,))
447 # There are some characters that are forbidden in filenames, and
448 # there is a limit on the total length of a path to a file. So use
449 # a hash of the parameters rather than concatenating the parameters
450 # into a string.
451 if args:
452 conv_id += "-" + md5('\0'.join(args)).hexdigest()
454 # Some options-file based tests rely on knowing the paths to which
455 # the repository should be written, so we handle that option as a
456 # predictable string:
457 if options_file is not None:
458 conv_id += '--options=%s' % (options_file,)
460 return conv_id
463 class Conversion:
464 """A record of a cvs2svn conversion.
466 Fields:
468 conv_id -- the conversion id for this Conversion.
470 name -- a one-word name indicating the involved repositories.
472 dumpfile -- the name of the SVN dumpfile created by the conversion
473 (if the DUMPFILE constructor argument was used); otherwise,
474 None.
476 repos -- the path to the svn repository. Unset if DUMPFILE was
477 specified.
479 logs -- a dictionary of Log instances, as returned by parse_log().
480 Unset if DUMPFILE was specified.
482 symbols -- a dictionary of symbols used for string interpolation
483 in path names.
485 stdout -- a list of lines written by cvs2svn to stdout
487 _wc -- the basename of the svn working copy (within tmp_dir).
488 Unset if DUMPFILE was specified.
490 _wc_path -- the path to the svn working copy, if it has already
491 been created; otherwise, None. (The working copy is created
492 lazily when get_wc() is called.) Unset if DUMPFILE was
493 specified.
495 _wc_tree -- the tree built from the svn working copy, if it has
496 already been created; otherwise, None. The tree is created
497 lazily when get_wc_tree() is called.) Unset if DUMPFILE was
498 specified.
500 _svnrepos -- the basename of the svn repository (within tmp_dir).
501 Unset if DUMPFILE was specified."""
503 # The number of the last cvs2svn pass (determined lazily by
504 # get_last_pass()).
505 last_pass = None
507 @classmethod
508 def get_last_pass(cls):
509 """Return the number of cvs2svn's last pass."""
511 if cls.last_pass is None:
512 out = run_script(cvs2svn, None, '--help-passes')
513 cls.last_pass = int(out[-1].split()[0])
514 return cls.last_pass
516 def __init__(
517 self, conv_id, name, error_re, passbypass, symbols, args,
518 options_file=None, symbol_hints_file=None, dumpfile=None,
520 self.conv_id = conv_id
521 self.name = name
522 self.symbols = symbols
523 if not os.path.isdir(tmp_dir):
524 os.mkdir(tmp_dir)
526 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
528 if dumpfile:
529 self.dumpfile = os.path.join(tmp_dir, dumpfile)
530 # Clean up from any previous invocations of this script.
531 erase(self.dumpfile)
532 else:
533 self.dumpfile = None
534 self.repos = os.path.join(tmp_dir, '%s-svnrepos' % self.conv_id)
535 self._wc = os.path.join(tmp_dir, '%s-wc' % self.conv_id)
536 self._wc_path = None
537 self._wc_tree = None
539 # Clean up from any previous invocations of this script.
540 erase(self.repos)
541 erase(self._wc)
543 args = list(args)
544 if options_file:
545 self.options_file = os.path.join(cvsrepos, options_file)
546 args.extend([
547 '--options=%s' % self.options_file,
549 assert not symbol_hints_file
550 else:
551 self.options_file = None
552 if tmp_dir != 'cvs2svn-tmp':
553 # Only include this argument if it differs from cvs2svn's default:
554 args.extend([
555 '--tmpdir=%s' % tmp_dir,
558 if symbol_hints_file:
559 self.symbol_hints_file = os.path.join(cvsrepos, symbol_hints_file)
560 args.extend([
561 '--symbol-hints=%s' % self.symbol_hints_file,
564 if self.dumpfile:
565 args.extend(['--dumpfile=%s' % (self.dumpfile,)])
566 else:
567 args.extend(['-s', self.repos])
568 args.extend([cvsrepos])
570 if passbypass:
571 self.stdout = []
572 for p in range(1, self.get_last_pass() + 1):
573 self.stdout += run_script(cvs2svn, error_re, '-p', str(p), *args)
574 else:
575 self.stdout = run_script(cvs2svn, error_re, *args)
577 if self.dumpfile:
578 if not os.path.isfile(self.dumpfile):
579 raise Failure(
580 "Dumpfile not created: '%s'"
581 % os.path.join(os.getcwd(), self.dumpfile)
583 else:
584 if os.path.isdir(self.repos):
585 self.logs = parse_log(self.repos, self.symbols)
586 elif error_re is None:
587 raise Failure(
588 "Repository not created: '%s'"
589 % os.path.join(os.getcwd(), self.repos)
592 def output_found(self, pattern):
593 """Return True if PATTERN matches any line in self.stdout.
595 PATTERN is a regular expression pattern as a string.
598 pattern_re = re.compile(pattern)
600 for line in self.stdout:
601 if pattern_re.match(line):
602 # We found the pattern that we were looking for.
603 return 1
604 else:
605 return 0
607 def find_tag_log(self, tagname):
608 """Search LOGS for a log message containing 'TAGNAME' and return the
609 log in which it was found."""
610 for i in xrange(len(self.logs), 0, -1):
611 if self.logs[i].msg.find("'"+tagname+"'") != -1:
612 return self.logs[i]
613 raise ValueError("Tag %s not found in logs" % tagname)
615 def get_wc(self, *args):
616 """Return the path to the svn working copy, or a path within the WC.
618 If a working copy has not been created yet, create it now.
620 If ARGS are specified, then they should be strings that form
621 fragments of a path within the WC. They are joined using
622 os.path.join() and appended to the WC path."""
624 if self._wc_path is None:
625 run_svn('co', repos_to_url(self.repos), self._wc)
626 self._wc_path = self._wc
627 return os.path.join(self._wc_path, *args)
629 def get_wc_tree(self):
630 if self._wc_tree is None:
631 self._wc_tree = build_tree_from_wc(self.get_wc(), 1)
632 return self._wc_tree
634 def path_exists(self, *args):
635 """Return True if the specified path exists within the repository.
637 (The strings in ARGS are first joined into a path using
638 os.path.join().)"""
640 return os.path.exists(self.get_wc(*args))
642 def check_props(self, keys, checks):
643 """Helper function for checking lots of properties. For a list of
644 files in the conversion, check that the values of the properties
645 listed in KEYS agree with those listed in CHECKS. CHECKS is a
646 list of tuples: [ (filename, [value, value, ...]), ...], where the
647 values are listed in the same order as the key names are listed in
648 KEYS."""
650 for (file, values) in checks:
651 assert len(values) == len(keys)
652 props = props_for_path(self.get_wc_tree(), file)
653 for i in range(len(keys)):
654 if props.get(keys[i]) != values[i]:
655 raise Failure(
656 "File %s has property %s set to \"%s\" "
657 "(it should have been \"%s\").\n"
658 % (file, keys[i], props.get(keys[i]), values[i],)
662 class GitConversion:
663 """A record of a cvs2svn conversion.
665 Fields:
667 name -- a one-word name indicating the CVS repository to be converted.
669 stdout -- a list of lines written by cvs2svn to stdout."""
671 def __init__(self, name, error_re, args, options_file=None):
672 self.name = name
673 if not os.path.isdir(tmp_dir):
674 os.mkdir(tmp_dir)
676 cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name)
678 args = list(args)
679 if options_file:
680 self.options_file = os.path.join(cvsrepos, options_file)
681 args.extend([
682 '--options=%s' % self.options_file,
684 else:
685 self.options_file = None
687 self.stdout = run_script(cvs2git, error_re, *args)
690 # Cache of conversions that have already been done. Keys are conv_id;
691 # values are Conversion instances.
692 already_converted = { }
694 def ensure_conversion(
695 name, error_re=None, passbypass=None,
696 trunk=None, branches=None, tags=None,
697 args=None, options_file=None, symbol_hints_file=None, dumpfile=None,
699 """Convert CVS repository NAME to Subversion, but only if it has not
700 been converted before by this invocation of this script. If it has
701 been converted before, return the Conversion object from the
702 previous invocation.
704 If no error, return a Conversion instance.
706 If ERROR_RE is a string, it is a regular expression expected to
707 match some line of stderr printed by the conversion. If there is an
708 error and ERROR_RE is not set, then raise Failure.
710 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
711 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
713 NAME is just one word. For example, 'main' would mean to convert
714 './test-data/main-cvsrepos', and after the conversion, the resulting
715 Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and
716 a checked out head working copy in './cvs2svn-tmp/main-wc'.
718 Any other options to pass to cvs2svn should be in ARGS, each element
719 being one option, e.g., '--trunk-only'. If the option takes an
720 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
721 are passed to cvs2svn in the order that they appear in ARGS.
723 If OPTIONS_FILE is specified, then it should be the name of a file
724 within the main directory of the cvs repository associated with this
725 test. It is passed to cvs2svn using the --options option (which
726 suppresses some other options that are incompatible with --options).
728 If SYMBOL_HINTS_FILE is specified, then it should be the name of a
729 file within the main directory of the cvs repository associated with
730 this test. It is passed to cvs2svn using the --symbol-hints option.
732 If DUMPFILE is specified, then it is the name of a dumpfile within
733 the temporary directory to which the conversion output should be
734 written."""
736 if args is None:
737 args = []
738 else:
739 args = list(args)
741 if trunk is None:
742 trunk = 'trunk'
743 else:
744 args.append('--trunk=%s' % (trunk,))
746 if branches is None:
747 branches = 'branches'
748 else:
749 args.append('--branches=%s' % (branches,))
751 if tags is None:
752 tags = 'tags'
753 else:
754 args.append('--tags=%s' % (tags,))
756 conv_id = make_conversion_id(
757 name, args, passbypass, options_file, symbol_hints_file
760 if conv_id not in already_converted:
761 try:
762 # Run the conversion and store the result for the rest of this
763 # session:
764 already_converted[conv_id] = Conversion(
765 conv_id, name, error_re, passbypass,
766 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
767 args, options_file, symbol_hints_file, dumpfile,
769 except Failure:
770 # Remember the failure so that a future attempt to run this conversion
771 # does not bother to retry, but fails immediately.
772 already_converted[conv_id] = None
773 raise
775 conv = already_converted[conv_id]
776 if conv is None:
777 raise Failure()
778 return conv
781 class Cvs2SvnTestFunction(TestCase):
782 """A TestCase based on a naked Python function object.
784 FUNC should be a function that returns None on success and throws an
785 svntest.Failure exception on failure. It should have a brief
786 docstring describing what it does (and fulfilling certain
787 conditions). FUNC must take no arguments.
789 This class is almost identical to svntest.testcase.FunctionTestCase,
790 except that the test function does not require a sandbox and does
791 not accept any parameter (not even sandbox=None).
793 This class can be used as an annotation on a Python function.
797 def __init__(self, func):
798 # it better be a function that accepts no parameters and has a
799 # docstring on it.
800 assert isinstance(func, types.FunctionType)
802 name = func.func_name
804 assert func.func_code.co_argcount == 0, \
805 '%s must not take any arguments' % name
807 doc = func.__doc__.strip()
808 assert doc, '%s must have a docstring' % name
810 # enforce stylistic guidelines for the function docstrings:
811 # - no longer than 50 characters
812 # - should not end in a period
813 # - should not be capitalized
814 assert len(doc) <= 50, \
815 "%s's docstring must be 50 characters or less" % name
816 assert doc[-1] != '.', \
817 "%s's docstring should not end in a period" % name
818 assert doc[0].lower() == doc[0], \
819 "%s's docstring should not be capitalized" % name
821 TestCase.__init__(self, doc=doc)
822 self.func = func
824 def get_function_name(self):
825 return self.func.func_name
827 def get_sandbox_name(self):
828 return None
830 def run(self, sandbox):
831 return self.func()
834 class Cvs2HgTestFunction(Cvs2SvnTestFunction):
835 """Same as Cvs2SvnTestFunction, but for test cases that should be
836 skipped if Mercurial is not available.
838 def run(self, sandbox):
839 if not have_hg:
840 raise svntest.Skip()
841 else:
842 return self.func()
845 class Cvs2SvnTestCase(TestCase):
846 def __init__(
847 self, name, doc=None, variant=None,
848 error_re=None, passbypass=None,
849 trunk=None, branches=None, tags=None,
850 args=None,
851 options_file=None, symbol_hints_file=None, dumpfile=None,
853 self.name = name
855 if doc is None:
856 # By default, use the first line of the class docstring as the
857 # doc:
858 doc = self.__doc__.splitlines()[0]
860 if variant is not None:
861 # Modify doc to show the variant. Trim doc first if necessary
862 # to stay within the 50-character limit.
863 suffix = '...variant %s' % (variant,)
864 doc = doc[:50 - len(suffix)] + suffix
866 TestCase.__init__(self, doc=doc)
868 self.error_re = error_re
869 self.passbypass = passbypass
870 self.trunk = trunk
871 self.branches = branches
872 self.tags = tags
873 self.args = args
874 self.options_file = options_file
875 self.symbol_hints_file = symbol_hints_file
876 self.dumpfile = dumpfile
878 def ensure_conversion(self):
879 return ensure_conversion(
880 self.name,
881 error_re=self.error_re, passbypass=self.passbypass,
882 trunk=self.trunk, branches=self.branches, tags=self.tags,
883 args=self.args,
884 options_file=self.options_file,
885 symbol_hints_file=self.symbol_hints_file,
886 dumpfile=self.dumpfile,
889 def get_sandbox_name(self):
890 return None
893 class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase):
894 """Test properties resulting from a conversion."""
896 def __init__(self, name, props_to_test, expected_props, **kw):
897 """Initialize an instance of Cvs2SvnPropertiesTestCase.
899 NAME is the name of the test, passed to Cvs2SvnTestCase.
900 PROPS_TO_TEST is a list of the names of svn properties that should
901 be tested. EXPECTED_PROPS is a list of tuples [(filename,
902 [value,...])], where the second item in each tuple is a list of
903 values expected for the properties listed in PROPS_TO_TEST for the
904 specified filename. If a property must *not* be set, then its
905 value should be listed as None."""
907 Cvs2SvnTestCase.__init__(self, name, **kw)
908 self.props_to_test = props_to_test
909 self.expected_props = expected_props
911 def run(self, sbox):
912 conv = self.ensure_conversion()
913 conv.check_props(self.props_to_test, self.expected_props)
916 #----------------------------------------------------------------------
917 # Tests.
918 #----------------------------------------------------------------------
921 @Cvs2SvnTestFunction
922 def show_usage():
923 "cvs2svn with no arguments shows usage"
924 out = run_script(cvs2svn, None)
925 if (len(out) > 2 and out[0].find('ERROR:') == 0
926 and out[1].find('DBM module')):
927 print 'cvs2svn cannot execute due to lack of proper DBM module.'
928 print 'Exiting without running any further tests.'
929 sys.exit(1)
930 if out[0].find('Usage:') < 0:
931 raise Failure('Basic cvs2svn invocation failed.')
934 @Cvs2SvnTestFunction
935 def cvs2svn_manpage():
936 "generate a manpage for cvs2svn"
937 out = run_script(cvs2svn, None, '--man')
940 @Cvs2SvnTestFunction
941 def cvs2git_manpage():
942 "generate a manpage for cvs2git"
943 out = run_script(cvs2git, None, '--man')
946 @Cvs2HgTestFunction
947 def cvs2hg_manpage():
948 "generate a manpage for cvs2hg"
949 out = run_script(cvs2hg, None, '--man')
952 @Cvs2SvnTestFunction
953 def show_help_passes():
954 "cvs2svn --help-passes shows pass information"
955 out = run_script(cvs2svn, None, '--help-passes')
956 if out[0].find('PASSES') < 0:
957 raise Failure('cvs2svn --help-passes failed.')
960 @Cvs2SvnTestFunction
961 def attr_exec():
962 "detection of the executable flag"
963 if sys.platform == 'win32':
964 raise svntest.Skip()
965 conv = ensure_conversion('main')
966 st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec'))
967 if not st.st_mode & stat.S_IXUSR:
968 raise Failure()
971 @Cvs2SvnTestFunction
972 def space_fname():
973 "conversion of filename with a space"
974 conv = ensure_conversion('main')
975 if not conv.path_exists('trunk', 'single-files', 'space fname'):
976 raise Failure()
979 @Cvs2SvnTestFunction
980 def two_quick():
981 "two commits in quick succession"
982 conv = ensure_conversion('main')
983 logs = parse_log(
984 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
985 if len(logs) != 2:
986 raise Failure()
989 class PruneWithCare(Cvs2SvnTestCase):
990 "prune, but never too much"
992 def __init__(self, **kw):
993 Cvs2SvnTestCase.__init__(self, 'main', **kw)
995 def run(self, sbox):
996 # Robert Pluim encountered this lovely one while converting the
997 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
998 # repository (see issue #1302). Step 4 is the doozy:
1000 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
1001 # revision 2: adds trunk/blah/NEWS
1002 # revision 3: deletes trunk/blah/cookie
1003 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
1004 # revision 5: does nothing
1006 # After fixing cvs2svn, the sequence (correctly) looks like this:
1008 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
1009 # revision 2: adds trunk/blah/NEWS
1010 # revision 3: deletes trunk/blah/cookie
1011 # revision 4: does nothing [because trunk/blah/cookie already deleted]
1012 # revision 5: deletes blah
1014 # The difference is in 4 and 5. In revision 4, it's not correct to
1015 # prune blah/, because NEWS is still in there, so revision 4 does
1016 # nothing now. But when we delete NEWS in 5, that should bubble up
1017 # and prune blah/ instead.
1019 # ### Note that empty revisions like 4 are probably going to become
1020 # ### at least optional, if not banished entirely from cvs2svn's
1021 # ### output. Hmmm, or they may stick around, with an extra
1022 # ### revision property explaining what happened. Need to think
1023 # ### about that. In some sense, it's a bug in Subversion itself,
1024 # ### that such revisions don't show up in 'svn log' output.
1026 # In the test below, 'trunk/full-prune/first' represents
1027 # cookie, and 'trunk/full-prune/second' represents NEWS.
1029 conv = self.ensure_conversion()
1031 # Confirm that revision 4 removes '/trunk/full-prune/first',
1032 # and that revision 6 removes '/trunk/full-prune'.
1034 # Also confirm similar things about '/full-prune-reappear/...',
1035 # which is similar, except that later on it reappears, restored
1036 # from pruneland, because a file gets added to it.
1038 # And finally, a similar thing for '/partial-prune/...', except that
1039 # in its case, a permanent file on the top level prevents the
1040 # pruning from going farther than the subdirectory containing first
1041 # and second.
1043 for path in ('full-prune/first',
1044 'full-prune-reappear/sub/first',
1045 'partial-prune/sub/first'):
1046 conv.logs[5].check_change('/%(trunk)s/' + path, 'D')
1048 for path in ('full-prune',
1049 'full-prune-reappear',
1050 'partial-prune/sub'):
1051 conv.logs[7].check_change('/%(trunk)s/' + path, 'D')
1053 for path in ('full-prune-reappear',
1054 'full-prune-reappear/appears-later'):
1055 conv.logs[33].check_change('/%(trunk)s/' + path, 'A')
1058 @Cvs2SvnTestFunction
1059 def interleaved_commits():
1060 "two interleaved trunk commits, different log msgs"
1061 # See test-data/main-cvsrepos/proj/README.
1062 conv = ensure_conversion('main')
1064 # The initial import.
1065 rev = 26
1066 conv.logs[rev].check('Initial import.', (
1067 ('/%(trunk)s/interleaved', 'A'),
1068 ('/%(trunk)s/interleaved/1', 'A'),
1069 ('/%(trunk)s/interleaved/2', 'A'),
1070 ('/%(trunk)s/interleaved/3', 'A'),
1071 ('/%(trunk)s/interleaved/4', 'A'),
1072 ('/%(trunk)s/interleaved/5', 'A'),
1073 ('/%(trunk)s/interleaved/a', 'A'),
1074 ('/%(trunk)s/interleaved/b', 'A'),
1075 ('/%(trunk)s/interleaved/c', 'A'),
1076 ('/%(trunk)s/interleaved/d', 'A'),
1077 ('/%(trunk)s/interleaved/e', 'A'),
1080 def check_letters(rev):
1081 """Check if REV is the rev where only letters were committed."""
1083 conv.logs[rev].check('Committing letters only.', (
1084 ('/%(trunk)s/interleaved/a', 'M'),
1085 ('/%(trunk)s/interleaved/b', 'M'),
1086 ('/%(trunk)s/interleaved/c', 'M'),
1087 ('/%(trunk)s/interleaved/d', 'M'),
1088 ('/%(trunk)s/interleaved/e', 'M'),
1091 def check_numbers(rev):
1092 """Check if REV is the rev where only numbers were committed."""
1094 conv.logs[rev].check('Committing numbers only.', (
1095 ('/%(trunk)s/interleaved/1', 'M'),
1096 ('/%(trunk)s/interleaved/2', 'M'),
1097 ('/%(trunk)s/interleaved/3', 'M'),
1098 ('/%(trunk)s/interleaved/4', 'M'),
1099 ('/%(trunk)s/interleaved/5', 'M'),
1102 # One of the commits was letters only, the other was numbers only.
1103 # But they happened "simultaneously", so we don't assume anything
1104 # about which commit appeared first, so we just try both ways.
1105 rev += 1
1106 try:
1107 check_letters(rev)
1108 check_numbers(rev + 1)
1109 except Failure:
1110 check_numbers(rev)
1111 check_letters(rev + 1)
1114 @Cvs2SvnTestFunction
1115 def simple_commits():
1116 "simple trunk commits"
1117 # See test-data/main-cvsrepos/proj/README.
1118 conv = ensure_conversion('main')
1120 # The initial import.
1121 conv.logs[13].check('Initial import.', (
1122 ('/%(trunk)s/proj', 'A'),
1123 ('/%(trunk)s/proj/default', 'A'),
1124 ('/%(trunk)s/proj/sub1', 'A'),
1125 ('/%(trunk)s/proj/sub1/default', 'A'),
1126 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1127 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1128 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1129 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1130 ('/%(trunk)s/proj/sub2', 'A'),
1131 ('/%(trunk)s/proj/sub2/default', 'A'),
1132 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1133 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1134 ('/%(trunk)s/proj/sub3', 'A'),
1135 ('/%(trunk)s/proj/sub3/default', 'A'),
1138 # The first commit.
1139 conv.logs[18].check('First commit to proj, affecting two files.', (
1140 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1141 ('/%(trunk)s/proj/sub3/default', 'M'),
1144 # The second commit.
1145 conv.logs[19].check('Second commit to proj, affecting all 7 files.', (
1146 ('/%(trunk)s/proj/default', 'M'),
1147 ('/%(trunk)s/proj/sub1/default', 'M'),
1148 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
1149 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1150 ('/%(trunk)s/proj/sub2/default', 'M'),
1151 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
1152 ('/%(trunk)s/proj/sub3/default', 'M')
1156 class SimpleTags(Cvs2SvnTestCase):
1157 "simple tags and branches, no commits"
1159 def __init__(self, **kw):
1160 # See test-data/main-cvsrepos/proj/README.
1161 Cvs2SvnTestCase.__init__(self, 'main', **kw)
1163 def run(self, sbox):
1164 conv = self.ensure_conversion()
1166 # Verify the copy source for the tags we are about to check
1167 # No need to verify the copyfrom revision, as simple_commits did that
1168 conv.logs[13].check('Initial import.', (
1169 ('/%(trunk)s/proj', 'A'),
1170 ('/%(trunk)s/proj/default', 'A'),
1171 ('/%(trunk)s/proj/sub1', 'A'),
1172 ('/%(trunk)s/proj/sub1/default', 'A'),
1173 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
1174 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
1175 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
1176 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
1177 ('/%(trunk)s/proj/sub2', 'A'),
1178 ('/%(trunk)s/proj/sub2/default', 'A'),
1179 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
1180 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
1181 ('/%(trunk)s/proj/sub3', 'A'),
1182 ('/%(trunk)s/proj/sub3/default', 'A'),
1185 fromstr = ' (from /%(branches)s/B_FROM_INITIALS:14)'
1187 # Tag on rev 1.1.1.1 of all files in proj
1188 conv.logs[14].check(sym_log_msg('B_FROM_INITIALS'), (
1189 ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'),
1190 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
1191 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
1194 # The same, as a tag
1195 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
1196 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
1197 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr, 'A'),
1200 # Tag on rev 1.1.1.1 of all files in proj, except one
1201 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
1202 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
1203 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr, 'A'),
1204 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
1207 # The same, as a branch
1208 conv.logs[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
1209 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr, 'A'),
1210 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
1214 @Cvs2SvnTestFunction
1215 def simple_branch_commits():
1216 "simple branch commits"
1217 # See test-data/main-cvsrepos/proj/README.
1218 conv = ensure_conversion('main')
1220 conv.logs[23].check('Modify three files, on branch B_MIXED.', (
1221 ('/%(branches)s/B_MIXED/proj/default', 'M'),
1222 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
1223 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
1227 @Cvs2SvnTestFunction
1228 def mixed_time_tag():
1229 "mixed-time tag"
1230 # See test-data/main-cvsrepos/proj/README.
1231 conv = ensure_conversion('main')
1233 log = conv.find_tag_log('T_MIXED')
1234 log.check_changes((
1235 ('/%(tags)s/T_MIXED (from /%(branches)s/B_MIXED:20)', 'A'),
1239 @Cvs2SvnTestFunction
1240 def mixed_time_branch_with_added_file():
1241 "mixed-time branch, and a file added to the branch"
1242 # See test-data/main-cvsrepos/proj/README.
1243 conv = ensure_conversion('main')
1245 # A branch from the same place as T_MIXED in the previous test,
1246 # plus a file added directly to the branch
1247 conv.logs[20].check(sym_log_msg('B_MIXED'), (
1248 ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'),
1249 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
1250 ('/%(branches)s/B_MIXED/single-files', 'D'),
1251 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
1252 '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'),
1253 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'),
1256 conv.logs[22].check('Add a file on branch B_MIXED.', (
1257 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
1261 @Cvs2SvnTestFunction
1262 def mixed_commit():
1263 "a commit affecting both trunk and a branch"
1264 # See test-data/main-cvsrepos/proj/README.
1265 conv = ensure_conversion('main')
1267 conv.logs[24].check(
1268 'A single commit affecting one file on branch B_MIXED '
1269 'and one on trunk.', (
1270 ('/%(trunk)s/proj/sub2/default', 'M'),
1271 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
1275 @Cvs2SvnTestFunction
1276 def split_time_branch():
1277 "branch some trunk files, and later branch the rest"
1278 # See test-data/main-cvsrepos/proj/README.
1279 conv = ensure_conversion('main')
1281 # First change on the branch, creating it
1282 conv.logs[25].check(sym_log_msg('B_SPLIT'), (
1283 ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'),
1284 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
1285 ('/%(branches)s/B_SPLIT/single-files', 'D'),
1286 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
1289 conv.logs[29].check('First change on branch B_SPLIT.', (
1290 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
1291 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
1292 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
1293 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
1294 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
1297 # A trunk commit for the file which was not branched
1298 conv.logs[30].check('A trunk change to sub1/subsubB/default. '
1299 'This was committed about an', (
1300 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
1303 # Add the file not already branched to the branch, with modification:w
1304 conv.logs[31].check(sym_log_msg('B_SPLIT'), (
1305 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
1306 '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'),
1309 conv.logs[32].check('This change affects sub3/default and '
1310 'sub1/subsubB/default, on branch', (
1311 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
1312 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1316 @Cvs2SvnTestFunction
1317 def multiple_tags():
1318 "multiple tags referring to same revision"
1319 conv = ensure_conversion('main')
1320 if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1321 raise Failure()
1322 if not conv.path_exists(
1323 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1324 raise Failure()
1327 @Cvs2SvnTestFunction
1328 def multiply_defined_symbols():
1329 "multiple definitions of symbol names"
1331 # We can only check one line of the error output at a time, so test
1332 # twice. (The conversion only have to be done once because the
1333 # results are cached.)
1334 conv = ensure_conversion(
1335 'multiply-defined-symbols',
1336 error_re=(
1337 r"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: "
1338 r"1\.2\.4 1\.2\.2"
1341 conv = ensure_conversion(
1342 'multiply-defined-symbols',
1343 error_re=(
1344 r"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: "
1345 r"1\.2 1\.1"
1350 @Cvs2SvnTestFunction
1351 def multiply_defined_symbols_renamed():
1352 "rename multiply defined symbols"
1354 conv = ensure_conversion(
1355 'multiply-defined-symbols',
1356 options_file='cvs2svn-rename.options',
1360 @Cvs2SvnTestFunction
1361 def multiply_defined_symbols_ignored():
1362 "ignore multiply defined symbols"
1364 conv = ensure_conversion(
1365 'multiply-defined-symbols',
1366 options_file='cvs2svn-ignore.options',
1370 @Cvs2SvnTestFunction
1371 def repeatedly_defined_symbols():
1372 "multiple identical definitions of symbol names"
1374 # If a symbol is defined multiple times but has the same value each
1375 # time, that should not be an error.
1377 conv = ensure_conversion('repeatedly-defined-symbols')
1380 @Cvs2SvnTestFunction
1381 def bogus_tag():
1382 "conversion of invalid symbolic names"
1383 conv = ensure_conversion('bogus-tag')
1386 @Cvs2SvnTestFunction
1387 def overlapping_branch():
1388 "ignore a file with a branch with two names"
1389 conv = ensure_conversion('overlapping-branch')
1391 if not conv.output_found('.*cannot also have name \'vendorB\''):
1392 raise Failure()
1394 conv.logs[2].check('imported', (
1395 ('/%(trunk)s/nonoverlapping-branch', 'A'),
1396 ('/%(trunk)s/overlapping-branch', 'A'),
1399 if len(conv.logs) != 2:
1400 raise Failure()
1403 class PhoenixBranch(Cvs2SvnTestCase):
1404 "convert a branch file rooted in a 'dead' revision"
1406 def __init__(self, **kw):
1407 Cvs2SvnTestCase.__init__(self, 'phoenix', **kw)
1409 def run(self, sbox):
1410 conv = self.ensure_conversion()
1411 conv.logs[8].check('This file was supplied by Jack Moffitt', (
1412 ('/%(branches)s/volsung_20010721', 'A'),
1413 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1415 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1416 ('/%(branches)s/volsung_20010721/phoenix', 'M'),
1420 ###TODO: We check for 4 changed paths here to accomodate creating tags
1421 ###and branches in rev 1, but that will change, so this will
1422 ###eventually change back.
1423 @Cvs2SvnTestFunction
1424 def ctrl_char_in_log():
1425 "handle a control char in a log message"
1426 # This was issue #1106.
1427 rev = 2
1428 conv = ensure_conversion('ctrl-char-in-log')
1429 conv.logs[rev].check_changes((
1430 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1432 if conv.logs[rev].msg.find('\x04') < 0:
1433 raise Failure(
1434 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1437 @Cvs2SvnTestFunction
1438 def overdead():
1439 "handle tags rooted in a redeleted revision"
1440 conv = ensure_conversion('overdead')
1443 class NoTrunkPrune(Cvs2SvnTestCase):
1444 "ensure that trunk doesn't get pruned"
1446 def __init__(self, **kw):
1447 Cvs2SvnTestCase.__init__(self, 'overdead', **kw)
1449 def run(self, sbox):
1450 conv = self.ensure_conversion()
1451 for rev in conv.logs.keys():
1452 rev_logs = conv.logs[rev]
1453 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1454 raise Failure()
1457 @Cvs2SvnTestFunction
1458 def double_delete():
1459 "file deleted twice, in the root of the repository"
1460 # This really tests several things: how we handle a file that's
1461 # removed (state 'dead') in two successive revisions; how we
1462 # handle a file in the root of the repository (there were some
1463 # bugs in cvs2svn's svn path construction for top-level files); and
1464 # the --no-prune option.
1465 conv = ensure_conversion(
1466 'double-delete', args=['--trunk-only', '--no-prune'])
1468 path = '/%(trunk)s/twice-removed'
1469 rev = 2
1470 conv.logs[rev].check('Updated CVS', (
1471 (path, 'A'),
1473 conv.logs[rev + 1].check('Remove this file for the first time.', (
1474 (path, 'D'),
1476 conv.logs[rev + 2].check('Remove this file for the second time,', (
1480 @Cvs2SvnTestFunction
1481 def split_branch():
1482 "branch created from both trunk and another branch"
1483 # See test-data/split-branch-cvsrepos/README.
1485 # The conversion will fail if the bug is present, and
1486 # ensure_conversion will raise Failure.
1487 conv = ensure_conversion('split-branch')
1490 @Cvs2SvnTestFunction
1491 def resync_misgroups():
1492 "resyncing should not misorder commit groups"
1493 # See test-data/resync-misgroups-cvsrepos/README.
1495 # The conversion will fail if the bug is present, and
1496 # ensure_conversion will raise Failure.
1497 conv = ensure_conversion('resync-misgroups')
1500 class TaggedBranchAndTrunk(Cvs2SvnTestCase):
1501 "allow tags with mixed trunk and branch sources"
1503 def __init__(self, **kw):
1504 Cvs2SvnTestCase.__init__(self, 'tagged-branch-n-trunk', **kw)
1506 def run(self, sbox):
1507 conv = self.ensure_conversion()
1509 tags = conv.symbols.get('tags', 'tags')
1511 a_path = conv.get_wc(tags, 'some-tag', 'a.txt')
1512 b_path = conv.get_wc(tags, 'some-tag', 'b.txt')
1513 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1514 raise Failure()
1515 if (open(a_path, 'r').read().find('1.24') == -1) \
1516 or (open(b_path, 'r').read().find('1.5') == -1):
1517 raise Failure()
1520 @Cvs2SvnTestFunction
1521 def enroot_race():
1522 "never use the rev-in-progress as a copy source"
1524 # See issue #1427 and r8544.
1525 conv = ensure_conversion('enroot-race')
1526 rev = 6
1527 conv.logs[rev].check_changes((
1528 ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'),
1529 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1530 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1532 conv.logs[rev + 1].check_changes((
1533 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1534 ('/%(trunk)s/proj/a.txt', 'M'),
1535 ('/%(trunk)s/proj/b.txt', 'M'),
1539 @Cvs2SvnTestFunction
1540 def enroot_race_obo():
1541 "do use the last completed rev as a copy source"
1542 conv = ensure_conversion('enroot-race-obo')
1543 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1544 if not len(conv.logs) == 3:
1545 raise Failure()
1548 class BranchDeleteFirst(Cvs2SvnTestCase):
1549 "correctly handle deletion as initial branch action"
1551 def __init__(self, **kw):
1552 Cvs2SvnTestCase.__init__(self, 'branch-delete-first', **kw)
1554 def run(self, sbox):
1555 # See test-data/branch-delete-first-cvsrepos/README.
1557 # The conversion will fail if the bug is present, and
1558 # ensure_conversion would raise Failure.
1559 conv = self.ensure_conversion()
1561 branches = conv.symbols.get('branches', 'branches')
1563 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1564 if conv.path_exists(branches, 'branch-1', 'file'):
1565 raise Failure()
1566 if conv.path_exists(branches, 'branch-2', 'file'):
1567 raise Failure()
1568 if not conv.path_exists(branches, 'branch-3', 'file'):
1569 raise Failure()
1572 @Cvs2SvnTestFunction
1573 def nonascii_filenames():
1574 "non ascii files converted incorrectly"
1575 # see issue #1255
1577 # on a en_US.iso-8859-1 machine this test fails with
1578 # svn: Can't recode ...
1580 # as described in the issue
1582 # on a en_US.UTF-8 machine this test fails with
1583 # svn: Malformed XML ...
1585 # which means at least it fails. Unfortunately it won't fail
1586 # with the same error...
1588 # mangle current locale settings so we know we're not running
1589 # a UTF-8 locale (which does not exhibit this problem)
1590 current_locale = locale.getlocale()
1591 new_locale = 'en_US.ISO8859-1'
1592 locale_changed = None
1594 # From http://docs.python.org/lib/module-sys.html
1596 # getfilesystemencoding():
1598 # Return the name of the encoding used to convert Unicode filenames
1599 # into system file names, or None if the system default encoding is
1600 # used. The result value depends on the operating system:
1602 # - On Windows 9x, the encoding is ``mbcs''.
1603 # - On Mac OS X, the encoding is ``utf-8''.
1604 # - On Unix, the encoding is the user's preference according to the
1605 # result of nl_langinfo(CODESET), or None if the
1606 # nl_langinfo(CODESET) failed.
1607 # - On Windows NT+, file names are Unicode natively, so no conversion is
1608 # performed.
1610 # So we're going to skip this test on Mac OS X for now.
1611 if sys.platform == "darwin":
1612 raise svntest.Skip()
1614 try:
1615 # change locale to non-UTF-8 locale to generate latin1 names
1616 locale.setlocale(locale.LC_ALL, # this might be too broad?
1617 new_locale)
1618 locale_changed = 1
1619 except locale.Error:
1620 raise svntest.Skip()
1622 try:
1623 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
1624 dstrepos_path = os.path.join(test_data_dir,'non-ascii-cvsrepos')
1625 if not os.path.exists(dstrepos_path):
1626 # create repos from existing main repos
1627 shutil.copytree(srcrepos_path, dstrepos_path)
1628 base_path = os.path.join(dstrepos_path, 'single-files')
1629 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1630 os.path.join(base_path, 'two\366uick,v'))
1631 new_path = os.path.join(dstrepos_path, 'single\366files')
1632 os.rename(base_path, new_path)
1634 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1635 finally:
1636 if locale_changed:
1637 locale.setlocale(locale.LC_ALL, current_locale)
1638 safe_rmtree(dstrepos_path)
1641 class UnicodeTest(Cvs2SvnTestCase):
1642 "metadata contains Unicode"
1644 warning_pattern = r'ERROR\: There were warnings converting .* messages'
1646 def __init__(self, name, warning_expected, **kw):
1647 if warning_expected:
1648 error_re = self.warning_pattern
1649 else:
1650 error_re = None
1652 Cvs2SvnTestCase.__init__(self, name, error_re=error_re, **kw)
1653 self.warning_expected = warning_expected
1655 def run(self, sbox):
1656 try:
1657 # ensure the availability of the "utf_8" encoding:
1658 u'a'.encode('utf_8').decode('utf_8')
1659 except LookupError:
1660 raise svntest.Skip()
1662 self.ensure_conversion()
1665 class UnicodeAuthor(UnicodeTest):
1666 "author name contains Unicode"
1668 def __init__(self, warning_expected, **kw):
1669 UnicodeTest.__init__(self, 'unicode-author', warning_expected, **kw)
1672 class UnicodeLog(UnicodeTest):
1673 "log message contains Unicode"
1675 def __init__(self, warning_expected, **kw):
1676 UnicodeTest.__init__(self, 'unicode-log', warning_expected, **kw)
1679 @Cvs2SvnTestFunction
1680 def vendor_branch_sameness():
1681 "avoid spurious changes for initial revs"
1682 conv = ensure_conversion(
1683 'vendor-branch-sameness', args=['--keep-trivial-imports']
1686 # The following files are in this repository:
1688 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1689 # the same contents, the file's default branch is 1.1.1,
1690 # and both revisions are in state 'Exp'.
1692 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1693 # 1.1 (the addition of a line of text).
1695 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1697 # d.txt: This file was created by 'cvs add' instead of import, so
1698 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1699 # The timestamp on the add is exactly the same as for the
1700 # imports of the other files.
1702 # e.txt: Like a.txt, except that the log message for revision 1.1
1703 # is not the standard import log message.
1705 # (Aside from e.txt, the log messages for the same revisions are the
1706 # same in all files.)
1708 # We expect that only a.txt is recognized as an import whose 1.1
1709 # revision can be omitted. The other files should be added on trunk
1710 # then filled to vbranchA, whereas a.txt should be added to vbranchA
1711 # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt
1712 # and e.txt should be copied untouched; b.txt should be 'M'odified,
1713 # and c.txt should be 'D'eleted.
1715 rev = 2
1716 conv.logs[rev].check('Initial revision', (
1717 ('/%(trunk)s/proj', 'A'),
1718 ('/%(trunk)s/proj/b.txt', 'A'),
1719 ('/%(trunk)s/proj/c.txt', 'A'),
1720 ('/%(trunk)s/proj/d.txt', 'A'),
1723 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1724 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1725 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1728 conv.logs[rev + 2].check('First vendor branch revision.', (
1729 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1730 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1731 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1734 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1735 'to compensate for changes in r4,', (
1736 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'),
1737 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'),
1738 ('/%(trunk)s/proj/c.txt', 'D'),
1741 rev = 7
1742 conv.logs[rev].check('This log message is not the standard', (
1743 ('/%(trunk)s/proj/e.txt', 'A'),
1746 conv.logs[rev + 2].check('First vendor branch revision', (
1747 ('/%(branches)s/vbranchB/proj/e.txt', 'M'),
1750 conv.logs[rev + 3].check('This commit was generated by cvs2svn '
1751 'to compensate for changes in r9,', (
1752 ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'),
1756 @Cvs2SvnTestFunction
1757 def vendor_branch_trunk_only():
1758 "handle vendor branches with --trunk-only"
1759 conv = ensure_conversion('vendor-branch-sameness', args=['--trunk-only'])
1761 rev = 2
1762 conv.logs[rev].check('Initial revision', (
1763 ('/%(trunk)s/proj', 'A'),
1764 ('/%(trunk)s/proj/b.txt', 'A'),
1765 ('/%(trunk)s/proj/c.txt', 'A'),
1766 ('/%(trunk)s/proj/d.txt', 'A'),
1769 conv.logs[rev + 1].check('First vendor branch revision', (
1770 ('/%(trunk)s/proj/a.txt', 'A'),
1771 ('/%(trunk)s/proj/b.txt', 'M'),
1772 ('/%(trunk)s/proj/c.txt', 'D'),
1775 conv.logs[rev + 2].check('This log message is not the standard', (
1776 ('/%(trunk)s/proj/e.txt', 'A'),
1779 conv.logs[rev + 3].check('First vendor branch revision', (
1780 ('/%(trunk)s/proj/e.txt', 'M'),
1784 @Cvs2SvnTestFunction
1785 def default_branches():
1786 "handle default branches correctly"
1787 conv = ensure_conversion('default-branches')
1789 # There are seven files in the repository:
1791 # a.txt:
1792 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1793 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1794 # committed (thus losing the default branch "1.1.1"), then
1795 # 1.1.1.4 was imported. All vendor import release tags are
1796 # still present.
1798 # b.txt:
1799 # Like a.txt, but without rev 1.2.
1801 # c.txt:
1802 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1804 # d.txt:
1805 # Same as the previous two, but 1.1.1 branch is unlabeled.
1807 # e.txt:
1808 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1810 # deleted-on-vendor-branch.txt,v:
1811 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1813 # added-then-imported.txt,v:
1814 # Added with 'cvs add' to create 1.1, then imported with
1815 # completely different contents to create 1.1.1.1, therefore
1816 # never had a default branch.
1819 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1820 ('/%(branches)s/unlabeled-1.1.1', 'A'),
1821 ('/%(branches)s/unlabeled-1.1.1/proj', 'A'),
1822 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'),
1823 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'),
1824 ('/%(branches)s/vbranchA', 'A'),
1825 ('/%(branches)s/vbranchA/proj', 'A'),
1826 ('/%(branches)s/vbranchA/proj/a.txt', 'A'),
1827 ('/%(branches)s/vbranchA/proj/b.txt', 'A'),
1828 ('/%(branches)s/vbranchA/proj/c.txt', 'A'),
1829 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1832 conv.logs[3].check("This commit was generated by cvs2svn "
1833 "to compensate for changes in r2,", (
1834 ('/%(trunk)s/proj', 'A'),
1835 ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'),
1836 ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'),
1837 ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'),
1838 ('/%(trunk)s/proj/d.txt '
1839 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1840 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1841 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'),
1842 ('/%(trunk)s/proj/e.txt '
1843 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'),
1846 conv.logs[4].check(sym_log_msg('vtag-1',1), (
1847 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'),
1848 ('/%(tags)s/vtag-1/proj/d.txt '
1849 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'),
1852 conv.logs[5].check("Import (vbranchA, vtag-2).", (
1853 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1854 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1855 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1856 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1857 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1858 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1861 conv.logs[6].check("This commit was generated by cvs2svn "
1862 "to compensate for changes in r5,", (
1863 ('/%(trunk)s/proj/a.txt '
1864 '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'),
1865 ('/%(trunk)s/proj/b.txt '
1866 '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'),
1867 ('/%(trunk)s/proj/c.txt '
1868 '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'),
1869 ('/%(trunk)s/proj/d.txt '
1870 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'),
1871 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1872 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)',
1873 'R'),
1874 ('/%(trunk)s/proj/e.txt '
1875 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'),
1878 conv.logs[7].check(sym_log_msg('vtag-2',1), (
1879 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'),
1880 ('/%(tags)s/vtag-2/proj/d.txt '
1881 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1884 conv.logs[8].check("Import (vbranchA, vtag-3).", (
1885 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1886 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1887 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1888 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1889 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1890 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1893 conv.logs[9].check("This commit was generated by cvs2svn "
1894 "to compensate for changes in r8,", (
1895 ('/%(trunk)s/proj/a.txt '
1896 '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'),
1897 ('/%(trunk)s/proj/b.txt '
1898 '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'),
1899 ('/%(trunk)s/proj/c.txt '
1900 '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'),
1901 ('/%(trunk)s/proj/d.txt '
1902 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'),
1903 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1904 ('/%(trunk)s/proj/e.txt '
1905 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'),
1908 conv.logs[10].check(sym_log_msg('vtag-3',1), (
1909 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'),
1910 ('/%(tags)s/vtag-3/proj/d.txt '
1911 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'),
1912 ('/%(tags)s/vtag-3/proj/e.txt '
1913 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'),
1916 conv.logs[11].check("First regular commit, to a.txt, on vtag-3.", (
1917 ('/%(trunk)s/proj/a.txt', 'M'),
1920 conv.logs[12].check("Add a file to the working copy.", (
1921 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1924 conv.logs[13].check(sym_log_msg('vbranchA'), (
1925 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1926 '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'),
1929 conv.logs[14].check("Import (vbranchA, vtag-4).", (
1930 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1931 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1932 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1933 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1934 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1935 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1936 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1939 conv.logs[15].check("This commit was generated by cvs2svn "
1940 "to compensate for changes in r14,", (
1941 ('/%(trunk)s/proj/b.txt '
1942 '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'),
1943 ('/%(trunk)s/proj/c.txt '
1944 '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'),
1945 ('/%(trunk)s/proj/d.txt '
1946 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'),
1947 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1948 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)',
1949 'A'),
1950 ('/%(trunk)s/proj/e.txt '
1951 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'),
1954 conv.logs[16].check(sym_log_msg('vtag-4',1), (
1955 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'),
1956 ('/%(tags)s/vtag-4/proj/d.txt '
1957 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'),
1961 @Cvs2SvnTestFunction
1962 def default_branches_trunk_only():
1963 "handle default branches with --trunk-only"
1965 conv = ensure_conversion('default-branches', args=['--trunk-only'])
1967 conv.logs[2].check("Import (vbranchA, vtag-1).", (
1968 ('/%(trunk)s/proj', 'A'),
1969 ('/%(trunk)s/proj/a.txt', 'A'),
1970 ('/%(trunk)s/proj/b.txt', 'A'),
1971 ('/%(trunk)s/proj/c.txt', 'A'),
1972 ('/%(trunk)s/proj/d.txt', 'A'),
1973 ('/%(trunk)s/proj/e.txt', 'A'),
1974 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1977 conv.logs[3].check("Import (vbranchA, vtag-2).", (
1978 ('/%(trunk)s/proj/a.txt', 'M'),
1979 ('/%(trunk)s/proj/b.txt', 'M'),
1980 ('/%(trunk)s/proj/c.txt', 'M'),
1981 ('/%(trunk)s/proj/d.txt', 'M'),
1982 ('/%(trunk)s/proj/e.txt', 'M'),
1983 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'),
1986 conv.logs[4].check("Import (vbranchA, vtag-3).", (
1987 ('/%(trunk)s/proj/a.txt', 'M'),
1988 ('/%(trunk)s/proj/b.txt', 'M'),
1989 ('/%(trunk)s/proj/c.txt', 'M'),
1990 ('/%(trunk)s/proj/d.txt', 'M'),
1991 ('/%(trunk)s/proj/e.txt', 'M'),
1992 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1995 conv.logs[5].check("First regular commit, to a.txt, on vtag-3.", (
1996 ('/%(trunk)s/proj/a.txt', 'M'),
1999 conv.logs[6].check("Add a file to the working copy.", (
2000 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
2003 conv.logs[7].check("Import (vbranchA, vtag-4).", (
2004 ('/%(trunk)s/proj/b.txt', 'M'),
2005 ('/%(trunk)s/proj/c.txt', 'M'),
2006 ('/%(trunk)s/proj/d.txt', 'M'),
2007 ('/%(trunk)s/proj/e.txt', 'M'),
2008 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
2012 @Cvs2SvnTestFunction
2013 def default_branch_and_1_2():
2014 "do not allow 1.2 revision with default branch"
2016 conv = ensure_conversion(
2017 'default-branch-and-1-2',
2018 error_re=(
2019 r'.*File \'.*\' has default branch=1\.1\.1 but also a revision 1\.2'
2024 @Cvs2SvnTestFunction
2025 def compose_tag_three_sources():
2026 "compose a tag from three sources"
2027 conv = ensure_conversion('compose-tag-three-sources')
2029 conv.logs[2].check("Add on trunk", (
2030 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
2031 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
2032 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
2033 ('/%(trunk)s/tagged-on-b1', 'A'),
2034 ('/%(trunk)s/tagged-on-b2', 'A'),
2037 conv.logs[3].check(sym_log_msg('b1'), (
2038 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
2041 conv.logs[4].check(sym_log_msg('b2'), (
2042 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
2045 conv.logs[5].check("Commit on branch b1", (
2046 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
2047 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
2048 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
2049 ('/%(branches)s/b1/tagged-on-b1', 'M'),
2050 ('/%(branches)s/b1/tagged-on-b2', 'M'),
2053 conv.logs[6].check("Commit on branch b2", (
2054 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
2055 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
2056 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
2057 ('/%(branches)s/b2/tagged-on-b1', 'M'),
2058 ('/%(branches)s/b2/tagged-on-b2', 'M'),
2061 conv.logs[7].check("Commit again on trunk", (
2062 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
2063 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
2064 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
2065 ('/%(trunk)s/tagged-on-b1', 'M'),
2066 ('/%(trunk)s/tagged-on-b2', 'M'),
2069 conv.logs[8].check(sym_log_msg('T',1), (
2070 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
2071 ('/%(tags)s/T/tagged-on-trunk-1.1 '
2072 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
2073 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:5)', 'R'),
2074 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:6)', 'R'),
2078 @Cvs2SvnTestFunction
2079 def pass5_when_to_fill():
2080 "reserve a svn revnum for a fill only when required"
2081 # The conversion will fail if the bug is present, and
2082 # ensure_conversion would raise Failure.
2083 conv = ensure_conversion('pass5-when-to-fill')
2086 class EmptyTrunk(Cvs2SvnTestCase):
2087 "don't break when the trunk is empty"
2089 def __init__(self, **kw):
2090 Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw)
2092 def run(self, sbox):
2093 # The conversion will fail if the bug is present, and
2094 # ensure_conversion would raise Failure.
2095 conv = self.ensure_conversion()
2098 @Cvs2SvnTestFunction
2099 def no_spurious_svn_commits():
2100 "ensure that we don't create any spurious commits"
2101 conv = ensure_conversion('phoenix')
2103 # Check spurious commit that could be created in
2104 # SVNCommitCreator._pre_commit()
2106 # (When you add a file on a branch, CVS creates a trunk revision
2107 # in state 'dead'. If the log message of that commit is equal to
2108 # the one that CVS generates, we do not ever create a 'fill'
2109 # SVNCommit for it.)
2111 # and spurious commit that could be created in
2112 # SVNCommitCreator._commit()
2114 # (When you add a file on a branch, CVS creates a trunk revision
2115 # in state 'dead'. If the log message of that commit is equal to
2116 # the one that CVS generates, we do not create a primary SVNCommit
2117 # for it.)
2118 conv.logs[17].check('File added on branch xiphophorus', (
2119 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
2122 # Check to make sure that a commit *is* generated:
2123 # (When you add a file on a branch, CVS creates a trunk revision
2124 # in state 'dead'. If the log message of that commit is NOT equal
2125 # to the one that CVS generates, we create a primary SVNCommit to
2126 # serve as a home for the log message in question.
2127 conv.logs[18].check('file added-on-branch2.txt was initially added on '
2128 + 'branch xiphophorus,\nand this log message was tweaked', ())
2130 # Check spurious commit that could be created in
2131 # SVNCommitCreator._commit_symbols().
2132 conv.logs[19].check('This file was also added on branch xiphophorus,', (
2133 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
2137 class PeerPathPruning(Cvs2SvnTestCase):
2138 "make sure that filling prunes paths correctly"
2140 def __init__(self, **kw):
2141 Cvs2SvnTestCase.__init__(self, 'peer-path-pruning', **kw)
2143 def run(self, sbox):
2144 conv = self.ensure_conversion()
2145 conv.logs[6].check(sym_log_msg('BRANCH'), (
2146 ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A'),
2147 ('/%(branches)s/BRANCH/bar', 'D'),
2148 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:5)', 'R'),
2152 @Cvs2SvnTestFunction
2153 def invalid_closings_on_trunk():
2154 "verify correct revs are copied to default branches"
2155 # The conversion will fail if the bug is present, and
2156 # ensure_conversion would raise Failure.
2157 conv = ensure_conversion('invalid-closings-on-trunk')
2160 @Cvs2SvnTestFunction
2161 def individual_passes():
2162 "run each pass individually"
2163 conv = ensure_conversion('main')
2164 conv2 = ensure_conversion('main', passbypass=1)
2166 if conv.logs != conv2.logs:
2167 raise Failure()
2170 @Cvs2SvnTestFunction
2171 def resync_bug():
2172 "reveal a big bug in our resync algorithm"
2173 # This will fail if the bug is present
2174 conv = ensure_conversion('resync-bug')
2177 @Cvs2SvnTestFunction
2178 def branch_from_default_branch():
2179 "reveal a bug in our default branch detection code"
2180 conv = ensure_conversion('branch-from-default-branch')
2182 # This revision will be a default branch synchronization only
2183 # if cvs2svn is correctly determining default branch revisions.
2185 # The bug was that cvs2svn was treating revisions on branches off of
2186 # default branches as default branch revisions, resulting in
2187 # incorrectly regarding the branch off of the default branch as a
2188 # non-trunk default branch. Crystal clear? I thought so. See
2189 # issue #42 for more incoherent blathering.
2190 conv.logs[5].check("This commit was generated by cvs2svn", (
2191 ('/%(trunk)s/proj/file.txt '
2192 '(from /%(branches)s/upstream/proj/file.txt:4)', 'R'),
2196 @Cvs2SvnTestFunction
2197 def file_in_attic_too():
2198 "die if a file exists in and out of the attic"
2199 ensure_conversion(
2200 'file-in-attic-too',
2201 error_re=(
2202 r'.*A CVS repository cannot contain both '
2203 r'(.*)' + re.escape(os.sep) + r'(.*) '
2204 + r'and '
2205 r'\1' + re.escape(os.sep) + r'Attic' + re.escape(os.sep) + r'\2'
2210 @Cvs2SvnTestFunction
2211 def retain_file_in_attic_too():
2212 "test --retain-conflicting-attic-files option"
2213 conv = ensure_conversion(
2214 'file-in-attic-too', args=['--retain-conflicting-attic-files'])
2215 if not conv.path_exists('trunk', 'file.txt'):
2216 raise Failure()
2217 if not conv.path_exists('trunk', 'Attic', 'file.txt'):
2218 raise Failure()
2221 @Cvs2SvnTestFunction
2222 def symbolic_name_filling_guide():
2223 "reveal a big bug in our SymbolFillingGuide"
2224 # This will fail if the bug is present
2225 conv = ensure_conversion('symbolic-name-overfill')
2228 # Helpers for tests involving file contents and properties.
2230 class NodeTreeWalkException:
2231 "Exception class for node tree traversals."
2232 pass
2234 def node_for_path(node, path):
2235 "In the tree rooted under SVNTree NODE, return the node at PATH."
2236 if node.name != '__SVN_ROOT_NODE':
2237 raise NodeTreeWalkException()
2238 path = path.strip('/')
2239 components = path.split('/')
2240 for component in components:
2241 node = get_child(node, component)
2242 return node
2244 # Helper for tests involving properties.
2245 def props_for_path(node, path):
2246 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
2247 return node_for_path(node, path).props
2250 class EOLMime(Cvs2SvnPropertiesTestCase):
2251 """eol settings and mime types together
2253 The files are as follows:
2255 trunk/foo.txt: no -kb, mime file says nothing.
2256 trunk/foo.xml: no -kb, mime file says text.
2257 trunk/foo.zip: no -kb, mime file says non-text.
2258 trunk/foo.bin: has -kb, mime file says nothing.
2259 trunk/foo.csv: has -kb, mime file says text.
2260 trunk/foo.dbf: has -kb, mime file says non-text.
2263 def __init__(self, args, **kw):
2264 # TODO: It's a bit klugey to construct this path here. But so far
2265 # there's only one test with a mime.types file. If we have more,
2266 # we should abstract this into some helper, which would be located
2267 # near ensure_conversion(). Note that it is a convention of this
2268 # test suite for a mime.types file to be located in the top level
2269 # of the CVS repository to which it applies.
2270 self.mime_path = os.path.join(
2271 test_data_dir, 'eol-mime-cvsrepos', 'mime.types')
2273 Cvs2SvnPropertiesTestCase.__init__(
2274 self, 'eol-mime',
2275 props_to_test=['svn:eol-style', 'svn:mime-type', 'svn:keywords'],
2276 args=['--mime-types=%s' % self.mime_path] + args,
2277 **kw)
2280 # We do four conversions. Each time, we pass --mime-types=FILE with
2281 # the same FILE, but vary --default-eol and --eol-from-mime-type.
2282 # Thus there's one conversion with neither flag, one with just the
2283 # former, one with just the latter, and one with both.
2286 # Neither --no-default-eol nor --eol-from-mime-type:
2287 eol_mime1 = EOLMime(
2288 variant=1,
2289 args=[],
2290 expected_props=[
2291 ('trunk/foo.txt', [None, None, None]),
2292 ('trunk/foo.xml', [None, 'text/xml', None]),
2293 ('trunk/foo.zip', [None, 'application/zip', None]),
2294 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2295 ('trunk/foo.csv', [None, 'text/csv', None]),
2296 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2300 # Just --no-default-eol, not --eol-from-mime-type:
2301 eol_mime2 = EOLMime(
2302 variant=2,
2303 args=['--default-eol=native'],
2304 expected_props=[
2305 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2306 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2307 ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS]),
2308 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2309 ('trunk/foo.csv', [None, 'text/csv', None]),
2310 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2314 # Just --eol-from-mime-type, not --no-default-eol:
2315 eol_mime3 = EOLMime(
2316 variant=3,
2317 args=['--eol-from-mime-type'],
2318 expected_props=[
2319 ('trunk/foo.txt', [None, None, None]),
2320 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2321 ('trunk/foo.zip', [None, 'application/zip', None]),
2322 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2323 ('trunk/foo.csv', [None, 'text/csv', None]),
2324 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2328 # Both --no-default-eol and --eol-from-mime-type:
2329 eol_mime4 = EOLMime(
2330 variant=4,
2331 args=['--eol-from-mime-type', '--default-eol=native'],
2332 expected_props=[
2333 ('trunk/foo.txt', ['native', None, KEYWORDS]),
2334 ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]),
2335 ('trunk/foo.zip', [None, 'application/zip', None]),
2336 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
2337 ('trunk/foo.csv', [None, 'text/csv', None]),
2338 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
2342 cvs_revnums_off = Cvs2SvnPropertiesTestCase(
2343 'eol-mime',
2344 doc='test non-setting of cvs2svn:cvs-rev property',
2345 args=[],
2346 props_to_test=['cvs2svn:cvs-rev'],
2347 expected_props=[
2348 ('trunk/foo.txt', [None]),
2349 ('trunk/foo.xml', [None]),
2350 ('trunk/foo.zip', [None]),
2351 ('trunk/foo.bin', [None]),
2352 ('trunk/foo.csv', [None]),
2353 ('trunk/foo.dbf', [None]),
2357 cvs_revnums_on = Cvs2SvnPropertiesTestCase(
2358 'eol-mime',
2359 doc='test setting of cvs2svn:cvs-rev property',
2360 args=['--cvs-revnums'],
2361 props_to_test=['cvs2svn:cvs-rev'],
2362 expected_props=[
2363 ('trunk/foo.txt', ['1.2']),
2364 ('trunk/foo.xml', ['1.2']),
2365 ('trunk/foo.zip', ['1.2']),
2366 ('trunk/foo.bin', ['1.2']),
2367 ('trunk/foo.csv', ['1.2']),
2368 ('trunk/foo.dbf', ['1.2']),
2372 keywords = Cvs2SvnPropertiesTestCase(
2373 'keywords',
2374 doc='test setting of svn:keywords property among others',
2375 args=['--default-eol=native'],
2376 props_to_test=['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
2377 expected_props=[
2378 ('trunk/foo.default', [KEYWORDS, 'native', None]),
2379 ('trunk/foo.kkvl', [KEYWORDS, 'native', None]),
2380 ('trunk/foo.kkv', [KEYWORDS, 'native', None]),
2381 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
2382 ('trunk/foo.kk', [None, 'native', None]),
2383 ('trunk/foo.ko', [None, 'native', None]),
2384 ('trunk/foo.kv', [None, 'native', None]),
2388 @Cvs2SvnTestFunction
2389 def ignore():
2390 "test setting of svn:ignore property"
2391 conv = ensure_conversion('cvsignore')
2392 wc_tree = conv.get_wc_tree()
2393 topdir_props = props_for_path(wc_tree, 'trunk/proj')
2394 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
2396 if topdir_props['svn:ignore'] != \
2397 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2398 raise Failure()
2400 if subdir_props['svn:ignore'] != \
2401 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
2402 raise Failure()
2405 @Cvs2SvnTestFunction
2406 def requires_cvs():
2407 "test that CVS can still do what RCS can't"
2408 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
2409 conv = ensure_conversion('requires-cvs', args=["--use-cvs"])
2411 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
2412 cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read()
2414 if atsign_contents[-1:] == "@":
2415 raise Failure()
2416 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
2417 raise Failure()
2419 if not (conv.logs[21].author == "William Lyon Phelps III" and
2420 conv.logs[20].author == "j random"):
2421 raise Failure()
2424 @Cvs2SvnTestFunction
2425 def questionable_branch_names():
2426 "test that we can handle weird branch names"
2427 conv = ensure_conversion('questionable-symbols')
2428 # If the conversion succeeds, then we're okay. We could check the
2429 # actual branch paths, too, but the main thing is to know that the
2430 # conversion doesn't fail.
2433 @Cvs2SvnTestFunction
2434 def questionable_tag_names():
2435 "test that we can handle weird tag names"
2436 conv = ensure_conversion('questionable-symbols')
2437 conv.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), (
2438 ('/%(tags)s/Tag_A (from /trunk:8)', 'A'),
2440 conv.find_tag_log('TagWith/Backslash_E').check(
2441 sym_log_msg('TagWith/Backslash_E',1),
2443 ('/%(tags)s/TagWith', 'A'),
2444 ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'),
2447 conv.find_tag_log('TagWith/Slash_Z').check(
2448 sym_log_msg('TagWith/Slash_Z',1),
2450 ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'),
2455 @Cvs2SvnTestFunction
2456 def revision_reorder_bug():
2457 "reveal a bug that reorders file revisions"
2458 conv = ensure_conversion('revision-reorder-bug')
2459 # If the conversion succeeds, then we're okay. We could check the
2460 # actual revisions, too, but the main thing is to know that the
2461 # conversion doesn't fail.
2464 @Cvs2SvnTestFunction
2465 def exclude():
2466 "test that exclude really excludes everything"
2467 conv = ensure_conversion('main', args=['--exclude=.*'])
2468 for log in conv.logs.values():
2469 for item in log.changed_paths.keys():
2470 if item.startswith('/branches/') or item.startswith('/tags/'):
2471 raise Failure()
2474 @Cvs2SvnTestFunction
2475 def vendor_branch_delete_add():
2476 "add trunk file that was deleted on vendor branch"
2477 # This will error if the bug is present
2478 conv = ensure_conversion('vendor-branch-delete-add')
2481 @Cvs2SvnTestFunction
2482 def resync_pass2_pull_forward():
2483 "ensure pass2 doesn't pull rev too far forward"
2484 conv = ensure_conversion('resync-pass2-pull-forward')
2485 # If the conversion succeeds, then we're okay. We could check the
2486 # actual revisions, too, but the main thing is to know that the
2487 # conversion doesn't fail.
2490 @Cvs2SvnTestFunction
2491 def native_eol():
2492 "only LFs for svn:eol-style=native files"
2493 conv = ensure_conversion('native-eol', args=['--default-eol=native'])
2494 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
2495 conv.repos)
2496 # Verify that all files in the dump have LF EOLs. We're actually
2497 # testing the whole dump file, but the dump file itself only uses
2498 # LF EOLs, so we're safe.
2499 for line in lines:
2500 if line[-1] != '\n' or line[:-1].find('\r') != -1:
2501 raise Failure()
2504 @Cvs2SvnTestFunction
2505 def double_fill():
2506 "reveal a bug that created a branch twice"
2507 conv = ensure_conversion('double-fill')
2508 # If the conversion succeeds, then we're okay. We could check the
2509 # actual revisions, too, but the main thing is to know that the
2510 # conversion doesn't fail.
2513 @Cvs2SvnTestFunction
2514 def double_fill2():
2515 "reveal a second bug that created a branch twice"
2516 conv = ensure_conversion('double-fill2')
2517 conv.logs[6].check_msg(sym_log_msg('BRANCH1'))
2518 conv.logs[7].check_msg(sym_log_msg('BRANCH2'))
2519 try:
2520 # This check should fail:
2521 conv.logs[8].check_msg(sym_log_msg('BRANCH2'))
2522 except Failure:
2523 pass
2524 else:
2525 raise Failure('Symbol filled twice in a row')
2528 @Cvs2SvnTestFunction
2529 def resync_pass2_push_backward():
2530 "ensure pass2 doesn't push rev too far backward"
2531 conv = ensure_conversion('resync-pass2-push-backward')
2532 # If the conversion succeeds, then we're okay. We could check the
2533 # actual revisions, too, but the main thing is to know that the
2534 # conversion doesn't fail.
2537 @Cvs2SvnTestFunction
2538 def double_add():
2539 "reveal a bug that added a branch file twice"
2540 conv = ensure_conversion('double-add')
2541 # If the conversion succeeds, then we're okay. We could check the
2542 # actual revisions, too, but the main thing is to know that the
2543 # conversion doesn't fail.
2546 @Cvs2SvnTestFunction
2547 def bogus_branch_copy():
2548 "reveal a bug that copies a branch file wrongly"
2549 conv = ensure_conversion('bogus-branch-copy')
2550 # If the conversion succeeds, then we're okay. We could check the
2551 # actual revisions, too, but the main thing is to know that the
2552 # conversion doesn't fail.
2555 @Cvs2SvnTestFunction
2556 def nested_ttb_directories():
2557 "require error if ttb directories are not disjoint"
2558 opts_list = [
2559 {'trunk' : 'a', 'branches' : 'a',},
2560 {'trunk' : 'a', 'tags' : 'a',},
2561 {'branches' : 'a', 'tags' : 'a',},
2562 # This option conflicts with the default trunk path:
2563 {'branches' : 'trunk',},
2564 # Try some nested directories:
2565 {'trunk' : 'a', 'branches' : 'a/b',},
2566 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
2567 {'branches' : 'a', 'tags' : 'a/b',},
2570 for opts in opts_list:
2571 ensure_conversion(
2572 'main', error_re=r'The following paths are not disjoint\:', **opts
2576 class AutoProps(Cvs2SvnPropertiesTestCase):
2577 """Test auto-props.
2579 The files are as follows:
2581 trunk/foo.txt: no -kb, mime auto-prop says nothing.
2582 trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
2583 trunk/foo.zip: no -kb, mime auto-prop says non-text.
2584 trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=<unset>.
2585 trunk/foo.bin: has -kb, mime auto-prop says nothing.
2586 trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF.
2587 trunk/foo.dbf: has -kb, mime auto-prop says non-text.
2588 trunk/foo.UPCASE1: no -kb, no mime type.
2589 trunk/foo.UPCASE2: no -kb, no mime type.
2592 def __init__(self, args, **kw):
2593 ### TODO: It's a bit klugey to construct this path here. See also
2594 ### the comment in eol_mime().
2595 auto_props_path = os.path.join(
2596 test_data_dir, 'eol-mime-cvsrepos', 'auto-props')
2598 Cvs2SvnPropertiesTestCase.__init__(
2599 self, 'eol-mime',
2600 props_to_test=[
2601 'myprop',
2602 'svn:eol-style',
2603 'svn:mime-type',
2604 'svn:keywords',
2605 'svn:executable',
2607 args=[
2608 '--auto-props=%s' % auto_props_path,
2609 '--eol-from-mime-type'
2610 ] + args,
2611 **kw)
2614 auto_props_ignore_case = AutoProps(
2615 doc="test auto-props",
2616 args=['--default-eol=native'],
2617 expected_props=[
2618 ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS, None]),
2619 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS, None]),
2620 ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]),
2621 ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]),
2622 ('trunk/foo.bin',
2623 ['bin', None, 'application/octet-stream', None, '']),
2624 ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]),
2625 ('trunk/foo.dbf',
2626 ['dbf', None, 'application/what-is-dbf', None, None]),
2627 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS, None]),
2628 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS, None]),
2632 @Cvs2SvnTestFunction
2633 def ctrl_char_in_filename():
2634 "do not allow control characters in filenames"
2636 try:
2637 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
2638 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
2639 if os.path.exists(dstrepos_path):
2640 safe_rmtree(dstrepos_path)
2642 # create repos from existing main repos
2643 shutil.copytree(srcrepos_path, dstrepos_path)
2644 base_path = os.path.join(dstrepos_path, 'single-files')
2645 try:
2646 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2647 os.path.join(base_path, 'two\rquick,v'))
2648 except:
2649 # Operating systems that don't allow control characters in
2650 # filenames will hopefully have thrown an exception; in that
2651 # case, just skip this test.
2652 raise svntest.Skip()
2654 conv = ensure_conversion(
2655 'ctrl-char-filename',
2656 error_re=(r'.*Character .* in filename .* '
2657 r'is not supported by Subversion\.'),
2659 finally:
2660 safe_rmtree(dstrepos_path)
2663 @Cvs2SvnTestFunction
2664 def commit_dependencies():
2665 "interleaved and multi-branch commits to same files"
2666 conv = ensure_conversion("commit-dependencies")
2667 conv.logs[2].check('adding', (
2668 ('/%(trunk)s/interleaved', 'A'),
2669 ('/%(trunk)s/interleaved/file1', 'A'),
2670 ('/%(trunk)s/interleaved/file2', 'A'),
2672 conv.logs[3].check('big commit', (
2673 ('/%(trunk)s/interleaved/file1', 'M'),
2674 ('/%(trunk)s/interleaved/file2', 'M'),
2676 conv.logs[4].check('dependant small commit', (
2677 ('/%(trunk)s/interleaved/file1', 'M'),
2679 conv.logs[5].check('adding', (
2680 ('/%(trunk)s/multi-branch', 'A'),
2681 ('/%(trunk)s/multi-branch/file1', 'A'),
2682 ('/%(trunk)s/multi-branch/file2', 'A'),
2684 conv.logs[6].check(sym_log_msg("branch"), (
2685 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2686 ('/%(branches)s/branch/interleaved', 'D'),
2688 conv.logs[7].check('multi-branch-commit', (
2689 ('/%(trunk)s/multi-branch/file1', 'M'),
2690 ('/%(trunk)s/multi-branch/file2', 'M'),
2691 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2692 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2696 @Cvs2SvnTestFunction
2697 def double_branch_delete():
2698 "fill branches before modifying files on them"
2699 conv = ensure_conversion('double-branch-delete')
2701 # Test for issue #102. The file IMarshalledValue.java is branched,
2702 # deleted, readded on the branch, and then deleted again. If the
2703 # fill for the file on the branch is postponed until after the
2704 # modification, the file will end up live on the branch instead of
2705 # dead! Make sure it happens at the right time.
2707 conv.logs[6].check('JBAS-2436 - Adding LGPL Header2', (
2708 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2711 conv.logs[7].check('JBAS-3025 - Removing dependency', (
2712 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2716 @Cvs2SvnTestFunction
2717 def symbol_mismatches():
2718 "error for conflicting tag/branch"
2720 ensure_conversion(
2721 'symbol-mess',
2722 args=['--symbol-default=strict'],
2723 error_re=r'.*Problems determining how symbols should be converted',
2727 @Cvs2SvnTestFunction
2728 def overlook_symbol_mismatches():
2729 "overlook conflicting tag/branch when --trunk-only"
2731 # This is a test for issue #85.
2733 ensure_conversion('symbol-mess', args=['--trunk-only'])
2736 @Cvs2SvnTestFunction
2737 def force_symbols():
2738 "force symbols to be tags/branches"
2740 conv = ensure_conversion(
2741 'symbol-mess',
2742 args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2743 if conv.path_exists('tags', 'BRANCH') \
2744 or not conv.path_exists('branches', 'BRANCH'):
2745 raise Failure()
2746 if not conv.path_exists('tags', 'TAG') \
2747 or conv.path_exists('branches', 'TAG'):
2748 raise Failure()
2749 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2750 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2751 raise Failure()
2752 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2753 or conv.path_exists('branches', 'MOSTLY_TAG'):
2754 raise Failure()
2757 @Cvs2SvnTestFunction
2758 def commit_blocks_tags():
2759 "commit prevents forced tag"
2761 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2762 ensure_conversion(
2763 'symbol-mess',
2764 args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']),
2765 error_re=(
2766 r'.*The following branches cannot be forced to be tags '
2767 r'because they have commits'
2772 @Cvs2SvnTestFunction
2773 def blocked_excludes():
2774 "error for blocked excludes"
2776 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2777 for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']:
2778 try:
2779 ensure_conversion(
2780 'symbol-mess',
2781 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker]))
2782 raise MissingErrorException()
2783 except Failure:
2784 pass
2787 @Cvs2SvnTestFunction
2788 def unblock_blocked_excludes():
2789 "excluding blocker removes blockage"
2791 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2792 for blocker in ['BRANCH', 'COMMIT']:
2793 ensure_conversion(
2794 'symbol-mess',
2795 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker,
2796 '--exclude=BLOCKING_%s' % blocker]))
2799 @Cvs2SvnTestFunction
2800 def regexp_force_symbols():
2801 "force symbols via regular expressions"
2803 conv = ensure_conversion(
2804 'symbol-mess',
2805 args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2806 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2807 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2808 raise Failure()
2809 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2810 or conv.path_exists('branches', 'MOSTLY_TAG'):
2811 raise Failure()
2814 @Cvs2SvnTestFunction
2815 def heuristic_symbol_default():
2816 "test 'heuristic' symbol default"
2818 conv = ensure_conversion(
2819 'symbol-mess', args=['--symbol-default=heuristic'])
2820 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2821 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2822 raise Failure()
2823 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2824 or conv.path_exists('branches', 'MOSTLY_TAG'):
2825 raise Failure()
2828 @Cvs2SvnTestFunction
2829 def branch_symbol_default():
2830 "test 'branch' symbol default"
2832 conv = ensure_conversion(
2833 'symbol-mess', args=['--symbol-default=branch'])
2834 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2835 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2836 raise Failure()
2837 if conv.path_exists('tags', 'MOSTLY_TAG') \
2838 or not conv.path_exists('branches', 'MOSTLY_TAG'):
2839 raise Failure()
2842 @Cvs2SvnTestFunction
2843 def tag_symbol_default():
2844 "test 'tag' symbol default"
2846 conv = ensure_conversion(
2847 'symbol-mess', args=['--symbol-default=tag'])
2848 if not conv.path_exists('tags', 'MOSTLY_BRANCH') \
2849 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
2850 raise Failure()
2851 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2852 or conv.path_exists('branches', 'MOSTLY_TAG'):
2853 raise Failure()
2856 @Cvs2SvnTestFunction
2857 def symbol_transform():
2858 "test --symbol-transform"
2860 conv = ensure_conversion(
2861 'symbol-mess',
2862 args=[
2863 '--symbol-default=heuristic',
2864 '--symbol-transform=BRANCH:branch',
2865 '--symbol-transform=TAG:tag',
2866 '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1',
2868 if not conv.path_exists('branches', 'branch'):
2869 raise Failure()
2870 if not conv.path_exists('tags', 'tag'):
2871 raise Failure()
2872 if not conv.path_exists('branches', 'MOSTLY.BRANCH'):
2873 raise Failure()
2874 if not conv.path_exists('tags', 'MOSTLY.TAG'):
2875 raise Failure()
2878 @Cvs2SvnTestFunction
2879 def write_symbol_info():
2880 "test --write-symbol-info"
2882 expected_lines = [
2883 ['0', '.trunk.',
2884 'trunk', 'trunk', '.'],
2885 ['0', 'BLOCKED_BY_UNNAMED',
2886 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'],
2887 ['0', 'BLOCKING_COMMIT',
2888 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'],
2889 ['0', 'BLOCKED_BY_COMMIT',
2890 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'],
2891 ['0', 'BLOCKING_BRANCH',
2892 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'],
2893 ['0', 'BLOCKED_BY_BRANCH',
2894 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'],
2895 ['0', 'MOSTLY_BRANCH',
2896 '.', '.', '.'],
2897 ['0', 'MOSTLY_TAG',
2898 '.', '.', '.'],
2899 ['0', 'BRANCH_WITH_COMMIT',
2900 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'],
2901 ['0', 'BRANCH',
2902 'branch', 'branches/BRANCH', '.trunk.'],
2903 ['0', 'TAG',
2904 'tag', 'tags/TAG', '.trunk.'],
2905 ['0', 'unlabeled-1.1.12.1.2',
2906 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'],
2908 expected_lines.sort()
2910 symbol_info_file = os.path.join(tmp_dir, 'symbol-mess-symbol-info.txt')
2911 try:
2912 ensure_conversion(
2913 'symbol-mess',
2914 args=[
2915 '--symbol-default=strict',
2916 '--write-symbol-info=%s' % (symbol_info_file,),
2917 '--passes=:CollateSymbolsPass',
2920 raise MissingErrorException()
2921 except Failure:
2922 pass
2923 lines = []
2924 comment_re = re.compile(r'^\s*\#')
2925 for l in open(symbol_info_file, 'r'):
2926 if comment_re.match(l):
2927 continue
2928 lines.append(l.strip().split())
2929 lines.sort()
2930 if lines != expected_lines:
2931 s = ['Symbol info incorrect\n']
2932 differ = Differ()
2933 for diffline in differ.compare(
2934 [' '.join(line) + '\n' for line in expected_lines],
2935 [' '.join(line) + '\n' for line in lines],
2937 s.append(diffline)
2938 raise Failure(''.join(s))
2941 @Cvs2SvnTestFunction
2942 def symbol_hints():
2943 "test --symbol-hints for setting branch/tag"
2945 conv = ensure_conversion(
2946 'symbol-mess', symbol_hints_file='symbol-mess-symbol-hints.txt',
2948 if not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2949 raise Failure()
2950 if not conv.path_exists('tags', 'MOSTLY_TAG'):
2951 raise Failure()
2952 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
2953 ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'),
2955 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2956 ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'),
2958 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
2959 ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'),
2963 @Cvs2SvnTestFunction
2964 def parent_hints():
2965 "test --symbol-hints for setting parent"
2967 conv = ensure_conversion(
2968 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints.txt',
2970 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
2971 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
2975 @Cvs2SvnTestFunction
2976 def parent_hints_invalid():
2977 "test --symbol-hints with an invalid parent"
2979 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2980 # this symbol hints file sets the preferred parent to BRANCH
2981 # instead:
2982 conv = ensure_conversion(
2983 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-invalid.txt',
2984 error_re=(
2985 r"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT"
2990 @Cvs2SvnTestFunction
2991 def parent_hints_wildcards():
2992 "test --symbol-hints wildcards"
2994 # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.;
2995 # this symbol hints file sets the preferred parent to BRANCH
2996 # instead:
2997 conv = ensure_conversion(
2998 'symbol-mess',
2999 symbol_hints_file='symbol-mess-parent-hints-wildcards.txt',
3001 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3002 ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'),
3006 @Cvs2SvnTestFunction
3007 def path_hints():
3008 "test --symbol-hints for setting svn paths"
3010 conv = ensure_conversion(
3011 'symbol-mess', symbol_hints_file='symbol-mess-path-hints.txt',
3013 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3014 ('/trunk', 'A'),
3015 ('/a', 'A'),
3016 ('/a/strange', 'A'),
3017 ('/a/strange/trunk', 'A'),
3018 ('/a/strange/trunk/path', 'A'),
3019 ('/branches', 'A'),
3020 ('/tags', 'A'),
3022 conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), (
3023 ('/special', 'A'),
3024 ('/special/tag', 'A'),
3025 ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'),
3027 conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), (
3028 ('/special/other', 'A'),
3029 ('/special/other/branch', 'A'),
3030 ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'),
3032 conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), (
3033 ('/special/branch', 'A'),
3034 ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'),
3038 @Cvs2SvnTestFunction
3039 def issue_99():
3040 "test problem from issue 99"
3042 conv = ensure_conversion('issue-99')
3045 @Cvs2SvnTestFunction
3046 def issue_100():
3047 "test problem from issue 100"
3049 conv = ensure_conversion('issue-100')
3050 file1 = conv.get_wc('trunk', 'file1.txt')
3051 if file(file1).read() != 'file1.txt<1.2>\n':
3052 raise Failure()
3055 @Cvs2SvnTestFunction
3056 def issue_106():
3057 "test problem from issue 106"
3059 conv = ensure_conversion('issue-106')
3062 @Cvs2SvnTestFunction
3063 def options_option():
3064 "use of the --options option"
3066 conv = ensure_conversion('main', options_file='cvs2svn.options')
3069 @Cvs2SvnTestFunction
3070 def multiproject():
3071 "multiproject conversion"
3073 conv = ensure_conversion(
3074 'main', options_file='cvs2svn-multiproject.options'
3076 conv.logs[1].check('Standard project directories initialized by cvs2svn.', (
3077 ('/partial-prune', 'A'),
3078 ('/partial-prune/trunk', 'A'),
3079 ('/partial-prune/branches', 'A'),
3080 ('/partial-prune/tags', 'A'),
3081 ('/partial-prune/releases', 'A'),
3085 @Cvs2SvnTestFunction
3086 def crossproject():
3087 "multiproject conversion with cross-project commits"
3089 conv = ensure_conversion(
3090 'main', options_file='cvs2svn-crossproject.options'
3094 @Cvs2SvnTestFunction
3095 def tag_with_no_revision():
3096 "tag defined but revision is deleted"
3098 conv = ensure_conversion('tag-with-no-revision')
3101 @Cvs2SvnTestFunction
3102 def delete_cvsignore():
3103 "svn:ignore should vanish when .cvsignore does"
3105 # This is issue #81.
3107 conv = ensure_conversion('delete-cvsignore')
3109 wc_tree = conv.get_wc_tree()
3110 props = props_for_path(wc_tree, 'trunk/proj')
3112 if props.has_key('svn:ignore'):
3113 raise Failure()
3116 @Cvs2SvnTestFunction
3117 def repeated_deltatext():
3118 "ignore repeated deltatext blocks with warning"
3120 conv = ensure_conversion('repeated-deltatext')
3121 warning_re = r'.*Deltatext block for revision 1.1 appeared twice'
3122 if not conv.output_found(warning_re):
3123 raise Failure()
3126 @Cvs2SvnTestFunction
3127 def nasty_graphs():
3128 "process some nasty dependency graphs"
3130 # It's not how well the bear can dance, but that the bear can dance
3131 # at all:
3132 conv = ensure_conversion('nasty-graphs')
3135 @Cvs2SvnTestFunction
3136 def tagging_after_delete():
3137 "optimal tag after deleting files"
3139 conv = ensure_conversion('tagging-after-delete')
3141 # tag should be 'clean', no deletes
3142 log = conv.find_tag_log('tag1')
3143 expected = (
3144 ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'),
3146 log.check_changes(expected)
3149 @Cvs2SvnTestFunction
3150 def crossed_branches():
3151 "branches created in inconsistent orders"
3153 conv = ensure_conversion('crossed-branches')
3156 @Cvs2SvnTestFunction
3157 def file_directory_conflict():
3158 "error when filename conflicts with directory name"
3160 conv = ensure_conversion(
3161 'file-directory-conflict',
3162 error_re=r'.*Directory name conflicts with filename',
3166 @Cvs2SvnTestFunction
3167 def attic_directory_conflict():
3168 "error when attic filename conflicts with dirname"
3170 # This tests the problem reported in issue #105.
3172 conv = ensure_conversion(
3173 'attic-directory-conflict',
3174 error_re=r'.*Directory name conflicts with filename',
3178 @Cvs2SvnTestFunction
3179 def internal_co():
3180 "verify that --use-internal-co works"
3182 rcs_conv = ensure_conversion(
3183 'main', args=['--use-rcs', '--default-eol=native'],
3185 conv = ensure_conversion(
3186 'main', args=['--default-eol=native'],
3188 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3189 raise Failure()
3190 rcs_lines = run_program(
3191 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3192 rcs_conv.repos)
3193 lines = run_program(
3194 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3195 conv.repos)
3196 # Compare all lines following the repository UUID:
3197 if lines[3:] != rcs_lines[3:]:
3198 raise Failure()
3201 @Cvs2SvnTestFunction
3202 def internal_co_exclude():
3203 "verify that --use-internal-co --exclude=... works"
3205 rcs_conv = ensure_conversion(
3206 'internal-co',
3207 args=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'],
3209 conv = ensure_conversion(
3210 'internal-co',
3211 args=['--exclude=BRANCH', '--default-eol=native'],
3213 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3214 raise Failure()
3215 rcs_lines = run_program(
3216 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3217 rcs_conv.repos)
3218 lines = run_program(
3219 svntest.main.svnadmin_binary, None, 'dump', '-q', '-r', '1:HEAD',
3220 conv.repos)
3221 # Compare all lines following the repository UUID:
3222 if lines[3:] != rcs_lines[3:]:
3223 raise Failure()
3226 @Cvs2SvnTestFunction
3227 def internal_co_trunk_only():
3228 "verify that --use-internal-co --trunk-only works"
3230 conv = ensure_conversion(
3231 'internal-co',
3232 args=['--trunk-only', '--default-eol=native'],
3234 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3235 raise Failure()
3238 @Cvs2SvnTestFunction
3239 def leftover_revs():
3240 "check for leftover checked-out revisions"
3242 conv = ensure_conversion(
3243 'leftover-revs',
3244 args=['--exclude=BRANCH', '--default-eol=native'],
3246 if conv.output_found(r'WARNING\: internal problem\: leftover revisions'):
3247 raise Failure()
3250 @Cvs2SvnTestFunction
3251 def requires_internal_co():
3252 "test that internal co can do more than RCS"
3253 # See issues 4, 11 for the bugs whose regression we're testing for.
3254 # Unlike in requires_cvs above, issue 29 is not covered.
3255 conv = ensure_conversion('requires-cvs')
3257 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
3259 if atsign_contents[-1:] == "@":
3260 raise Failure()
3262 if not (conv.logs[21].author == "William Lyon Phelps III" and
3263 conv.logs[20].author == "j random"):
3264 raise Failure()
3267 @Cvs2SvnTestFunction
3268 def internal_co_keywords():
3269 "test that internal co handles keywords correctly"
3270 conv_ic = ensure_conversion('internal-co-keywords',
3271 args=["--keywords-off"])
3272 conv_cvs = ensure_conversion('internal-co-keywords',
3273 args=["--use-cvs", "--keywords-off"])
3275 ko_ic = file(conv_ic.get_wc('trunk', 'dir', 'ko.txt')).read()
3276 ko_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'ko.txt')).read()
3277 kk_ic = file(conv_ic.get_wc('trunk', 'dir', 'kk.txt')).read()
3278 kk_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kk.txt')).read()
3279 kv_ic = file(conv_ic.get_wc('trunk', 'dir', 'kv.txt')).read()
3280 kv_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kv.txt')).read()
3282 if ko_ic != ko_cvs:
3283 raise Failure()
3284 if kk_ic != kk_cvs:
3285 raise Failure()
3287 # The date format changed between cvs and co ('/' instead of '-').
3288 # Accept either one:
3289 date_substitution_re = re.compile(r' ([0-9]*)-([0-9]*)-([0-9]*) ')
3290 if kv_ic != kv_cvs \
3291 and date_substitution_re.sub(r' \1/\2/\3 ', kv_ic) != kv_cvs:
3292 raise Failure()
3295 @Cvs2SvnTestFunction
3296 def timestamp_chaos():
3297 "test timestamp adjustments"
3299 conv = ensure_conversion('timestamp-chaos', args=["-v"])
3301 # The times are expressed here in UTC:
3302 times = [
3303 '2007-01-01 21:00:00', # Initial commit
3304 '2007-01-01 21:00:00', # revision 1.1 of both files
3305 '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards
3306 '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards
3307 '2007-01-01 22:00:00', # revision 1.3 of both files
3310 # Convert the times to seconds since the epoch, in UTC:
3311 times = [calendar.timegm(svn_strptime(t)) for t in times]
3313 for i in range(len(times)):
3314 if abs(conv.logs[i + 1].date - times[i]) > 0.1:
3315 raise Failure()
3318 @Cvs2SvnTestFunction
3319 def symlinks():
3320 "convert a repository that contains symlinks"
3322 # This is a test for issue #97.
3324 proj = os.path.join(test_data_dir, 'symlinks-cvsrepos', 'proj')
3325 links = [
3327 os.path.join('..', 'file.txt,v'),
3328 os.path.join(proj, 'dir1', 'file.txt,v'),
3331 'dir1',
3332 os.path.join(proj, 'dir2'),
3336 try:
3337 os.symlink
3338 except AttributeError:
3339 # Apparently this OS doesn't support symlinks, so skip test.
3340 raise svntest.Skip()
3342 try:
3343 for (src,dst) in links:
3344 os.symlink(src, dst)
3346 conv = ensure_conversion('symlinks')
3347 conv.logs[2].check('', (
3348 ('/%(trunk)s/proj', 'A'),
3349 ('/%(trunk)s/proj/file.txt', 'A'),
3350 ('/%(trunk)s/proj/dir1', 'A'),
3351 ('/%(trunk)s/proj/dir1/file.txt', 'A'),
3352 ('/%(trunk)s/proj/dir2', 'A'),
3353 ('/%(trunk)s/proj/dir2/file.txt', 'A'),
3355 finally:
3356 for (src,dst) in links:
3357 os.remove(dst)
3360 @Cvs2SvnTestFunction
3361 def empty_trunk_path():
3362 "allow --trunk to be empty if --trunk-only"
3364 # This is a test for issue #53.
3366 conv = ensure_conversion(
3367 'main', args=['--trunk-only', '--trunk='],
3371 @Cvs2SvnTestFunction
3372 def preferred_parent_cycle():
3373 "handle a cycle in branch parent preferences"
3375 conv = ensure_conversion('preferred-parent-cycle')
3378 @Cvs2SvnTestFunction
3379 def branch_from_empty_dir():
3380 "branch from an empty directory"
3382 conv = ensure_conversion('branch-from-empty-dir')
3385 @Cvs2SvnTestFunction
3386 def trunk_readd():
3387 "add a file on a branch then on trunk"
3389 conv = ensure_conversion('trunk-readd')
3392 @Cvs2SvnTestFunction
3393 def branch_from_deleted_1_1():
3394 "branch from a 1.1 revision that will be deleted"
3396 conv = ensure_conversion('branch-from-deleted-1-1')
3397 conv.logs[5].check('Adding b.txt:1.1.2.1', (
3398 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3400 conv.logs[6].check('Adding b.txt:1.1.4.1', (
3401 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3403 conv.logs[7].check('Adding b.txt:1.2', (
3404 ('/%(trunk)s/proj/b.txt', 'A'),
3407 conv.logs[8].check('Adding c.txt:1.1.2.1', (
3408 ('/%(branches)s/BRANCH1/proj/c.txt', 'A'),
3410 conv.logs[9].check('Adding c.txt:1.1.4.1', (
3411 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3415 @Cvs2SvnTestFunction
3416 def add_on_branch():
3417 "add a file on a branch using newer CVS"
3419 conv = ensure_conversion('add-on-branch')
3420 conv.logs[6].check('Adding b.txt:1.1', (
3421 ('/%(trunk)s/proj/b.txt', 'A'),
3423 conv.logs[7].check('Adding b.txt:1.1.2.2', (
3424 ('/%(branches)s/BRANCH1/proj/b.txt', 'A'),
3426 conv.logs[8].check('Adding c.txt:1.1', (
3427 ('/%(trunk)s/proj/c.txt', 'A'),
3429 conv.logs[9].check('Removing c.txt:1.2', (
3430 ('/%(trunk)s/proj/c.txt', 'D'),
3432 conv.logs[10].check('Adding c.txt:1.2.2.2', (
3433 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3435 conv.logs[11].check('Adding d.txt:1.1', (
3436 ('/%(trunk)s/proj/d.txt', 'A'),
3438 conv.logs[12].check('Adding d.txt:1.1.2.2', (
3439 ('/%(branches)s/BRANCH3/proj/d.txt', 'A'),
3443 @Cvs2SvnTestFunction
3444 def main_git():
3445 "test output in git-fast-import format"
3447 # Note: To test importing into git, do
3449 # ./run-tests <test-number>
3450 # rm -rf .git
3451 # git-init
3452 # cat cvs2svn-tmp/{blobfile,dumpfile}.out | git fast-import
3454 # Or, to load the dumpfiles separately:
3456 # cat cvs2svn-tmp/git-blob.dat \
3457 # | git fast-import --export-marks=cvs2svn-tmp/git-marks.dat
3458 # cat cvs2svn-tmp/git-dump.dat \
3459 # | git fast-import --import-marks=cvs2svn-tmp/git-marks.dat
3461 # Then use "gitk --all", "git log", etc. to test the contents of the
3462 # repository.
3464 # We don't have the infrastructure to check that the resulting git
3465 # repository is correct, so we just check that the conversion runs
3466 # to completion:
3467 conv = GitConversion('main', None, [
3468 '--blobfile=cvs2svn-tmp/blobfile.out',
3469 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3470 '--username=cvs2git',
3471 'test-data/main-cvsrepos',
3475 @Cvs2SvnTestFunction
3476 def main_git2():
3477 "test cvs2git --use-external-blob-generator option"
3479 # See comment in main_git() for more information.
3481 conv = GitConversion('main', None, [
3482 '--use-external-blob-generator',
3483 '--blobfile=cvs2svn-tmp/blobfile.out',
3484 '--dumpfile=cvs2svn-tmp/dumpfile.out',
3485 '--username=cvs2git',
3486 'test-data/main-cvsrepos',
3490 @Cvs2SvnTestFunction
3491 def git_options():
3492 "test cvs2git using options file"
3494 conv = GitConversion('main', None, [], options_file='cvs2git.options')
3497 @Cvs2SvnTestFunction
3498 def main_hg():
3499 "output in git-fast-import format with inline data"
3501 # The output should be suitable for import by Mercurial.
3503 # We don't have the infrastructure to check that the resulting
3504 # Mercurial repository is correct, so we just check that the
3505 # conversion runs to completion:
3506 conv = GitConversion('main', None, [], options_file='cvs2hg.options')
3509 @Cvs2SvnTestFunction
3510 def invalid_symbol():
3511 "a symbol with the incorrect format"
3513 conv = ensure_conversion('invalid-symbol')
3514 if not conv.output_found(
3515 r".*branch 'SYMBOL' references invalid revision 1$"
3517 raise Failure()
3520 @Cvs2SvnTestFunction
3521 def invalid_symbol_ignore():
3522 "ignore a symbol using a SymbolMapper"
3524 conv = ensure_conversion(
3525 'invalid-symbol', options_file='cvs2svn-ignore.options'
3529 @Cvs2SvnTestFunction
3530 def invalid_symbol_ignore2():
3531 "ignore a symbol using an IgnoreSymbolTransform"
3533 conv = ensure_conversion(
3534 'invalid-symbol', options_file='cvs2svn-ignore2.options'
3538 class EOLVariants(Cvs2SvnTestCase):
3539 "handle various --eol-style options"
3541 eol_style_strings = {
3542 'LF' : '\n',
3543 'CR' : '\r',
3544 'CRLF' : '\r\n',
3545 'native' : '\n',
3548 def __init__(self, eol_style):
3549 self.eol_style = eol_style
3550 self.dumpfile = 'eol-variants-%s.dump' % (self.eol_style,)
3551 Cvs2SvnTestCase.__init__(
3552 self, 'eol-variants', variant=self.eol_style,
3553 dumpfile=self.dumpfile,
3554 args=[
3555 '--default-eol=%s' % (self.eol_style,),
3559 def run(self, sbox):
3560 conv = self.ensure_conversion()
3561 dump_contents = open(conv.dumpfile, 'rb').read()
3562 expected_text = self.eol_style_strings[self.eol_style].join(
3563 ['line 1', 'line 2', '\n\n']
3565 if not dump_contents.endswith(expected_text):
3566 raise Failure()
3569 @Cvs2SvnTestFunction
3570 def no_revs_file():
3571 "handle a file with no revisions (issue #80)"
3573 conv = ensure_conversion('no-revs-file')
3576 @Cvs2SvnTestFunction
3577 def mirror_keyerror_test():
3578 "a case that gave KeyError in SVNRepositoryMirror"
3580 conv = ensure_conversion('mirror-keyerror')
3583 @Cvs2SvnTestFunction
3584 def exclude_ntdb_test():
3585 "exclude a non-trunk default branch"
3587 symbol_info_file = os.path.join(tmp_dir, 'exclude-ntdb-symbol-info.txt')
3588 conv = ensure_conversion(
3589 'exclude-ntdb',
3590 args=[
3591 '--write-symbol-info=%s' % (symbol_info_file,),
3592 '--exclude=branch3',
3593 '--exclude=tag3',
3594 '--exclude=vendortag3',
3595 '--exclude=vendorbranch',
3600 @Cvs2SvnTestFunction
3601 def mirror_keyerror2_test():
3602 "a case that gave KeyError in RepositoryMirror"
3604 conv = ensure_conversion('mirror-keyerror2')
3607 @Cvs2SvnTestFunction
3608 def mirror_keyerror3_test():
3609 "a case that gave KeyError in RepositoryMirror"
3611 conv = ensure_conversion('mirror-keyerror3')
3614 @Cvs2SvnTestFunction
3615 def add_cvsignore_to_branch_test():
3616 "check adding .cvsignore to an existing branch"
3618 # This a test for issue #122.
3620 conv = ensure_conversion('add-cvsignore-to-branch')
3621 wc_tree = conv.get_wc_tree()
3622 trunk_props = props_for_path(wc_tree, 'trunk/dir')
3623 if trunk_props['svn:ignore'] != '*.o\n\n':
3624 raise Failure()
3626 branch_props = props_for_path(wc_tree, 'branches/BRANCH/dir')
3627 if branch_props['svn:ignore'] != '*.o\n\n':
3628 raise Failure()
3631 @Cvs2SvnTestFunction
3632 def missing_deltatext():
3633 "a revision's deltatext is missing"
3635 # This is a type of RCS file corruption that has been observed.
3636 conv = ensure_conversion(
3637 'missing-deltatext',
3638 error_re=(
3639 r"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4"
3644 @Cvs2SvnTestFunction
3645 def transform_unlabeled_branch_name():
3646 "transform name of unlabeled branch"
3648 conv = ensure_conversion(
3649 'unlabeled-branch',
3650 args=[
3651 '--symbol-transform=unlabeled-1.1.4:BRANCH2',
3656 @Cvs2SvnTestFunction
3657 def ignore_unlabeled_branch():
3658 "ignoring an unlabeled branch is not allowed"
3660 conv = ensure_conversion(
3661 'unlabeled-branch',
3662 options_file='cvs2svn-ignore.options',
3663 error_re=(
3664 r"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' "
3665 r"in \'.*\' contains commits"
3670 @Cvs2SvnTestFunction
3671 def unlabeled_branch_name_collision():
3672 "transform branch to same name as unlabeled branch"
3674 conv = ensure_conversion(
3675 'unlabeled-branch',
3676 args=[
3677 '--symbol-transform=unlabeled-1.1.4:BRANCH',
3679 error_re=(
3680 r"ERROR\: Symbol name \'BRANCH\' is already used"
3685 @Cvs2SvnTestFunction
3686 def collision_with_unlabeled_branch_name():
3687 "transform unlabeled branch to same name as branch"
3689 conv = ensure_conversion(
3690 'unlabeled-branch',
3691 args=[
3692 '--symbol-transform=BRANCH:unlabeled-1.1.4',
3694 error_re=(
3695 r"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used"
3700 @Cvs2SvnTestFunction
3701 def many_deletes():
3702 "a repo with many removable dead revisions"
3704 conv = ensure_conversion('many-deletes')
3705 conv.logs[5].check('Add files on BRANCH', (
3706 ('/%(branches)s/BRANCH/proj/b.txt', 'A'),
3708 conv.logs[6].check('Add files on BRANCH2', (
3709 ('/%(branches)s/BRANCH2/proj/b.txt', 'A'),
3710 ('/%(branches)s/BRANCH2/proj/c.txt', 'A'),
3711 ('/%(branches)s/BRANCH2/proj/d.txt', 'A'),
3715 cvs_description = Cvs2SvnPropertiesTestCase(
3716 'main',
3717 doc='test handling of CVS file descriptions',
3718 props_to_test=['cvs:description'],
3719 expected_props=[
3720 ('trunk/proj/default', ['This is an example file description.']),
3721 ('trunk/proj/sub1/default', [None]),
3725 @Cvs2SvnTestFunction
3726 def include_empty_directories():
3727 "test --include-empty-directories option"
3729 conv = ensure_conversion(
3730 'empty-directories', args=['--include-empty-directories'],
3732 conv.logs[1].check('Standard project directories', (
3733 ('/%(trunk)s', 'A'),
3734 ('/%(branches)s', 'A'),
3735 ('/%(tags)s', 'A'),
3736 ('/%(trunk)s/root-empty-directory', 'A'),
3737 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3739 conv.logs[3].check('Add b.txt.', (
3740 ('/%(trunk)s/direct', 'A'),
3741 ('/%(trunk)s/direct/b.txt', 'A'),
3742 ('/%(trunk)s/direct/empty-directory', 'A'),
3743 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3745 conv.logs[4].check('Add c.txt.', (
3746 ('/%(trunk)s/indirect', 'A'),
3747 ('/%(trunk)s/indirect/subdirectory', 'A'),
3748 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3749 ('/%(trunk)s/indirect/empty-directory', 'A'),
3750 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3752 conv.logs[5].check('Remove b.txt', (
3753 ('/%(trunk)s/direct', 'D'),
3755 conv.logs[6].check('Remove c.txt', (
3756 ('/%(trunk)s/indirect', 'D'),
3758 conv.logs[7].check('Re-add b.txt.', (
3759 ('/%(trunk)s/direct', 'A'),
3760 ('/%(trunk)s/direct/b.txt', 'A'),
3761 ('/%(trunk)s/direct/empty-directory', 'A'),
3762 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3764 conv.logs[8].check('Re-add c.txt.', (
3765 ('/%(trunk)s/indirect', 'A'),
3766 ('/%(trunk)s/indirect/subdirectory', 'A'),
3767 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3768 ('/%(trunk)s/indirect/empty-directory', 'A'),
3769 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3771 conv.logs[9].check('This commit was manufactured', (
3772 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3774 conv.logs[10].check('This commit was manufactured', (
3775 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3777 conv.logs[11].check('Import d.txt.', (
3778 ('/%(branches)s/VENDORBRANCH', 'A'),
3779 ('/%(branches)s/VENDORBRANCH/import', 'A'),
3780 ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'),
3781 ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'),
3782 ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory',
3783 'A'),
3784 ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'),
3785 ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory',
3786 'A'),
3788 conv.logs[12].check('This commit was generated', (
3789 ('/%(trunk)s/import', 'A'),
3790 ('/%(trunk)s/import/d.txt '
3791 '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'),
3792 ('/%(trunk)s/import/empty-directory', 'A'),
3793 ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'),
3797 @Cvs2SvnTestFunction
3798 def include_empty_directories_no_prune():
3799 "test --include-empty-directories with --no-prune"
3801 conv = ensure_conversion(
3802 'empty-directories', args=['--include-empty-directories', '--no-prune'],
3804 conv.logs[1].check('Standard project directories', (
3805 ('/%(trunk)s', 'A'),
3806 ('/%(branches)s', 'A'),
3807 ('/%(tags)s', 'A'),
3808 ('/%(trunk)s/root-empty-directory', 'A'),
3809 ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'),
3811 conv.logs[3].check('Add b.txt.', (
3812 ('/%(trunk)s/direct', 'A'),
3813 ('/%(trunk)s/direct/b.txt', 'A'),
3814 ('/%(trunk)s/direct/empty-directory', 'A'),
3815 ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'),
3817 conv.logs[4].check('Add c.txt.', (
3818 ('/%(trunk)s/indirect', 'A'),
3819 ('/%(trunk)s/indirect/subdirectory', 'A'),
3820 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3821 ('/%(trunk)s/indirect/empty-directory', 'A'),
3822 ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'),
3824 conv.logs[5].check('Remove b.txt', (
3825 ('/%(trunk)s/direct/b.txt', 'D'),
3827 conv.logs[6].check('Remove c.txt', (
3828 ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'),
3830 conv.logs[7].check('Re-add b.txt.', (
3831 ('/%(trunk)s/direct/b.txt', 'A'),
3833 conv.logs[8].check('Re-add c.txt.', (
3834 ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'),
3836 conv.logs[9].check('This commit was manufactured', (
3837 ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'),
3839 conv.logs[10].check('This commit was manufactured', (
3840 ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'),
3844 @Cvs2SvnTestFunction
3845 def exclude_symbol_default():
3846 "test 'exclude' symbol default"
3848 conv = ensure_conversion(
3849 'symbol-mess', args=['--symbol-default=exclude'])
3850 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
3851 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
3852 raise Failure()
3853 if conv.path_exists('tags', 'MOSTLY_TAG') \
3854 or conv.path_exists('branches', 'MOSTLY_TAG'):
3855 raise Failure()
3858 @Cvs2SvnTestFunction
3859 def add_on_branch2():
3860 "another add-on-branch test case"
3862 conv = ensure_conversion('add-on-branch2')
3863 if len(conv.logs) != 2:
3864 raise Failure()
3865 conv.logs[2].check('add file on branch', (
3866 ('/%(branches)s/BRANCH', 'A'),
3867 ('/%(branches)s/BRANCH/file1', 'A'),
3871 ########################################################################
3872 # Run the tests
3874 # list all tests here, starting with None:
3875 test_list = [
3876 None,
3877 # 1:
3878 show_usage,
3879 cvs2svn_manpage,
3880 cvs2git_manpage,
3881 XFail(cvs2hg_manpage),
3882 attr_exec,
3883 space_fname,
3884 two_quick,
3885 PruneWithCare(),
3886 PruneWithCare(variant=1, trunk='a', branches='b', tags='c'),
3887 # 10:
3888 PruneWithCare(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3889 PruneWithCare(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3890 interleaved_commits,
3891 simple_commits,
3892 SimpleTags(),
3893 SimpleTags(variant=1, trunk='a', branches='b', tags='c'),
3894 SimpleTags(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3895 SimpleTags(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3896 simple_branch_commits,
3897 mixed_time_tag,
3898 # 20:
3899 mixed_time_branch_with_added_file,
3900 mixed_commit,
3901 split_time_branch,
3902 bogus_tag,
3903 overlapping_branch,
3904 PhoenixBranch(),
3905 PhoenixBranch(variant=1, trunk='a/1', branches='b/1', tags='c/1'),
3906 ctrl_char_in_log,
3907 overdead,
3908 NoTrunkPrune(),
3909 # 30:
3910 NoTrunkPrune(variant=1, trunk='a', branches='b', tags='c'),
3911 NoTrunkPrune(variant=2, trunk='a/1', branches='b/1', tags='c/1'),
3912 NoTrunkPrune(variant=3, trunk='a/1', branches='a/2', tags='a/3'),
3913 double_delete,
3914 split_branch,
3915 resync_misgroups,
3916 TaggedBranchAndTrunk(),
3917 TaggedBranchAndTrunk(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3918 enroot_race,
3919 enroot_race_obo,
3920 # 40:
3921 BranchDeleteFirst(),
3922 BranchDeleteFirst(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3923 nonascii_filenames,
3924 UnicodeAuthor(
3925 warning_expected=1),
3926 UnicodeAuthor(
3927 warning_expected=0,
3928 variant='encoding', args=['--encoding=utf_8']),
3929 UnicodeAuthor(
3930 warning_expected=0,
3931 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
3932 UnicodeLog(
3933 warning_expected=1),
3934 UnicodeLog(
3935 warning_expected=0,
3936 variant='encoding', args=['--encoding=utf_8']),
3937 UnicodeLog(
3938 warning_expected=0,
3939 variant='fallback-encoding', args=['--fallback-encoding=utf_8']),
3940 vendor_branch_sameness,
3941 # 50:
3942 vendor_branch_trunk_only,
3943 default_branches,
3944 default_branches_trunk_only,
3945 default_branch_and_1_2,
3946 compose_tag_three_sources,
3947 pass5_when_to_fill,
3948 PeerPathPruning(),
3949 PeerPathPruning(variant=1, trunk='a/1', branches='a/2', tags='a/3'),
3950 EmptyTrunk(),
3951 EmptyTrunk(variant=1, trunk='a', branches='b', tags='c'),
3952 # 60:
3953 EmptyTrunk(variant=2, trunk='a/1', branches='a/2', tags='a/3'),
3954 no_spurious_svn_commits,
3955 invalid_closings_on_trunk,
3956 individual_passes,
3957 resync_bug,
3958 branch_from_default_branch,
3959 file_in_attic_too,
3960 retain_file_in_attic_too,
3961 symbolic_name_filling_guide,
3962 eol_mime1,
3963 # 70:
3964 eol_mime2,
3965 eol_mime3,
3966 eol_mime4,
3967 cvs_revnums_off,
3968 cvs_revnums_on,
3969 keywords,
3970 ignore,
3971 requires_cvs,
3972 questionable_branch_names,
3973 questionable_tag_names,
3974 # 80:
3975 revision_reorder_bug,
3976 exclude,
3977 vendor_branch_delete_add,
3978 resync_pass2_pull_forward,
3979 native_eol,
3980 double_fill,
3981 XFail(double_fill2),
3982 resync_pass2_push_backward,
3983 double_add,
3984 bogus_branch_copy,
3985 # 90:
3986 nested_ttb_directories,
3987 auto_props_ignore_case,
3988 ctrl_char_in_filename,
3989 commit_dependencies,
3990 show_help_passes,
3991 multiple_tags,
3992 multiply_defined_symbols,
3993 multiply_defined_symbols_renamed,
3994 multiply_defined_symbols_ignored,
3995 repeatedly_defined_symbols,
3996 # 100:
3997 double_branch_delete,
3998 symbol_mismatches,
3999 overlook_symbol_mismatches,
4000 force_symbols,
4001 commit_blocks_tags,
4002 blocked_excludes,
4003 unblock_blocked_excludes,
4004 regexp_force_symbols,
4005 heuristic_symbol_default,
4006 branch_symbol_default,
4007 # 110:
4008 tag_symbol_default,
4009 symbol_transform,
4010 write_symbol_info,
4011 symbol_hints,
4012 parent_hints,
4013 parent_hints_invalid,
4014 parent_hints_wildcards,
4015 path_hints,
4016 issue_99,
4017 issue_100,
4018 # 120:
4019 issue_106,
4020 options_option,
4021 multiproject,
4022 crossproject,
4023 tag_with_no_revision,
4024 delete_cvsignore,
4025 repeated_deltatext,
4026 nasty_graphs,
4027 XFail(tagging_after_delete),
4028 crossed_branches,
4029 # 130:
4030 file_directory_conflict,
4031 attic_directory_conflict,
4032 internal_co,
4033 internal_co_exclude,
4034 internal_co_trunk_only,
4035 internal_co_keywords,
4036 leftover_revs,
4037 requires_internal_co,
4038 timestamp_chaos,
4039 symlinks,
4040 # 140:
4041 empty_trunk_path,
4042 preferred_parent_cycle,
4043 branch_from_empty_dir,
4044 trunk_readd,
4045 branch_from_deleted_1_1,
4046 add_on_branch,
4047 main_git,
4048 main_git2,
4049 git_options,
4050 main_hg,
4051 # 150:
4052 invalid_symbol,
4053 invalid_symbol_ignore,
4054 invalid_symbol_ignore2,
4055 EOLVariants('LF'),
4056 EOLVariants('CR'),
4057 EOLVariants('CRLF'),
4058 EOLVariants('native'),
4059 no_revs_file,
4060 mirror_keyerror_test,
4061 exclude_ntdb_test,
4062 # 160:
4063 mirror_keyerror2_test,
4064 mirror_keyerror3_test,
4065 XFail(add_cvsignore_to_branch_test),
4066 missing_deltatext,
4067 transform_unlabeled_branch_name,
4068 ignore_unlabeled_branch,
4069 unlabeled_branch_name_collision,
4070 collision_with_unlabeled_branch_name,
4071 many_deletes,
4072 cvs_description,
4073 # 170:
4074 include_empty_directories,
4075 include_empty_directories_no_prune,
4076 exclude_symbol_default,
4077 add_on_branch2,
4080 if __name__ == '__main__':
4082 # Configure the environment for reproducable output from svn, etc.
4083 os.environ["LC_ALL"] = "C"
4085 # Unfortunately, there is no way under Windows to make Subversion
4086 # think that the local time zone is UTC, so we just work in the
4087 # local time zone.
4089 # The Subversion test suite code assumes it's being invoked from
4090 # within a working copy of the Subversion sources, and tries to use
4091 # the binaries in that tree. Since the cvs2svn tree never contains
4092 # a Subversion build, we just use the system's installed binaries.
4093 svntest.main.svn_binary = svn_binary
4094 svntest.main.svnlook_binary = svnlook_binary
4095 svntest.main.svnadmin_binary = svnadmin_binary
4096 svntest.main.svnversion_binary = svnversion_binary
4098 run_tests(test_list)
4099 # NOTREACHED
4102 ### End of file.