Since, with the addition of cvs2svn_lib, the value of $LastChangedRevision$ is
[cvs2svn.git] / run-tests.py
blobf27c9cfefb33138944cde7a2fad84b0d4e88376a
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-2004 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
46 # Make sure this Python is recent enough.
47 if sys.hexversion < 0x02020000:
48 sys.stderr.write("error: Python 2.2 or higher required, "
49 "see www.python.org.\n")
50 sys.exit(1)
52 # This script needs to run in the correct directory. Make sure we're there.
53 if not (os.path.exists('cvs2svn') and os.path.exists('test-data')):
54 sys.stderr.write("error: I need to be run in the directory containing "
55 "'cvs2svn' and 'test-data'.\n")
56 sys.exit(1)
58 # Load the Subversion test framework.
59 import svntest
61 # Abbreviations
62 Skip = svntest.testcase.Skip
63 XFail = svntest.testcase.XFail
65 cvs2svn = os.path.abspath('cvs2svn')
67 # We use the installed svn and svnlook binaries, instead of using
68 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
69 # behavior -- or even existence -- of local builds shouldn't affect
70 # the cvs2svn test suite.
71 svn = 'svn'
72 svnlook = 'svnlook'
74 test_data_dir = 'test-data'
75 tmp_dir = 'tmp'
78 #----------------------------------------------------------------------
79 # Helpers.
80 #----------------------------------------------------------------------
83 class RunProgramException:
84 pass
86 class MissingErrorException:
87 pass
89 def run_program(program, error_re, *varargs):
90 """Run PROGRAM with VARARGS, return stdout as a list of lines.
91 If there is any stderr and ERROR_RE is None, raise
92 RunProgramException, and print the stderr lines if
93 svntest.main.verbose_mode is true.
95 If ERROR_RE is not None, it is a string regular expression that must
96 match some line of stderr. If it fails to match, raise
97 MissingErrorExpection."""
98 out, err = svntest.main.run_command(program, 1, 0, *varargs)
99 if err:
100 if error_re:
101 for line in err:
102 if re.match(error_re, line):
103 return out
104 raise MissingErrorException
105 else:
106 if svntest.main.verbose_mode:
107 print '\n%s said:\n' % program
108 for line in err:
109 print ' ' + line,
110 print
111 raise RunProgramException
112 return out
115 def run_cvs2svn(error_re, *varargs):
116 """Run cvs2svn with VARARGS, return stdout as a list of lines.
117 If there is any stderr and ERROR_RE is None, raise
118 RunProgramException, and print the stderr lines if
119 svntest.main.verbose_mode is true.
121 If ERROR_RE is not None, it is a string regular expression that must
122 match some line of stderr. If it fails to match, raise
123 MissingErrorException."""
124 # Use the same python that is running this script
125 return run_program(sys.executable, error_re, cvs2svn, *varargs)
126 # On Windows, for an unknown reason, the cmd.exe process invoked by
127 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
128 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
129 # this. Therefore, the redirection of the output to the .s-revs file fails.
130 # We no longer use the problematic invocation on any system, but this
131 # comment remains to warn about this problem.
134 def run_svn(*varargs):
135 """Run svn with VARARGS; return stdout as a list of lines.
136 If there is any stderr, raise RunProgramException, and print the
137 stderr lines if svntest.main.verbose_mode is true."""
138 return run_program(svn, None, *varargs)
141 def repos_to_url(path_to_svn_repos):
142 """This does what you think it does."""
143 rpath = os.path.abspath(path_to_svn_repos)
144 if rpath[0] != '/':
145 rpath = '/' + rpath
146 return 'file://%s' % rpath.replace(os.sep, '/')
148 if hasattr(time, 'strptime'):
149 def svn_strptime(timestr):
150 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
151 else:
152 # This is for Python earlier than 2.3 on Windows
153 _re_rev_date = re.compile(r'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)')
154 def svn_strptime(timestr):
155 matches = _re_rev_date.match(timestr).groups()
156 return tuple(map(int, matches)) + (0, 1, -1)
158 class Log:
159 def __init__(self, revision, author, date, symbols):
160 self.revision = revision
161 self.author = author
163 # Internally, we represent the date as seconds since epoch (UTC).
164 # Since standard subversion log output shows dates in localtime
166 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
168 # and time.mktime() converts from localtime, it all works out very
169 # happily.
170 self.date = time.mktime(svn_strptime(date[0:19]))
172 # The following symbols are used for string interpolation when
173 # checking paths:
174 self.symbols = symbols
176 # The changed paths will be accumulated later, as log data is read.
177 # Keys here are paths such as '/trunk/foo/bar', values are letter
178 # codes such as 'M', 'A', and 'D'.
179 self.changed_paths = { }
181 # The msg will be accumulated later, as log data is read.
182 self.msg = ''
184 def absorb_changed_paths(self, out):
185 'Read changed paths from OUT into self, until no more.'
186 while 1:
187 line = out.readline()
188 if len(line) == 1: return
189 line = line[:-1]
190 op_portion = line[3:4]
191 path_portion = line[5:]
192 # If we're running on Windows we get backslashes instead of
193 # forward slashes.
194 path_portion = path_portion.replace('\\', '/')
195 # # We could parse out history information, but currently we
196 # # just leave it in the path portion because that's how some
197 # # tests expect it.
199 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
200 # if m:
201 # path_portion = m.group(1)
202 self.changed_paths[path_portion] = op_portion
204 def __cmp__(self, other):
205 return cmp(self.revision, other.revision) or \
206 cmp(self.author, other.author) or cmp(self.date, other.date) or \
207 cmp(self.changed_paths, other.changed_paths) or \
208 cmp(self.msg, other.msg)
210 def get_path_op(self, path):
211 """Return the operator for the change involving PATH.
213 PATH is allowed to include string interpolation directives (e.g.,
214 '%(trunk)s'), which are interpolated against self.symbols. Return
215 None if there is no record for PATH."""
216 return self.changed_paths.get(path % self.symbols)
218 def check_msg(self, msg):
219 """Verify that this Log's message starts with the specified MSG."""
220 if self.msg.find(msg) != 0:
221 raise svntest.Failure(
222 "Revision %d log message was:\n%s\n\n"
223 "It should have begun with:\n%s\n\n"
224 % (self.revision, self.msg, msg,)
227 def check_change(self, path, op):
228 """Verify that this Log includes a change for PATH with operator OP.
230 PATH is allowed to include string interpolation directives (e.g.,
231 '%(trunk)s'), which are interpolated against self.symbols."""
233 path = path % self.symbols
234 found_op = self.changed_paths.get(path, None)
235 if found_op is None:
236 raise svntest.Failure(
237 "Revision %d does not include change for path %s "
238 "(it should have been %s).\n"
239 % (self.revision, path, op,)
241 if found_op != op:
242 raise svntest.Failure(
243 "Revision %d path %s had op %s (it should have been %s)\n"
244 % (self.revision, path, found_op, op,)
247 def check_changes(self, changed_paths):
248 """Verify that this Log has precisely the CHANGED_PATHS specified.
250 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
251 strings are allowed to include string interpolation directives
252 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
254 cp = {}
255 for (path, op) in changed_paths:
256 cp[path % self.symbols] = op
258 if self.changed_paths != cp:
259 raise svntest.Failure(
260 "Revision %d changed paths list was:\n%s\n\n"
261 "It should have been:\n%s\n\n"
262 % (self.revision, self.changed_paths, cp,)
265 def check(self, msg, changed_paths):
266 """Verify that this Log has the MSG and CHANGED_PATHS specified.
268 Convenience function to check two things at once. MSG is passed
269 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
271 self.check_msg(msg)
272 self.check_changes(changed_paths)
275 def parse_log(svn_repos, symbols):
276 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
278 Initialize the Logs' symbols with SYMBOLS."""
280 class LineFeeder:
281 'Make a list of lines behave like an open file handle.'
282 def __init__(self, lines):
283 self.lines = lines
284 def readline(self):
285 if len(self.lines) > 0:
286 return self.lines.pop(0)
287 else:
288 return None
290 def absorb_message_body(out, num_lines, log):
291 'Read NUM_LINES of log message body from OUT into Log item LOG.'
292 i = 0
293 while i < num_lines:
294 line = out.readline()
295 log.msg += line
296 i += 1
298 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
299 '(?P<author>[^\|]+) \| '
300 '(?P<date>[^\|]+) '
301 '\| (?P<lines>[0-9]+) (line|lines)$')
303 log_separator = '-' * 72
305 logs = { }
307 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
309 while 1:
310 this_log = None
311 line = out.readline()
312 if not line: break
313 line = line[:-1]
315 if line.find(log_separator) == 0:
316 line = out.readline()
317 if not line: break
318 line = line[:-1]
319 m = log_start_re.match(line)
320 if m:
321 this_log = Log(
322 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
323 line = out.readline()
324 if not line.find('Changed paths:') == 0:
325 print 'unexpected log output (missing changed paths)'
326 print "Line: '%s'" % line
327 sys.exit(1)
328 this_log.absorb_changed_paths(out)
329 absorb_message_body(out, int(m.group('lines')), this_log)
330 logs[this_log.revision] = this_log
331 elif len(line) == 0:
332 break # We've reached the end of the log output.
333 else:
334 print 'unexpected log output (missing revision line)'
335 print "Line: '%s'" % line
336 sys.exit(1)
337 else:
338 print 'unexpected log output (missing log separator)'
339 print "Line: '%s'" % line
340 sys.exit(1)
342 return logs
345 def erase(path):
346 """Unconditionally remove PATH and its subtree, if any. PATH may be
347 non-existent, a file or symlink, or a directory."""
348 if os.path.isdir(path):
349 svntest.main.safe_rmtree(path)
350 elif os.path.exists(path):
351 os.remove(path)
354 def sym_log_msg(symbolic_name, is_tag=None):
355 """Return the expected log message for a cvs2svn-synthesized revision
356 creating branch or tag SYMBOLIC_NAME."""
357 # This is a copy-paste of part of cvs2svn's make_revision_props
358 if is_tag:
359 type = 'tag'
360 else:
361 type = 'branch'
363 # In Python 2.2.3, we could use textwrap.fill(). Oh well :-).
364 if len(symbolic_name) >= 13:
365 space_or_newline = '\n'
366 else:
367 space_or_newline = ' '
369 log = "This commit was manufactured by cvs2svn to create %s%s'%s'." \
370 % (type, space_or_newline, symbolic_name)
372 return log
375 def make_conversion_id(name, args, passbypass):
376 """Create an identifying tag for a conversion.
378 The return value can also be used as part of a filesystem path.
380 NAME is the name of the CVS repository.
382 ARGS are the extra arguments to be passed to cvs2svn.
384 PASSBYPASS is a boolean indicating whether the conversion is to be
385 run one pass at a time.
387 The 1-to-1 mapping between cvs2svn command parameters and
388 conversion_ids allows us to avoid running the same conversion more
389 than once, when multiple tests use exactly the same conversion."""
391 conv_id = name
393 _win32_fname_mapping = { '/': '_sl_', '\\': '_bs_', ':': '_co_',
394 '*': '_st_', '?': '_qm_', '"': '_qq_',
395 '<': '_lt_', '>': '_gt_', '|': '_pi_', }
396 for arg in args:
397 # Replace some characters that Win32 isn't happy about having in a
398 # filename (which was causing the eol_mime test to fail).
399 sanitized_arg = arg
400 for a, b in _win32_fname_mapping.items():
401 sanitized_arg = sanitized_arg.replace(a, b)
402 conv_id = conv_id + sanitized_arg
404 if passbypass:
405 conv_id = conv_id + '-passbypass'
407 return conv_id
410 class Conversion:
411 """A record of a cvs2svn conversion.
413 Fields:
415 conv_id -- the conversion id for this Conversion.
417 name -- a one-word name indicating the involved repositories.
419 repos -- the path to the svn repository.
421 logs -- a dictionary of Log instances, as returned by parse_log().
423 symbols -- a dictionary of symbols used for string interpolation
424 in path names.
426 _wc -- the basename of the svn working copy (within tmp_dir).
428 _wc_path -- the path to the svn working copy, if it has already
429 been created; otherwise, None. (The working copy is created
430 lazily when get_wc() is called.)
432 _wc_tree -- the tree built from the svn working copy, if it has
433 already been created; otherwise, None. The tree is created
434 lazily when get_wc_tree() is called.)
436 _svnrepos -- the basename of the svn repository (within tmp_dir)."""
438 def __init__(self, conv_id, name, error_re, passbypass, symbols, args):
439 self.conv_id = conv_id
440 self.name = name
441 self.symbols = symbols
442 if not os.path.isdir(tmp_dir):
443 os.mkdir(tmp_dir)
445 cvsrepos = os.path.join('..', test_data_dir, '%s-cvsrepos' % self.name)
447 saved_wd = os.getcwd()
448 try:
449 os.chdir(tmp_dir)
451 self._svnrepos = '%s-svnrepos' % self.conv_id
452 self.repos = os.path.join(tmp_dir, self._svnrepos)
453 self._wc = '%s-wc' % self.conv_id
454 self._wc_path = None
455 self._wc_tree = None
457 # Clean up from any previous invocations of this script.
458 erase(self._svnrepos)
459 erase(self._wc)
461 try:
462 args.extend( [ '--bdb-txn-nosync', '-s', self._svnrepos, cvsrepos ] )
463 if passbypass:
464 for p in range(1, 10):
465 run_cvs2svn(error_re, '-p', str(p), *args)
466 else:
467 run_cvs2svn(error_re, *args)
468 except RunProgramException:
469 raise svntest.Failure
470 except MissingErrorException:
471 raise svntest.Failure("Test failed because no error matched '%s'"
472 % error_re)
474 if not os.path.isdir(self._svnrepos):
475 raise svntest.Failure("Repository not created: '%s'"
476 % os.path.join(os.getcwd(), self._svnrepos))
478 self.logs = parse_log(self._svnrepos, self.symbols)
479 finally:
480 os.chdir(saved_wd)
482 def find_tag_log(self, tagname):
483 """Search LOGS for a log message containing 'TAGNAME' and return the
484 log in which it was found."""
485 for i in xrange(len(self.logs), 0, -1):
486 if self.logs[i].msg.find("'"+tagname+"'") != -1:
487 return self.logs[i]
488 raise ValueError("Tag %s not found in logs" % tagname)
490 def get_wc(self, *args):
491 """Return the path to the svn working copy, or a path within the WC.
493 If a working copy has not been created yet, create it now.
495 If ARGS are specified, then they should be strings that form
496 fragments of a path within the WC. They are joined using
497 os.path.join() and appended to the WC path."""
499 if self._wc_path is None:
500 saved_wd = os.getcwd()
501 try:
502 os.chdir(tmp_dir)
503 run_svn('co', repos_to_url(self._svnrepos), self._wc)
504 self._wc_path = os.path.join(tmp_dir, self._wc)
505 finally:
506 os.chdir(saved_wd)
507 return os.path.join(self._wc_path, *args)
509 def get_wc_tree(self):
510 if self._wc_tree is None:
511 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
512 return self._wc_tree
514 def path_exists(self, *args):
515 """Return True if the specified path exists within the repository.
517 (The strings in ARGS are first joined into a path using
518 os.path.join().)"""
520 return os.path.exists(self.get_wc(*args))
522 def check_props(self, keys, checks):
523 """Helper function for checking lots of properties. For a list of
524 files in the conversion, check that the values of the properties
525 listed in KEYS agree with those listed in CHECKS. CHECKS is a
526 list of tuples: [ (filename, [value, value, ...]), ...], where the
527 values are listed in the same order as the key names are listed in
528 KEYS."""
530 for (file, values) in checks:
531 assert len(values) == len(keys)
532 props = props_for_path(self.get_wc_tree(), file)
533 for i in range(len(keys)):
534 if props.get(keys[i]) != values[i]:
535 raise svntest.Failure(
536 "File %s has property %s set to \"%s\" "
537 "(it should have been \"%s\").\n"
538 % (file, keys[i], props.get(keys[i]), values[i],)
542 # Cache of conversions that have already been done. Keys are conv_id;
543 # values are Conversion instances.
544 already_converted = { }
546 def ensure_conversion(name, error_re=None, passbypass=None,
547 trunk=None, branches=None, tags=None, args=None):
548 """Convert CVS repository NAME to Subversion, but only if it has not
549 been converted before by this invocation of this script. If it has
550 been converted before, return the Conversion object from the
551 previous invocation.
553 If no error, return a Conversion instance.
555 If ERROR_RE is a string, it is a regular expression expected to
556 match some line of stderr printed by the conversion. If there is an
557 error and ERROR_RE is not set, then raise svntest.Failure.
559 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
560 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
562 NAME is just one word. For example, 'main' would mean to convert
563 './test-data/main-cvsrepos', and after the conversion, the resulting
564 Subversion repository would be in './tmp/main-svnrepos', and a
565 checked out head working copy in './tmp/main-wc'.
567 Any other options to pass to cvs2svn should be in ARGS, each element
568 being one option, e.g., '--trunk-only'. If the option takes an
569 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
570 are passed to cvs2svn in the order that they appear in ARGS.
573 if args is None:
574 args = []
575 else:
576 args = list(args)
578 if trunk is None:
579 trunk = 'trunk'
580 else:
581 args.append('--trunk=%s' % (trunk,))
583 if branches is None:
584 branches = 'branches'
585 else:
586 args.append('--branches=%s' % (branches,))
588 if tags is None:
589 tags = 'tags'
590 else:
591 args.append('--tags=%s' % (tags,))
593 conv_id = make_conversion_id(name, args, passbypass)
595 if conv_id not in already_converted:
596 try:
597 # Run the conversion and store the result for the rest of this
598 # session:
599 already_converted[conv_id] = Conversion(
600 conv_id, name, error_re, passbypass,
601 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
602 args)
603 except svntest.Failure:
604 # Remember the failure so that a future attempt to run this conversion
605 # does not bother to retry, but fails immediately.
606 already_converted[conv_id] = None
607 raise
609 conv = already_converted[conv_id]
610 if conv is None:
611 raise svntest.Failure
612 return conv
615 #----------------------------------------------------------------------
616 # Tests.
617 #----------------------------------------------------------------------
620 def show_usage():
621 "cvs2svn with no arguments shows usage"
622 out = run_cvs2svn(None)
623 if (len(out) > 2 and out[0].find('ERROR:') == 0
624 and out[1].find('DBM module')):
625 print 'cvs2svn cannot execute due to lack of proper DBM module.'
626 print 'Exiting without running any further tests.'
627 sys.exit(1)
628 if out[0].find('USAGE') < 0:
629 raise svntest.Failure('Basic cvs2svn invocation failed.')
632 def show_help_passes():
633 "cvs2svn --help-passes shows pass information"
634 out = run_cvs2svn(None, '--help-passes')
635 if out[0].find('PASSES') < 0:
636 raise svntest.Failure('cvs2svn --help-passes failed.')
639 def attr_exec():
640 "detection of the executable flag"
641 if sys.platform == 'win32':
642 raise svntest.Skip
643 conv = ensure_conversion('main')
644 st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec'))
645 if not st[0] & stat.S_IXUSR:
646 raise svntest.Failure
649 def space_fname():
650 "conversion of filename with a space"
651 conv = ensure_conversion('main')
652 if not conv.path_exists('trunk', 'single-files', 'space fname'):
653 raise svntest.Failure
656 def two_quick():
657 "two commits in quick succession"
658 conv = ensure_conversion('main')
659 logs = parse_log(
660 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
661 if len(logs) != 2:
662 raise svntest.Failure
665 def prune_with_care(**kw):
666 "prune, but never too much"
667 # Robert Pluim encountered this lovely one while converting the
668 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
669 # repository (see issue #1302). Step 4 is the doozy:
671 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
672 # revision 2: adds trunk/blah/NEWS
673 # revision 3: deletes trunk/blah/cookie
674 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
675 # revision 5: does nothing
677 # After fixing cvs2svn, the sequence (correctly) looks like this:
679 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
680 # revision 2: adds trunk/blah/NEWS
681 # revision 3: deletes trunk/blah/cookie
682 # revision 4: does nothing [because trunk/blah/cookie already deleted]
683 # revision 5: deletes blah
685 # The difference is in 4 and 5. In revision 4, it's not correct to
686 # prune blah/, because NEWS is still in there, so revision 4 does
687 # nothing now. But when we delete NEWS in 5, that should bubble up
688 # and prune blah/ instead.
690 # ### Note that empty revisions like 4 are probably going to become
691 # ### at least optional, if not banished entirely from cvs2svn's
692 # ### output. Hmmm, or they may stick around, with an extra
693 # ### revision property explaining what happened. Need to think
694 # ### about that. In some sense, it's a bug in Subversion itself,
695 # ### that such revisions don't show up in 'svn log' output.
697 # In the test below, 'trunk/full-prune/first' represents
698 # cookie, and 'trunk/full-prune/second' represents NEWS.
700 conv = ensure_conversion('main', **kw)
702 # Confirm that revision 4 removes '/trunk/full-prune/first',
703 # and that revision 6 removes '/trunk/full-prune'.
705 # Also confirm similar things about '/full-prune-reappear/...',
706 # which is similar, except that later on it reappears, restored
707 # from pruneland, because a file gets added to it.
709 # And finally, a similar thing for '/partial-prune/...', except that
710 # in its case, a permanent file on the top level prevents the
711 # pruning from going farther than the subdirectory containing first
712 # and second.
714 rev = 11
715 for path in ('/%(trunk)s/full-prune/first',
716 '/%(trunk)s/full-prune-reappear/sub/first',
717 '/%(trunk)s/partial-prune/sub/first'):
718 conv.logs[rev].check_change(path, 'D')
720 rev = 13
721 for path in ('/%(trunk)s/full-prune',
722 '/%(trunk)s/full-prune-reappear',
723 '/%(trunk)s/partial-prune/sub'):
724 conv.logs[rev].check_change(path, 'D')
726 rev = 47
727 for path in ('/%(trunk)s/full-prune-reappear',
728 '/%(trunk)s/full-prune-reappear/appears-later'):
729 conv.logs[rev].check_change(path, 'A')
732 def prune_with_care_variants():
733 "prune, with alternate repo layout"
734 prune_with_care(trunk='a', branches='b', tags='c')
735 prune_with_care(trunk='a/1', branches='b/1', tags='c/1')
736 prune_with_care(trunk='a/1', branches='a/2', tags='a/3')
739 def interleaved_commits():
740 "two interleaved trunk commits, different log msgs"
741 # See test-data/main-cvsrepos/proj/README.
742 conv = ensure_conversion('main')
744 # The initial import.
745 rev = 37
746 conv.logs[rev].check('Initial revision', (
747 ('/%(trunk)s/interleaved', 'A'),
748 ('/%(trunk)s/interleaved/1', 'A'),
749 ('/%(trunk)s/interleaved/2', 'A'),
750 ('/%(trunk)s/interleaved/3', 'A'),
751 ('/%(trunk)s/interleaved/4', 'A'),
752 ('/%(trunk)s/interleaved/5', 'A'),
753 ('/%(trunk)s/interleaved/a', 'A'),
754 ('/%(trunk)s/interleaved/b', 'A'),
755 ('/%(trunk)s/interleaved/c', 'A'),
756 ('/%(trunk)s/interleaved/d', 'A'),
757 ('/%(trunk)s/interleaved/e', 'A'),
760 # This PEP explains why we pass the 'log' parameter to these two
761 # nested functions, instead of just inheriting it from the enclosing
762 # scope: http://www.python.org/peps/pep-0227.html
764 def check_letters(log):
765 """Check if REV is the rev where only letters were committed."""
766 log.check('Committing letters only.', (
767 ('/%(trunk)s/interleaved/a', 'M'),
768 ('/%(trunk)s/interleaved/b', 'M'),
769 ('/%(trunk)s/interleaved/c', 'M'),
770 ('/%(trunk)s/interleaved/d', 'M'),
771 ('/%(trunk)s/interleaved/e', 'M'),
774 def check_numbers(log):
775 """Check if REV is the rev where only numbers were committed."""
776 log.check('Committing numbers only.', (
777 ('/%(trunk)s/interleaved/1', 'M'),
778 ('/%(trunk)s/interleaved/2', 'M'),
779 ('/%(trunk)s/interleaved/3', 'M'),
780 ('/%(trunk)s/interleaved/4', 'M'),
781 ('/%(trunk)s/interleaved/5', 'M'),
784 # One of the commits was letters only, the other was numbers only.
785 # But they happened "simultaneously", so we don't assume anything
786 # about which commit appeared first, so we just try both ways.
787 rev = rev + 3
788 try:
789 check_letters(conv.logs[rev])
790 check_numbers(conv.logs[rev + 1])
791 except svntest.Failure:
792 check_numbers(conv.logs[rev])
793 check_letters(conv.logs[rev + 1])
796 def simple_commits():
797 "simple trunk commits"
798 # See test-data/main-cvsrepos/proj/README.
799 conv = ensure_conversion('main')
801 # The initial import.
802 rev = 23
803 conv.logs[rev].check('Initial revision', (
804 ('/%(trunk)s/proj', 'A'),
805 ('/%(trunk)s/proj/default', 'A'),
806 ('/%(trunk)s/proj/sub1', 'A'),
807 ('/%(trunk)s/proj/sub1/default', 'A'),
808 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
809 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
810 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
811 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
812 ('/%(trunk)s/proj/sub2', 'A'),
813 ('/%(trunk)s/proj/sub2/default', 'A'),
814 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
815 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
816 ('/%(trunk)s/proj/sub3', 'A'),
817 ('/%(trunk)s/proj/sub3/default', 'A'),
820 # The first commit.
821 rev = 30
822 conv.logs[rev].check('First commit to proj, affecting two files.', (
823 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
824 ('/%(trunk)s/proj/sub3/default', 'M'),
827 # The second commit.
828 rev = 31
829 conv.logs[rev].check('Second commit to proj, affecting all 7 files.', (
830 ('/%(trunk)s/proj/default', 'M'),
831 ('/%(trunk)s/proj/sub1/default', 'M'),
832 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
833 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
834 ('/%(trunk)s/proj/sub2/default', 'M'),
835 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
836 ('/%(trunk)s/proj/sub3/default', 'M')
840 def simple_tags(**kw):
841 "simple tags and branches with no commits"
842 # See test-data/main-cvsrepos/proj/README.
843 conv = ensure_conversion('main', **kw)
845 # Verify the copy source for the tags we are about to check
846 # No need to verify the copyfrom revision, as simple_commits did that
847 conv.logs[24].check(sym_log_msg('vendorbranch'), (
848 ('/%(branches)s/vendorbranch/proj (from /%(trunk)s/proj:23)', 'A'),
851 fromstr = ' (from /%(branches)s/vendorbranch:25)'
853 # Tag on rev 1.1.1.1 of all files in proj
854 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
855 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
856 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr, 'A'),
857 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
858 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
861 # The same, as a branch
862 conv.logs[26].check(sym_log_msg('B_FROM_INITIALS'), (
863 ('/%(branches)s/B_FROM_INITIALS'+fromstr, 'A'),
864 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
865 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
868 # Tag on rev 1.1.1.1 of all files in proj, except one
869 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
870 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
871 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr, 'A'),
872 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
873 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
874 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
877 # The same, as a branch
878 conv.logs[27].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
879 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr, 'A'),
880 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
881 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
882 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
886 def simple_tags_variants():
887 "simple tags, with alternate repo layout"
888 simple_tags(trunk='a', branches='b', tags='c')
889 simple_tags(trunk='a/1', branches='b/1', tags='c/1')
890 simple_tags(trunk='a/1', branches='a/2', tags='a/3')
893 def simple_branch_commits():
894 "simple branch commits"
895 # See test-data/main-cvsrepos/proj/README.
896 conv = ensure_conversion('main')
898 rev = 35
899 conv.logs[rev].check('Modify three files, on branch B_MIXED.', (
900 ('/%(branches)s/B_MIXED/proj/default', 'M'),
901 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
902 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
906 def mixed_time_tag():
907 "mixed-time tag"
908 # See test-data/main-cvsrepos/proj/README.
909 conv = ensure_conversion('main')
911 log = conv.find_tag_log('T_MIXED')
912 expected = (
913 ('/%(tags)s/T_MIXED (from /%(trunk)s:31)', 'A'),
914 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
915 ('/%(tags)s/T_MIXED/single-files', 'D'),
916 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
917 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
918 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
920 if log.revision == 16:
921 expected.append(('/%(tags)s', 'A'))
922 log.check_changes(expected)
925 def mixed_time_branch_with_added_file():
926 "mixed-time branch, and a file added to the branch"
927 # See test-data/main-cvsrepos/proj/README.
928 conv = ensure_conversion('main')
930 # A branch from the same place as T_MIXED in the previous test,
931 # plus a file added directly to the branch
932 conv.logs[32].check(sym_log_msg('B_MIXED'), (
933 ('/%(branches)s/B_MIXED (from /%(trunk)s:31)', 'A'),
934 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
935 ('/%(branches)s/B_MIXED/single-files', 'D'),
936 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
937 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
938 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
941 conv.logs[34].check('Add a file on branch B_MIXED.', (
942 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
946 def mixed_commit():
947 "a commit affecting both trunk and a branch"
948 # See test-data/main-cvsrepos/proj/README.
949 conv = ensure_conversion('main')
951 conv.logs[36].check(
952 'A single commit affecting one file on branch B_MIXED '
953 'and one on trunk.', (
954 ('/%(trunk)s/proj/sub2/default', 'M'),
955 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
959 def split_time_branch():
960 "branch some trunk files, and later branch the rest"
961 # See test-data/main-cvsrepos/proj/README.
962 conv = ensure_conversion('main')
964 rev = 42
965 # First change on the branch, creating it
966 conv.logs[rev].check(sym_log_msg('B_SPLIT'), (
967 ('/%(branches)s/B_SPLIT (from /%(trunk)s:36)', 'A'),
968 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
969 ('/%(branches)s/B_SPLIT/single-files', 'D'),
970 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
973 conv.logs[rev + 1].check('First change on branch B_SPLIT.', (
974 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
975 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
976 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
977 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
978 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
981 # A trunk commit for the file which was not branched
982 conv.logs[rev + 2].check('A trunk change to sub1/subsubB/default. '
983 'This was committed about an', (
984 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
987 # Add the file not already branched to the branch, with modification:w
988 conv.logs[rev + 3].check(sym_log_msg('B_SPLIT'), (
989 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
990 '(from /%(trunk)s/proj/sub1/subsubB:44)', 'A'),
993 conv.logs[rev + 4].check('This change affects sub3/default and '
994 'sub1/subsubB/default, on branch', (
995 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
996 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
1000 def multiple_tags():
1001 "multiple tags referring to same revision"
1002 conv = ensure_conversion('main')
1003 if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'):
1004 raise svntest.Failure
1005 if not conv.path_exists(
1006 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'):
1007 raise svntest.Failure
1009 def bogus_tag():
1010 "conversion of invalid symbolic names"
1011 conv = ensure_conversion('bogus-tag')
1014 def overlapping_branch():
1015 "ignore a file with a branch with two names"
1016 conv = ensure_conversion('overlapping-branch',
1017 error_re='.*cannot also have name \'vendorB\'')
1018 rev = 4
1019 conv.logs[rev].check_change('/%(branches)s/vendorA (from /%(trunk)s:3)',
1020 'A')
1021 # We don't know what order the first two commits would be in, since
1022 # they have different log messages but the same timestamps. As only
1023 # one of the files would be on the vendorB branch in the regression
1024 # case being tested here, we allow for either order.
1025 if (conv.logs[rev].get_path_op(
1026 '/%(branches)s/vendorB (from /%(trunk)s:2)') == 'A'
1027 or conv.logs[rev].get_path_op(
1028 '/%(branches)s/vendorB (from /%(trunk)s:3)') == 'A'):
1029 raise svntest.Failure
1030 conv.logs[rev + 1].check_changes(())
1031 if len(conv.logs) != rev + 1:
1032 raise svntest.Failure
1035 def phoenix_branch(**kw):
1036 "convert a branch file rooted in a 'dead' revision"
1037 conv = ensure_conversion('phoenix', **kw)
1038 conv.logs[8].check(sym_log_msg('volsung_20010721'), (
1039 ('/%(branches)s/volsung_20010721 (from /%(trunk)s:7)', 'A'),
1040 ('/%(branches)s/volsung_20010721/file.txt', 'D'),
1042 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1043 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1047 def phoenix_branch_variants():
1048 "'dead' revision, with alternate repo layout"
1049 phoenix_branch(trunk='a/1', branches='b/1', tags='c/1')
1052 ###TODO: We check for 4 changed paths here to accomodate creating tags
1053 ###and branches in rev 1, but that will change, so this will
1054 ###eventually change back.
1055 def ctrl_char_in_log():
1056 "handle a control char in a log message"
1057 # This was issue #1106.
1058 rev = 2
1059 conv = ensure_conversion('ctrl-char-in-log')
1060 conv.logs[rev].check_changes((
1061 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1063 if conv.logs[rev].msg.find('\x04') < 0:
1064 raise svntest.Failure(
1065 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1068 def overdead():
1069 "handle tags rooted in a redeleted revision"
1070 conv = ensure_conversion('overdead')
1073 def no_trunk_prune(**kw):
1074 "ensure that trunk doesn't get pruned"
1075 conv = ensure_conversion('overdead', **kw)
1076 for rev in conv.logs.keys():
1077 rev_logs = conv.logs[rev]
1078 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1079 raise svntest.Failure
1082 def no_trunk_prune_variants():
1083 "no trunk pruning, with alternate repo layout"
1084 no_trunk_prune(trunk='a', branches='b', tags='c')
1085 no_trunk_prune(trunk='a/1', branches='b/1', tags='c/1')
1086 no_trunk_prune(trunk='a/1', branches='a/2', tags='a/3')
1089 def double_delete():
1090 "file deleted twice, in the root of the repository"
1091 # This really tests several things: how we handle a file that's
1092 # removed (state 'dead') in two successive revisions; how we
1093 # handle a file in the root of the repository (there were some
1094 # bugs in cvs2svn's svn path construction for top-level files); and
1095 # the --no-prune option.
1096 conv = ensure_conversion(
1097 'double-delete', args=['--trunk-only', '--no-prune'])
1099 path = '/%(trunk)s/twice-removed'
1100 rev = 2
1101 conv.logs[rev].check_change(path, 'A')
1102 conv.logs[rev].check_msg('Initial revision')
1104 conv.logs[rev + 1].check_change(path, 'D')
1105 conv.logs[rev + 1].check_msg('Remove this file for the first time.')
1107 if conv.logs[rev + 1].get_path_op('/%(trunk)s') is not None:
1108 raise svntest.Failure
1111 def split_branch():
1112 "branch created from both trunk and another branch"
1113 # See test-data/split-branch-cvsrepos/README.
1115 # The conversion will fail if the bug is present, and
1116 # ensure_conversion will raise svntest.Failure.
1117 conv = ensure_conversion('split-branch')
1120 def resync_misgroups():
1121 "resyncing should not misorder commit groups"
1122 # See test-data/resync-misgroups-cvsrepos/README.
1124 # The conversion will fail if the bug is present, and
1125 # ensure_conversion will raise svntest.Failure.
1126 conv = ensure_conversion('resync-misgroups')
1129 def tagged_branch_and_trunk(**kw):
1130 "allow tags with mixed trunk and branch sources"
1131 conv = ensure_conversion('tagged-branch-n-trunk', **kw)
1133 tags = kw.get('tags', 'tags')
1135 a_path = conv.get_wc(tags, 'some-tag', 'a.txt')
1136 b_path = conv.get_wc(tags, 'some-tag', 'b.txt')
1137 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1138 raise svntest.Failure
1139 if (open(a_path, 'r').read().find('1.24') == -1) \
1140 or (open(b_path, 'r').read().find('1.5') == -1):
1141 raise svntest.Failure
1144 def tagged_branch_and_trunk_variants():
1145 "mixed tags, with alternate repo layout"
1146 tagged_branch_and_trunk(trunk='a/1', branches='a/2', tags='a/3')
1149 def enroot_race():
1150 "never use the rev-in-progress as a copy source"
1151 # See issue #1427 and r8544.
1152 conv = ensure_conversion('enroot-race')
1153 rev = 8
1154 conv.logs[rev].check_changes((
1155 ('/%(branches)s/mybranch (from /%(trunk)s:7)', 'A'),
1156 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1157 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1159 conv.logs[rev + 1].check_changes((
1160 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1161 ('/%(trunk)s/proj/a.txt', 'M'),
1162 ('/%(trunk)s/proj/b.txt', 'M'),
1166 def enroot_race_obo():
1167 "do use the last completed rev as a copy source"
1168 conv = ensure_conversion('enroot-race-obo')
1169 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1170 if not len(conv.logs) == 3:
1171 raise svntest.Failure
1174 def branch_delete_first(**kw):
1175 "correctly handle deletion as initial branch action"
1176 # See test-data/branch-delete-first-cvsrepos/README.
1178 # The conversion will fail if the bug is present, and
1179 # ensure_conversion would raise svntest.Failure.
1180 conv = ensure_conversion('branch-delete-first', **kw)
1182 branches = kw.get('branches', 'branches')
1184 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1185 if conv.path_exists(branches, 'branch-1', 'file'):
1186 raise svntest.Failure
1187 if conv.path_exists(branches, 'branch-2', 'file'):
1188 raise svntest.Failure
1189 if not conv.path_exists(branches, 'branch-3', 'file'):
1190 raise svntest.Failure
1193 def branch_delete_first_variants():
1194 "initial delete, with alternate repo layout"
1195 branch_delete_first(trunk='a/1', branches='a/2', tags='a/3')
1198 def nonascii_filenames():
1199 "non ascii files converted incorrectly"
1200 # see issue #1255
1202 # on a en_US.iso-8859-1 machine this test fails with
1203 # svn: Can't recode ...
1205 # as described in the issue
1207 # on a en_US.UTF-8 machine this test fails with
1208 # svn: Malformed XML ...
1210 # which means at least it fails. Unfortunately it won't fail
1211 # with the same error...
1213 # mangle current locale settings so we know we're not running
1214 # a UTF-8 locale (which does not exhibit this problem)
1215 current_locale = locale.getlocale()
1216 new_locale = 'en_US.ISO8859-1'
1217 locale_changed = None
1219 # From http://docs.python.org/lib/module-sys.html
1221 # getfilesystemencoding():
1223 # Return the name of the encoding used to convert Unicode filenames
1224 # into system file names, or None if the system default encoding is
1225 # used. The result value depends on the operating system:
1227 # - On Windows 9x, the encoding is ``mbcs''.
1228 # - On Mac OS X, the encoding is ``utf-8''.
1229 # - On Unix, the encoding is the user's preference according to the
1230 # result of nl_langinfo(CODESET), or None if the
1231 # nl_langinfo(CODESET) failed.
1232 # - On Windows NT+, file names are Unicode natively, so no conversion is
1233 # performed.
1235 # So we're going to skip this test on Mac OS X for now.
1236 if sys.platform == "darwin":
1237 raise svntest.Skip
1239 try:
1240 # change locale to non-UTF-8 locale to generate latin1 names
1241 locale.setlocale(locale.LC_ALL, # this might be too broad?
1242 new_locale)
1243 locale_changed = 1
1244 except locale.Error:
1245 raise svntest.Skip
1247 try:
1248 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
1249 dstrepos_path = os.path.join(test_data_dir,'non-ascii-cvsrepos')
1250 if not os.path.exists(dstrepos_path):
1251 # create repos from existing main repos
1252 shutil.copytree(srcrepos_path, dstrepos_path)
1253 base_path = os.path.join(dstrepos_path, 'single-files')
1254 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1255 os.path.join(base_path, 'two\366uick,v'))
1256 new_path = os.path.join(dstrepos_path, 'single\366files')
1257 os.rename(base_path, new_path)
1259 # if ensure_conversion can generate a
1260 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1261 finally:
1262 if locale_changed:
1263 locale.setlocale(locale.LC_ALL, current_locale)
1264 svntest.main.safe_rmtree(dstrepos_path)
1267 def vendor_branch_sameness():
1268 "avoid spurious changes for initial revs"
1269 conv = ensure_conversion('vendor-branch-sameness')
1271 # There are four files in the repository:
1273 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1274 # the same contents, the file's default branch is 1.1.1,
1275 # and both revisions are in state 'Exp'.
1277 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1278 # 1.1 (the addition of a line of text).
1280 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1282 # d.txt: This file was created by 'cvs add' instead of import, so
1283 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1284 # The timestamp on the add is exactly the same as for the
1285 # imports of the other files.
1287 # (Log messages for the same revisions are the same in all files.)
1289 # What we expect to see is everyone added in r1, then trunk/proj
1290 # copied in r2. In the copy, only a.txt should be left untouched;
1291 # b.txt should be 'M'odified, and (for different reasons) c.txt and
1292 # d.txt should be 'D'eleted.
1294 rev = 2
1295 conv.logs[rev].check('Initial revision', (
1296 ('/%(trunk)s/proj', 'A'),
1297 ('/%(trunk)s/proj/a.txt', 'A'),
1298 ('/%(trunk)s/proj/b.txt', 'A'),
1299 ('/%(trunk)s/proj/c.txt', 'A'),
1300 ('/%(trunk)s/proj/d.txt', 'A'),
1303 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1304 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1305 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1308 conv.logs[rev + 2].check('First vendor branch revision.', (
1309 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1310 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1314 def default_branches():
1315 "handle default branches correctly"
1316 conv = ensure_conversion('default-branches')
1318 # There are seven files in the repository:
1320 # a.txt:
1321 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1322 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1323 # committed (thus losing the default branch "1.1.1"), then
1324 # 1.1.1.4 was imported. All vendor import release tags are
1325 # still present.
1327 # b.txt:
1328 # Like a.txt, but without rev 1.2.
1330 # c.txt:
1331 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1333 # d.txt:
1334 # Same as the previous two, but 1.1.1 branch is unlabeled.
1336 # e.txt:
1337 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1339 # deleted-on-vendor-branch.txt,v:
1340 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1342 # added-then-imported.txt,v:
1343 # Added with 'cvs add' to create 1.1, then imported with
1344 # completely different contents to create 1.1.1.1, therefore
1345 # never had a default branch.
1348 conv.logs[18].check(sym_log_msg('vtag-4',1), (
1349 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:16)', 'A'),
1350 ('/%(tags)s/vtag-4/proj/d.txt '
1351 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'A'),
1354 conv.logs[6].check(sym_log_msg('vtag-1',1), (
1355 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:5)', 'A'),
1356 ('/%(tags)s/vtag-1/proj/d.txt '
1357 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1360 conv.logs[9].check(sym_log_msg('vtag-2',1), (
1361 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:7)', 'A'),
1362 ('/%(tags)s/vtag-2/proj/d.txt '
1363 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'A'),
1366 conv.logs[12].check(sym_log_msg('vtag-3',1), (
1367 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:10)', 'A'),
1368 ('/%(tags)s/vtag-3/proj/d.txt '
1369 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'A'),
1370 ('/%(tags)s/vtag-3/proj/e.txt '
1371 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'A'),
1374 conv.logs[17].check("This commit was generated by cvs2svn "
1375 "to compensate for changes in r16,", (
1376 ('/%(trunk)s/proj/b.txt '
1377 '(from /%(branches)s/vbranchA/proj/b.txt:16)', 'R'),
1378 ('/%(trunk)s/proj/c.txt '
1379 '(from /%(branches)s/vbranchA/proj/c.txt:16)', 'R'),
1380 ('/%(trunk)s/proj/d.txt '
1381 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'R'),
1382 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1383 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:16)',
1384 'A'),
1385 ('/%(trunk)s/proj/e.txt '
1386 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:16)', 'R'),
1389 conv.logs[16].check("Import (vbranchA, vtag-4).", (
1390 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1391 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1392 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1393 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1394 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1395 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1396 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1399 conv.logs[15].check(sym_log_msg('vbranchA'), (
1400 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1401 '(from /%(trunk)s/proj/added-then-imported.txt:14)', 'A'),
1404 conv.logs[14].check("Add a file to the working copy.", (
1405 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1408 conv.logs[13].check("First regular commit, to a.txt, on vtag-3.", (
1409 ('/%(trunk)s/proj/a.txt', 'M'),
1412 conv.logs[11].check("This commit was generated by cvs2svn "
1413 "to compensate for changes in r10,", (
1414 ('/%(trunk)s/proj/a.txt '
1415 '(from /%(branches)s/vbranchA/proj/a.txt:10)', 'R'),
1416 ('/%(trunk)s/proj/b.txt '
1417 '(from /%(branches)s/vbranchA/proj/b.txt:10)', 'R'),
1418 ('/%(trunk)s/proj/c.txt '
1419 '(from /%(branches)s/vbranchA/proj/c.txt:10)', 'R'),
1420 ('/%(trunk)s/proj/d.txt '
1421 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'R'),
1422 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1423 ('/%(trunk)s/proj/e.txt '
1424 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'R'),
1427 conv.logs[10].check("Import (vbranchA, vtag-3).", (
1428 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1429 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1430 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1431 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1432 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1433 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1436 conv.logs[8].check("This commit was generated by cvs2svn "
1437 "to compensate for changes in r7,", (
1438 ('/%(trunk)s/proj/a.txt '
1439 '(from /%(branches)s/vbranchA/proj/a.txt:7)', 'R'),
1440 ('/%(trunk)s/proj/b.txt '
1441 '(from /%(branches)s/vbranchA/proj/b.txt:7)', 'R'),
1442 ('/%(trunk)s/proj/c.txt '
1443 '(from /%(branches)s/vbranchA/proj/c.txt:7)', 'R'),
1444 ('/%(trunk)s/proj/d.txt '
1445 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'R'),
1446 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1447 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:7)',
1448 'R'),
1449 ('/%(trunk)s/proj/e.txt '
1450 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:7)', 'R'),
1453 conv.logs[7].check("Import (vbranchA, vtag-2).", (
1454 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1455 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1456 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1457 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1458 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1459 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1462 conv.logs[5].check("Import (vbranchA, vtag-1).", ())
1464 conv.logs[4].check(sym_log_msg('vbranchA'), (
1465 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1466 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1467 ('/%(branches)s/vbranchA/proj/e.txt', 'D'),
1470 conv.logs[3].check(sym_log_msg('unlabeled-1.1.1'), (
1471 ('/%(branches)s/unlabeled-1.1.1 (from /%(trunk)s:2)', 'A'),
1472 ('/%(branches)s/unlabeled-1.1.1/proj/a.txt', 'D'),
1473 ('/%(branches)s/unlabeled-1.1.1/proj/b.txt', 'D'),
1474 ('/%(branches)s/unlabeled-1.1.1/proj/c.txt', 'D'),
1475 ('/%(branches)s/unlabeled-1.1.1/proj/deleted-on-vendor-branch.txt', 'D'),
1478 conv.logs[2].check("Initial revision", (
1479 ('/%(trunk)s/proj', 'A'),
1480 ('/%(trunk)s/proj/a.txt', 'A'),
1481 ('/%(trunk)s/proj/b.txt', 'A'),
1482 ('/%(trunk)s/proj/c.txt', 'A'),
1483 ('/%(trunk)s/proj/d.txt', 'A'),
1484 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1485 ('/%(trunk)s/proj/e.txt', 'A'),
1489 def compose_tag_three_sources():
1490 "compose a tag from three sources"
1491 conv = ensure_conversion('compose-tag-three-sources')
1493 conv.logs[2].check("Add on trunk", (
1494 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
1495 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
1496 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
1497 ('/%(trunk)s/tagged-on-b1', 'A'),
1498 ('/%(trunk)s/tagged-on-b2', 'A'),
1501 conv.logs[3].check(sym_log_msg('b1'), (
1502 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
1505 conv.logs[4].check(sym_log_msg('b2'), (
1506 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
1509 conv.logs[5].check("Commit on branch b1", (
1510 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
1511 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
1512 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
1513 ('/%(branches)s/b1/tagged-on-b1', 'M'),
1514 ('/%(branches)s/b1/tagged-on-b2', 'M'),
1517 conv.logs[6].check("Commit on branch b2", (
1518 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
1519 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
1520 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
1521 ('/%(branches)s/b2/tagged-on-b1', 'M'),
1522 ('/%(branches)s/b2/tagged-on-b2', 'M'),
1525 conv.logs[7].check("Commit again on trunk", (
1526 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
1527 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
1528 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
1529 ('/%(trunk)s/tagged-on-b1', 'M'),
1530 ('/%(trunk)s/tagged-on-b2', 'M'),
1533 conv.logs[8].check(sym_log_msg('T',1), (
1534 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
1535 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:7)', 'R'),
1536 ('/%(tags)s/T/tagged-on-trunk-1.1 '
1537 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
1538 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:7)', 'R'),
1542 def pass5_when_to_fill():
1543 "reserve a svn revnum for a fill only when required"
1544 # The conversion will fail if the bug is present, and
1545 # ensure_conversion would raise svntest.Failure.
1546 conv = ensure_conversion('pass5-when-to-fill')
1549 def empty_trunk(**kw):
1550 "don't break when the trunk is empty"
1551 # The conversion will fail if the bug is present, and
1552 # ensure_conversion would raise svntest.Failure.
1553 conv = ensure_conversion('empty-trunk', **kw)
1556 def empty_trunk_variants():
1557 "empty trunk, with alternate repo layout"
1558 empty_trunk(trunk='a', branches='b', tags='c')
1559 empty_trunk(trunk='a/1', branches='a/2', tags='a/3')
1562 def no_spurious_svn_commits():
1563 "ensure that we don't create any spurious commits"
1564 conv = ensure_conversion('phoenix')
1566 # Check spurious commit that could be created in CVSCommit._pre_commit
1567 # (When you add a file on a branch, CVS creates a trunk revision
1568 # in state 'dead'. If the log message of that commit is equal to
1569 # the one that CVS generates, we do not ever create a 'fill'
1570 # SVNCommit for it.)
1572 # and spurious commit that could be created in CVSCommit._commit
1573 # (When you add a file on a branch, CVS creates a trunk revision
1574 # in state 'dead'. If the log message of that commit is equal to
1575 # the one that CVS generates, we do not create a primary SVNCommit
1576 # for it.)
1577 conv.logs[18].check('File added on branch xiphophorus', (
1578 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
1581 # Check to make sure that a commit *is* generated:
1582 # (When you add a file on a branch, CVS creates a trunk revision
1583 # in state 'dead'. If the log message of that commit is NOT equal
1584 # to the one that CVS generates, we create a primary SVNCommit to
1585 # serve as a home for the log message in question.
1586 conv.logs[19].check('file added-on-branch2.txt was initially added on '
1587 + 'branch xiphophorus,\nand this log message was tweaked', ())
1589 # Check spurious commit that could be created in
1590 # CVSRevisionAggregator.attempt_to_commit_symbols
1591 # (We shouldn't consider a CVSRevision whose op is OP_DEAD as a
1592 # candidate for the LastSymbolicNameDatabase.
1593 conv.logs[20].check('This file was also added on branch xiphophorus,', (
1594 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
1598 def peer_path_pruning(**kw):
1599 "make sure that filling prunes paths correctly"
1600 conv = ensure_conversion('peer-path-pruning', **kw)
1601 conv.logs[8].check(sym_log_msg('BRANCH'), (
1602 ('/%(branches)s/BRANCH (from /%(trunk)s:6)', 'A'),
1603 ('/%(branches)s/BRANCH/bar', 'D'),
1604 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:7)', 'R'),
1608 def peer_path_pruning_variants():
1609 "filling prune paths, with alternate repo layout"
1610 peer_path_pruning(trunk='a/1', branches='a/2', tags='a/3')
1613 def invalid_closings_on_trunk():
1614 "verify correct revs are copied to default branches"
1615 # The conversion will fail if the bug is present, and
1616 # ensure_conversion would raise svntest.Failure.
1617 conv = ensure_conversion('invalid-closings-on-trunk')
1620 def individual_passes():
1621 "run each pass individually"
1622 conv = ensure_conversion('main')
1623 conv2 = ensure_conversion('main', passbypass=1)
1625 if conv.logs != conv2.logs:
1626 raise svntest.Failure
1629 def resync_bug():
1630 "reveal a big bug in our resync algorithm"
1631 # This will fail if the bug is present
1632 conv = ensure_conversion('resync-bug')
1635 def branch_from_default_branch():
1636 "reveal a bug in our default branch detection code"
1637 conv = ensure_conversion('branch-from-default-branch')
1639 # This revision will be a default branch synchronization only
1640 # if cvs2svn is correctly determining default branch revisions.
1642 # The bug was that cvs2svn was treating revisions on branches off of
1643 # default branches as default branch revisions, resulting in
1644 # incorrectly regarding the branch off of the default branch as a
1645 # non-trunk default branch. Crystal clear? I thought so. See
1646 # issue #42 for more incoherent blathering.
1647 conv.logs[6].check("This commit was generated by cvs2svn", (
1648 ('/%(trunk)s/proj/file.txt '
1649 '(from /%(branches)s/upstream/proj/file.txt:5)', 'R'),
1652 def file_in_attic_too():
1653 "die if a file exists in and out of the attic"
1654 try:
1655 ensure_conversion('file-in-attic-too')
1656 raise MissingErrorException
1657 except svntest.Failure:
1658 pass
1660 def symbolic_name_filling_guide():
1661 "reveal a big bug in our SymbolFillingGuide"
1662 # This will fail if the bug is present
1663 conv = ensure_conversion('symbolic-name-overfill')
1666 # Helpers for tests involving file contents and properties.
1668 class NodeTreeWalkException:
1669 "Exception class for node tree traversals."
1670 pass
1672 def node_for_path(node, path):
1673 "In the tree rooted under SVNTree NODE, return the node at PATH."
1674 if node.name != '__SVN_ROOT_NODE':
1675 raise NodeTreeWalkException
1676 path = path.strip('/')
1677 components = path.split('/')
1678 for component in components:
1679 node = svntest.tree.get_child(node, component)
1680 return node
1682 # Helper for tests involving properties.
1683 def props_for_path(node, path):
1684 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
1685 return node_for_path(node, path).props
1688 def eol_mime():
1689 "test eol settings and mime types together"
1690 ###TODO: It's a bit klugey to construct this path here. But so far
1691 ### there's only one test with a mime.types file. If we have more,
1692 ### we should abstract this into some helper, which would be located
1693 ### near ensure_conversion(). Note that it is a convention of this
1694 ### test suite for a mime.types file to be located in the top level
1695 ### of the CVS repository to which it applies.
1696 mime_path = os.path.join('..', test_data_dir, 'eol-mime-cvsrepos',
1697 'mime.types')
1699 # We do four conversions. Each time, we pass --mime-types=FILE with
1700 # the same FILE, but vary --no-default-eol and --eol-from-mime-type.
1701 # Thus there's one conversion with neither flag, one with just the
1702 # former, one with just the latter, and one with both.
1704 # In two of the four conversions, we pass --cvs-revnums to make
1705 # certain that there are no bad interactions.
1707 # The files are as follows:
1709 # trunk/foo.txt: no -kb, mime file says nothing.
1710 # trunk/foo.xml: no -kb, mime file says text.
1711 # trunk/foo.zip: no -kb, mime file says non-text.
1712 # trunk/foo.bin: has -kb, mime file says nothing.
1713 # trunk/foo.csv: has -kb, mime file says text.
1714 # trunk/foo.dbf: has -kb, mime file says non-text.
1716 ## Neither --no-default-eol nor --eol-from-mime-type. ##
1717 conv = ensure_conversion(
1718 'eol-mime', args=['--mime-types=%s' % mime_path, '--cvs-revnums'])
1719 conv.check_props(
1720 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1722 ('trunk/foo.txt', ['native', None, '1.2']),
1723 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1724 ('trunk/foo.zip', ['native', 'application/zip', '1.2']),
1725 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1726 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1727 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1731 ## Just --no-default-eol, not --eol-from-mime-type. ##
1732 conv = ensure_conversion(
1733 'eol-mime', args=['--mime-types=%s' % mime_path, '--no-default-eol'])
1734 conv.check_props(
1735 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1737 ('trunk/foo.txt', [None, None, None]),
1738 ('trunk/foo.xml', [None, 'text/xml', None]),
1739 ('trunk/foo.zip', [None, 'application/zip', None]),
1740 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1741 ('trunk/foo.csv', [None, 'text/csv', None]),
1742 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1746 ## Just --eol-from-mime-type, not --no-default-eol. ##
1747 conv = ensure_conversion('eol-mime', args=[
1748 '--mime-types=%s' % mime_path, '--eol-from-mime-type', '--cvs-revnums'
1750 conv.check_props(
1751 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1753 ('trunk/foo.txt', ['native', None, '1.2']),
1754 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1755 ('trunk/foo.zip', [None, 'application/zip', '1.2']),
1756 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1757 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1758 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1762 ## Both --no-default-eol and --eol-from-mime-type. ##
1763 conv = ensure_conversion('eol-mime', args=[
1764 '--mime-types=%s' % mime_path, '--eol-from-mime-type',
1765 '--no-default-eol'])
1766 conv.check_props(
1767 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1769 ('trunk/foo.txt', [None, None, None]),
1770 ('trunk/foo.xml', ['native', 'text/xml', None]),
1771 ('trunk/foo.zip', [None, 'application/zip', None]),
1772 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1773 ('trunk/foo.csv', [None, 'text/csv', None]),
1774 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1779 def keywords():
1780 "test setting of svn:keywords property among others"
1781 conv = ensure_conversion('keywords')
1782 conv.check_props(
1783 ['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
1785 ('trunk/foo.default', ['Author Date Id Revision', 'native', None]),
1786 ('trunk/foo.kkvl', ['Author Date Id Revision', 'native', None]),
1787 ('trunk/foo.kkv', ['Author Date Id Revision', 'native', None]),
1788 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
1789 ('trunk/foo.kk', [None, 'native', None]),
1790 ('trunk/foo.ko', [None, 'native', None]),
1791 ('trunk/foo.kv', [None, 'native', None]),
1796 def ignore():
1797 "test setting of svn:ignore property"
1798 conv = ensure_conversion('cvsignore')
1799 wc_tree = conv.get_wc_tree()
1800 topdir_props = props_for_path(wc_tree, 'trunk/proj')
1801 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
1803 if topdir_props['svn:ignore'] != \
1804 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n\n':
1805 raise svntest.Failure
1807 if subdir_props['svn:ignore'] != \
1808 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n\n':
1809 raise svntest.Failure
1812 def requires_cvs():
1813 "test that CVS can still do what RCS can't"
1814 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
1815 conv = ensure_conversion('requires-cvs', args=["--use-cvs"])
1817 atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read()
1818 cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read()
1820 if atsign_contents[-1:] == "@":
1821 raise svntest.Failure
1822 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
1823 raise svntest.Failure
1825 if not (conv.logs[21].author == "William Lyon Phelps III" and
1826 conv.logs[20].author == "j random"):
1827 raise svntest.Failure
1830 def questionable_branch_names():
1831 "test that we can handle weird branch names"
1832 conv = ensure_conversion('questionable-symbols')
1833 # If the conversion succeeds, then we're okay. We could check the
1834 # actual branch paths, too, but the main thing is to know that the
1835 # conversion doesn't fail.
1838 def questionable_tag_names():
1839 "test that we can handle weird tag names"
1840 conv = ensure_conversion('questionable-symbols')
1841 for tag_name in ['Tag_A', 'TagWith--Backslash_E', 'TagWith++Slash_Z']:
1842 conv.find_tag_log(tag_name).check(sym_log_msg(tag_name,1), (
1843 ('/%(tags)s/' + tag_name + ' (from /trunk:8)', 'A'),
1847 def revision_reorder_bug():
1848 "reveal a bug that reorders file revisions"
1849 conv = ensure_conversion('revision-reorder-bug')
1850 # If the conversion succeeds, then we're okay. We could check the
1851 # actual revisions, too, but the main thing is to know that the
1852 # conversion doesn't fail.
1855 def exclude():
1856 "test that exclude really excludes everything"
1857 conv = ensure_conversion('main', args=['--exclude=.*'])
1858 for log in conv.logs.values():
1859 for item in log.changed_paths.keys():
1860 if item.startswith('/branches/') or item.startswith('/tags/'):
1861 raise svntest.Failure
1864 def vendor_branch_delete_add():
1865 "add trunk file that was deleted on vendor branch"
1866 # This will error if the bug is present
1867 conv = ensure_conversion('vendor-branch-delete-add')
1870 def resync_pass2_pull_forward():
1871 "ensure pass2 doesn't pull rev too far forward"
1872 conv = ensure_conversion('resync-pass2-pull-forward')
1873 # If the conversion succeeds, then we're okay. We could check the
1874 # actual revisions, too, but the main thing is to know that the
1875 # conversion doesn't fail.
1878 def native_eol():
1879 "only LFs for svn:eol-style=native files"
1880 conv = ensure_conversion('native-eol')
1881 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
1882 conv.repos)
1883 # Verify that all files in the dump have LF EOLs. We're actually
1884 # testing the whole dump file, but the dump file itself only uses
1885 # LF EOLs, so we're safe.
1886 for line in lines:
1887 if line[-1] != '\n' or line[:-1].find('\r') != -1:
1888 raise svntest.Failure
1891 def double_fill():
1892 "reveal a bug that created a branch twice"
1893 conv = ensure_conversion('double-fill')
1894 # If the conversion succeeds, then we're okay. We could check the
1895 # actual revisions, too, but the main thing is to know that the
1896 # conversion doesn't fail.
1899 def resync_pass2_push_backward():
1900 "ensure pass2 doesn't push rev too far backward"
1901 conv = ensure_conversion('resync-pass2-push-backward')
1902 # If the conversion succeeds, then we're okay. We could check the
1903 # actual revisions, too, but the main thing is to know that the
1904 # conversion doesn't fail.
1907 def double_add():
1908 "reveal a bug that added a branch file twice"
1909 conv = ensure_conversion('double-add')
1910 # If the conversion succeeds, then we're okay. We could check the
1911 # actual revisions, too, but the main thing is to know that the
1912 # conversion doesn't fail.
1915 def bogus_branch_copy():
1916 "reveal a bug that copies a branch file wrongly"
1917 conv = ensure_conversion('bogus-branch-copy')
1918 # If the conversion succeeds, then we're okay. We could check the
1919 # actual revisions, too, but the main thing is to know that the
1920 # conversion doesn't fail.
1923 def nested_ttb_directories():
1924 "require error if ttb directories are not disjoint"
1925 opts_list = [
1926 {'trunk' : 'a', 'branches' : 'a',},
1927 {'trunk' : 'a', 'tags' : 'a',},
1928 {'branches' : 'a', 'tags' : 'a',},
1929 # This option conflicts with the default trunk path:
1930 {'branches' : 'trunk',},
1931 # Try some nested directories:
1932 {'trunk' : 'a', 'branches' : 'a/b',},
1933 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
1934 {'branches' : 'a', 'tags' : 'a/b',},
1937 for opts in opts_list:
1938 try:
1939 ensure_conversion(
1940 'main', error_re=r'.*paths .* and .* are not disjoint\.', **opts
1942 raise MissingErrorException
1943 except svntest.Failure:
1944 pass
1947 def auto_props_ignore_case():
1948 "test auto-props (case-insensitive)"
1949 ### TODO: It's a bit klugey to construct this path here. See also
1950 ### the comment in eol_mime().
1951 auto_props_path = os.path.join('..', test_data_dir, 'eol-mime-cvsrepos',
1952 'auto-props')
1954 # The files are as follows:
1956 # trunk/foo.txt: no -kb, mime auto-prop says nothing.
1957 # trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
1958 # trunk/foo.zip: no -kb, mime auto-prop says non-text.
1959 # trunk/foo.bin: has -kb, mime auto-prop says nothing.
1960 # trunk/foo.csv: has -kb, mime auto-prop says text.
1961 # trunk/foo.dbf: has -kb, mime auto-prop says non-text.
1962 # trunk/foo.UPCASE1: no -kb, no mime type.
1963 # trunk/foo.UPCASE2: no -kb, no mime type.
1965 conv = ensure_conversion(
1966 'eol-mime',
1967 args=['--auto-props=%s' % auto_props_path, '--auto-props-ignore-case'])
1968 conv.check_props(
1969 ['myprop', 'svn:eol-style', 'svn:mime-type'],
1971 ('trunk/foo.txt', ['txt', 'native', None]),
1972 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml']),
1973 ('trunk/foo.zip', ['zip', 'native', 'application/zip']),
1974 ('trunk/foo.bin', ['bin', None, 'application/octet-stream']),
1975 ('trunk/foo.csv', ['csv', None, 'text/csv']),
1976 ('trunk/foo.dbf', ['dbf', None, 'application/what-is-dbf']),
1977 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None]),
1978 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None]),
1983 def auto_props():
1984 "test auto-props (case-sensitive)"
1985 # See auto_props for comments.
1986 auto_props_path = os.path.join('..', test_data_dir, 'eol-mime-cvsrepos',
1987 'auto-props')
1989 conv = ensure_conversion(
1990 'eol-mime', args=['--auto-props=%s' % auto_props_path])
1991 conv.check_props(
1992 ['myprop', 'svn:eol-style', 'svn:mime-type'],
1994 ('trunk/foo.txt', ['txt', 'native', None]),
1995 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml']),
1996 ('trunk/foo.zip', ['zip', 'native', 'application/zip']),
1997 ('trunk/foo.bin', ['bin', None, 'application/octet-stream']),
1998 ('trunk/foo.csv', ['csv', None, 'text/csv']),
1999 ('trunk/foo.dbf', ['dbf', None, 'application/what-is-dbf']),
2000 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None]),
2001 ('trunk/foo.UPCASE2', [None, 'native', None]),
2006 def ctrl_char_in_filename():
2007 "do not allow control characters in filenames"
2009 try:
2010 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
2011 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
2012 if os.path.exists(dstrepos_path):
2013 svntest.main.safe_rmtree(dstrepos_path)
2015 # create repos from existing main repos
2016 shutil.copytree(srcrepos_path, dstrepos_path)
2017 base_path = os.path.join(dstrepos_path, 'single-files')
2018 try:
2019 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2020 os.path.join(base_path, 'two\rquick,v'))
2021 except:
2022 # Operating systems that don't allow control characters in
2023 # filenames will hopefully have thrown an exception; in that
2024 # case, just skip this test.
2025 raise svntest.Skip
2027 try:
2028 conv = ensure_conversion(
2029 'ctrl-char-filename',
2030 error_re=(r'.*Character .* in filename .* '
2031 r'is not supported by subversion\.'),
2033 raise MissingErrorException
2034 except svntest.Failure:
2035 pass
2036 finally:
2037 svntest.main.safe_rmtree(dstrepos_path)
2040 def commit_dependencies():
2041 "interleaved and multi-branch commits to same files"
2042 conv = ensure_conversion("commit-dependencies")
2043 conv.logs[2].check('adding', (
2044 ('/%(trunk)s/interleaved', 'A'),
2045 ('/%(trunk)s/interleaved/file1', 'A'),
2046 ('/%(trunk)s/interleaved/file2', 'A'),
2048 conv.logs[3].check('big commit', (
2049 ('/%(trunk)s/interleaved/file1', 'M'),
2050 ('/%(trunk)s/interleaved/file2', 'M'),
2052 conv.logs[4].check('dependant small commit', (
2053 ('/%(trunk)s/interleaved/file1', 'M'),
2055 conv.logs[5].check('adding', (
2056 ('/%(trunk)s/multi-branch', 'A'),
2057 ('/%(trunk)s/multi-branch/file1', 'A'),
2058 ('/%(trunk)s/multi-branch/file2', 'A'),
2060 conv.logs[6].check(sym_log_msg("branch"), (
2061 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2062 ('/%(branches)s/branch/interleaved', 'D'),
2064 conv.logs[7].check('multi-branch-commit', (
2065 ('/%(trunk)s/multi-branch/file1', 'M'),
2066 ('/%(trunk)s/multi-branch/file2', 'M'),
2067 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2068 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2072 def double_branch_delete():
2073 "fill branches before modifying files on them"
2074 conv = ensure_conversion('double-branch-delete')
2076 # Test for issue #102. The file IMarshalledValue.java is branched,
2077 # deleted, readded on the branch, and then deleted again. If the
2078 # fill for the file on the branch is postponed until after the
2079 # modification, the file will end up live on the branch instead of
2080 # dead! Make sure it happens at the right time.
2082 conv.logs[6].check(sym_log_msg('Branch_4_0'), (
2083 ('/%(branches)s/Branch_4_0/IMarshalledValue.java '
2084 '(from /%(trunk)s/IMarshalledValue.java:5)', 'A'),
2087 conv.logs[7].check('file IMarshalledValue.java was added on branch', (
2088 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'),
2091 conv.logs[8].check('JBAS-2436 - Adding LGPL Header2', (
2092 ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'),
2096 def symbol_mismatches():
2097 "error for conflicting tag/branch"
2099 try:
2100 ensure_conversion('symbol-mess')
2101 raise MissingErrorException
2102 except svntest.Failure:
2103 pass
2106 def force_symbols():
2107 "force symbols to be tags/branches"
2109 conv = ensure_conversion(
2110 'symbol-mess',
2111 args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'])
2112 if conv.path_exists('tags', 'BRANCH') \
2113 or not conv.path_exists('branches', 'BRANCH'):
2114 raise svntest.Failure
2115 if not conv.path_exists('tags', 'TAG') \
2116 or conv.path_exists('branches', 'TAG'):
2117 raise svntest.Failure
2118 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2119 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2120 raise svntest.Failure
2121 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2122 or conv.path_exists('branches', 'MOSTLY_TAG'):
2123 raise svntest.Failure
2126 def commit_blocks_tags():
2127 "commit prevents forced tag"
2129 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2130 try:
2131 ensure_conversion(
2132 'symbol-mess',
2133 args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']))
2134 raise MissingErrorException
2135 except svntest.Failure:
2136 pass
2139 def blocked_excludes():
2140 "error for blocked excludes"
2142 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2143 for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']:
2144 try:
2145 ensure_conversion(
2146 'symbol-mess',
2147 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker]))
2148 raise MissingErrorException
2149 except svntest.Failure:
2150 pass
2153 def unblock_blocked_excludes():
2154 "excluding blocker removes blockage"
2156 basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']
2157 for blocker in ['BRANCH', 'COMMIT']:
2158 ensure_conversion(
2159 'symbol-mess',
2160 args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker,
2161 '--exclude=BLOCKING_%s' % blocker]))
2164 def regexp_force_symbols():
2165 "force symbols via regular expressions"
2167 conv = ensure_conversion(
2168 'symbol-mess',
2169 args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG'])
2170 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2171 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2172 raise svntest.Failure
2173 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2174 or conv.path_exists('branches', 'MOSTLY_TAG'):
2175 raise svntest.Failure
2178 def heuristic_symbol_default():
2179 "test 'heuristic' symbol default"
2181 conv = ensure_conversion(
2182 'symbol-mess', args=['--symbol-default=heuristic'])
2183 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2184 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2185 raise svntest.Failure
2186 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2187 or conv.path_exists('branches', 'MOSTLY_TAG'):
2188 raise svntest.Failure
2191 def branch_symbol_default():
2192 "test 'branch' symbol default"
2194 conv = ensure_conversion(
2195 'symbol-mess', args=['--symbol-default=branch'])
2196 if conv.path_exists('tags', 'MOSTLY_BRANCH') \
2197 or not conv.path_exists('branches', 'MOSTLY_BRANCH'):
2198 raise svntest.Failure
2199 if conv.path_exists('tags', 'MOSTLY_TAG') \
2200 or not conv.path_exists('branches', 'MOSTLY_TAG'):
2201 raise svntest.Failure
2204 def tag_symbol_default():
2205 "test 'tag' symbol default"
2207 conv = ensure_conversion(
2208 'symbol-mess', args=['--symbol-default=tag'])
2209 if not conv.path_exists('tags', 'MOSTLY_BRANCH') \
2210 or conv.path_exists('branches', 'MOSTLY_BRANCH'):
2211 raise svntest.Failure
2212 if not conv.path_exists('tags', 'MOSTLY_TAG') \
2213 or conv.path_exists('branches', 'MOSTLY_TAG'):
2214 raise svntest.Failure
2217 #----------------------------------------------------------------------
2219 ########################################################################
2220 # Run the tests
2222 # list all tests here, starting with None:
2223 test_list = [ None,
2224 show_usage, # 1
2225 attr_exec,
2226 space_fname,
2227 two_quick,
2228 prune_with_care,
2229 interleaved_commits,
2230 simple_commits,
2231 simple_tags,
2232 simple_branch_commits,
2233 mixed_time_tag, # 10
2234 mixed_time_branch_with_added_file,
2235 mixed_commit,
2236 split_time_branch,
2237 bogus_tag,
2238 overlapping_branch,
2239 phoenix_branch,
2240 ctrl_char_in_log,
2241 overdead,
2242 no_trunk_prune,
2243 double_delete, # 20
2244 split_branch,
2245 resync_misgroups,
2246 tagged_branch_and_trunk,
2247 enroot_race,
2248 enroot_race_obo,
2249 branch_delete_first,
2250 nonascii_filenames,
2251 vendor_branch_sameness,
2252 default_branches,
2253 compose_tag_three_sources, # 30
2254 pass5_when_to_fill,
2255 peer_path_pruning,
2256 empty_trunk,
2257 no_spurious_svn_commits,
2258 invalid_closings_on_trunk,
2259 individual_passes,
2260 resync_bug,
2261 branch_from_default_branch,
2262 file_in_attic_too,
2263 symbolic_name_filling_guide, # 40
2264 eol_mime,
2265 keywords,
2266 ignore,
2267 requires_cvs,
2268 questionable_branch_names,
2269 questionable_tag_names,
2270 revision_reorder_bug,
2271 exclude,
2272 vendor_branch_delete_add,
2273 resync_pass2_pull_forward, # 50
2274 native_eol,
2275 double_fill,
2276 resync_pass2_push_backward,
2277 double_add,
2278 bogus_branch_copy,
2279 nested_ttb_directories,
2280 prune_with_care_variants,
2281 simple_tags_variants,
2282 phoenix_branch_variants,
2283 no_trunk_prune_variants, # 60
2284 tagged_branch_and_trunk_variants,
2285 branch_delete_first_variants,
2286 empty_trunk_variants,
2287 peer_path_pruning_variants,
2288 auto_props_ignore_case,
2289 auto_props,
2290 ctrl_char_in_filename,
2291 commit_dependencies,
2292 show_help_passes,
2293 multiple_tags, # 70
2294 double_branch_delete,
2295 symbol_mismatches,
2296 force_symbols,
2297 commit_blocks_tags,
2298 blocked_excludes,
2299 unblock_blocked_excludes,
2300 regexp_force_symbols,
2301 heuristic_symbol_default,
2302 branch_symbol_default,
2303 tag_symbol_default, # 80
2306 if __name__ == '__main__':
2308 # The Subversion test suite code assumes it's being invoked from
2309 # within a working copy of the Subversion sources, and tries to use
2310 # the binaries in that tree. Since the cvs2svn tree never contains
2311 # a Subversion build, we just use the system's installed binaries.
2312 svntest.main.svn_binary = 'svn'
2313 svntest.main.svnlook_binary = 'svnlook'
2314 svntest.main.svnadmin_binary = 'svnadmin'
2315 svntest.main.svnversion_binary = 'svnversion'
2317 svntest.main.run_tests(test_list)
2318 # NOTREACHED
2321 ### End of file.