Avoid 'path name too long' error when running tests by using relative paths.
[cvs2svn.git] / run-tests.py
blob590e31a1e1dff978c12afad66ee01be261ebabcd
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 string
41 import re
42 import os
43 import time
44 import os.path
45 import locale
47 # This script needs to run in the correct directory. Make sure we're there.
48 if not (os.path.exists('cvs2svn') and os.path.exists('test-data')):
49 sys.stderr.write("error: I need to be run in the directory containing "
50 "'cvs2svn' and 'test-data'.\n")
51 sys.exit(1)
53 # Load the Subversion test framework.
54 import svntest
56 # Abbreviations
57 Skip = svntest.testcase.Skip
58 XFail = svntest.testcase.XFail
59 Item = svntest.wc.StateItem
61 # Always run from inside the tmp_dir, so use appropriate relative path.
62 cvs2svn = os.path.join('..', 'cvs2svn')
64 # We use the installed svn and svnlook binaries, instead of using
65 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
66 # behavior -- or even existence -- of local builds shouldn't affect
67 # the cvs2svn test suite.
68 svn = 'svn'
69 svnlook = 'svnlook'
71 test_data_dir = 'test-data'
72 tmp_dir = 'tmp'
75 #----------------------------------------------------------------------
76 # Helpers.
77 #----------------------------------------------------------------------
80 class RunProgramException:
81 pass
83 class MissingErrorException:
84 pass
86 def run_program(program, error_re, *varargs):
87 """Run PROGRAM with VARARGS, return stdout as a list of lines.
88 If there is any stderr and ERROR_RE is None, raise
89 RunProgramException, and print the stderr lines if
90 svntest.main.verbose_mode is true.
92 If ERROR_RE is not None, it is a string regular expression that must
93 match some line of stderr. If it fails to match, raise
94 MissingErrorExpection."""
95 out, err = svntest.main.run_command(program, 1, 0, *varargs)
96 if err:
97 if error_re:
98 for line in err:
99 if re.match(error_re, line):
100 return out
101 raise MissingErrorException
102 else:
103 if svntest.main.verbose_mode:
104 print '\n%s said:\n' % program
105 for line in err:
106 print ' ' + line,
107 print
108 raise RunProgramException
109 return out
112 def run_cvs2svn(error_re, *varargs):
113 """Run cvs2svn with VARARGS, return stdout as a list of lines.
114 If there is any stderr and ERROR_RE is None, raise
115 RunProgramException, and print the stderr lines if
116 svntest.main.verbose_mode is true.
118 If ERROR_RE is not None, it is a string regular expression that must
119 match some line of stderr. If it fails to match, raise
120 MissingErrorException."""
121 # Use the same python that is running this script
122 return run_program(sys.executable, error_re, cvs2svn, *varargs)
123 # On Windows, for an unknown reason, the cmd.exe process invoked by
124 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
125 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
126 # this. Therefore, the redirection of the output to the .s-revs file fails.
127 # We no longer use the problematic invocation on any system, but this
128 # comment remains to warn about this problem.
131 def run_svn(*varargs):
132 """Run svn with VARARGS; return stdout as a list of lines.
133 If there is any stderr, raise RunProgramException, and print the
134 stderr lines if svntest.main.verbose_mode is true."""
135 return run_program(svn, None, *varargs)
138 def repos_to_url(path_to_svn_repos):
139 """This does what you think it does."""
140 rpath = os.path.abspath(path_to_svn_repos)
141 if rpath[0] != '/':
142 rpath = '/' + rpath
143 return 'file://%s' % string.replace(rpath, os.sep, '/')
145 if hasattr(time, 'strptime'):
146 def svn_strptime(timestr):
147 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
148 else:
149 # This is for Python earlier than 2.3 on Windows
150 _re_rev_date = re.compile(r'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)')
151 def svn_strptime(timestr):
152 matches = _re_rev_date.match(timestr).groups()
153 return tuple(map(int, matches)) + (0, 1, -1)
155 class Log:
156 def __init__(self, revision, author, date, symbols):
157 self.revision = revision
158 self.author = author
160 # Internally, we represent the date as seconds since epoch (UTC).
161 # Since standard subversion log output shows dates in localtime
163 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
165 # and time.mktime() converts from localtime, it all works out very
166 # happily.
167 self.date = time.mktime(svn_strptime(date[0:19]))
169 # The following symbols are used for string interpolation when
170 # checking paths:
171 self.symbols = symbols
173 # The changed paths will be accumulated later, as log data is read.
174 # Keys here are paths such as '/trunk/foo/bar', values are letter
175 # codes such as 'M', 'A', and 'D'.
176 self.changed_paths = { }
178 # The msg will be accumulated later, as log data is read.
179 self.msg = ''
181 def absorb_changed_paths(self, out):
182 'Read changed paths from OUT into self, until no more.'
183 while 1:
184 line = out.readline()
185 if len(line) == 1: return
186 line = line[:-1]
187 op_portion = line[3:4]
188 path_portion = line[5:]
189 # If we're running on Windows we get backslashes instead of
190 # forward slashes.
191 path_portion = path_portion.replace('\\', '/')
192 # # We could parse out history information, but currently we
193 # # just leave it in the path portion because that's how some
194 # # tests expect it.
196 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
197 # if m:
198 # path_portion = m.group(1)
199 self.changed_paths[path_portion] = op_portion
201 def __cmp__(self, other):
202 return cmp(self.revision, other.revision) or \
203 cmp(self.author, other.author) or cmp(self.date, other.date) or \
204 cmp(self.changed_paths, other.changed_paths) or \
205 cmp(self.msg, other.msg)
207 def get_path_op(self, path):
208 """Return the operator for the change involving PATH.
210 PATH is allowed to include string interpolation directives (e.g.,
211 '%(trunk)s'), which are interpolated against self.symbols. Return
212 None if there is no record for PATH."""
213 return self.changed_paths.get(path % self.symbols)
215 def check_msg(self, msg):
216 """Verify that this Log's message starts with the specified MSG."""
217 if self.msg.find(msg) != 0:
218 raise svntest.Failure(
219 "Revision %d log message was:\n%s\n\n"
220 "It should have begun with:\n%s\n\n"
221 % (self.revision, self.msg, msg,)
224 def check_change(self, path, op):
225 """Verify that this Log includes a change for PATH with operator OP.
227 PATH is allowed to include string interpolation directives (e.g.,
228 '%(trunk)s'), which are interpolated against self.symbols."""
230 path = path % self.symbols
231 found_op = self.changed_paths.get(path, None)
232 if found_op is None:
233 raise svntest.Failure(
234 "Revision %d does not include change for path %s "
235 "(it should have been %s).\n"
236 % (self.revision, path, op,)
238 if found_op != op:
239 raise svntest.Failure(
240 "Revision %d path %s had op %s (it should have been %s)\n"
241 % (self.revision, path, found_op, op,)
244 def check_changes(self, changed_paths):
245 """Verify that this Log has precisely the CHANGED_PATHS specified.
247 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
248 strings are allowed to include string interpolation directives
249 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
251 cp = {}
252 for (path, op) in changed_paths:
253 cp[path % self.symbols] = op
255 if self.changed_paths != cp:
256 raise svntest.Failure(
257 "Revision %d changed paths list was:\n%s\n\n"
258 "It should have been:\n%s\n\n"
259 % (self.revision, self.changed_paths, cp,)
262 def check(self, msg, changed_paths):
263 """Verify that this Log has the MSG and CHANGED_PATHS specified.
265 Convenience function to check two things at once. MSG is passed
266 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
268 self.check_msg(msg)
269 self.check_changes(changed_paths)
272 def parse_log(svn_repos, symbols):
273 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
275 Initialize the Logs' symbols with SYMBOLS."""
277 class LineFeeder:
278 'Make a list of lines behave like an open file handle.'
279 def __init__(self, lines):
280 self.lines = lines
281 def readline(self):
282 if len(self.lines) > 0:
283 return self.lines.pop(0)
284 else:
285 return None
287 def absorb_message_body(out, num_lines, log):
288 'Read NUM_LINES of log message body from OUT into Log item LOG.'
289 i = 0
290 while i < num_lines:
291 line = out.readline()
292 log.msg += line
293 i += 1
295 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
296 '(?P<author>[^\|]+) \| '
297 '(?P<date>[^\|]+) '
298 '\| (?P<lines>[0-9]+) (line|lines)$')
300 log_separator = '-' * 72
302 logs = { }
304 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
306 while 1:
307 this_log = None
308 line = out.readline()
309 if not line: break
310 line = line[:-1]
312 if line.find(log_separator) == 0:
313 line = out.readline()
314 if not line: break
315 line = line[:-1]
316 m = log_start_re.match(line)
317 if m:
318 this_log = Log(
319 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
320 line = out.readline()
321 if not line.find('Changed paths:') == 0:
322 print 'unexpected log output (missing changed paths)'
323 print "Line: '%s'" % line
324 sys.exit(1)
325 this_log.absorb_changed_paths(out)
326 absorb_message_body(out, int(m.group('lines')), this_log)
327 logs[this_log.revision] = this_log
328 elif len(line) == 0:
329 break # We've reached the end of the log output.
330 else:
331 print 'unexpected log output (missing revision line)'
332 print "Line: '%s'" % line
333 sys.exit(1)
334 else:
335 print 'unexpected log output (missing log separator)'
336 print "Line: '%s'" % line
337 sys.exit(1)
339 return logs
342 def erase(path):
343 """Unconditionally remove PATH and its subtree, if any. PATH may be
344 non-existent, a file or symlink, or a directory."""
345 if os.path.isdir(path):
346 svntest.main.safe_rmtree(path)
347 elif os.path.exists(path):
348 os.remove(path)
351 def sym_log_msg(symbolic_name, is_tag=None):
352 """Return the expected log message for a cvs2svn-synthesized revision
353 creating branch or tag SYMBOLIC_NAME."""
354 # This is a copy-paste of part of cvs2svn's make_revision_props
355 if is_tag:
356 type = 'tag'
357 else:
358 type = 'branch'
360 # In Python 2.2.3, we could use textwrap.fill(). Oh well :-).
361 if len(symbolic_name) >= 13:
362 space_or_newline = '\n'
363 else:
364 space_or_newline = ' '
366 log = "This commit was manufactured by cvs2svn to create %s%s'%s'." \
367 % (type, space_or_newline, symbolic_name)
369 return log
372 def make_conversion_id(name, args, passbypass):
373 """Create an identifying tag for a conversion.
375 The return value can also be used as part of a filesystem path.
377 NAME is the name of the CVS repository.
379 ARGS are the extra arguments to be passed to cvs2svn.
381 PASSBYPASS is a boolean indicating whether the conversion is to be
382 run one pass at a time.
384 The 1-to-1 mapping between cvs2svn command parameters and
385 conversion_ids allows us to avoid running the same conversion more
386 than once, when multiple tests use exactly the same conversion."""
388 conv_id = name
390 _win32_fname_mapping = { '/': '_sl_', '\\': '_bs_', ':': '_co_',
391 '*': '_st_', '?': '_qm_', '"': '_qq_',
392 '<': '_lt_', '>': '_gt_', '|': '_pi_', }
393 for arg in args:
394 # Replace some characters that Win32 isn't happy about having in a
395 # filename (which was causing the eol_mime test to fail).
396 sanitized_arg = arg
397 for a, b in _win32_fname_mapping.items():
398 sanitized_arg = sanitized_arg.replace(a, b)
399 conv_id = conv_id + sanitized_arg
401 if passbypass:
402 conv_id = conv_id + '-passbypass'
404 return conv_id
407 class Conversion:
408 """A record of a cvs2svn conversion.
410 Fields:
412 conv_id -- the conversion id for this Conversion.
414 name -- a one-word name indicating the involved repositories.
416 repos -- the path to the svn repository.
418 logs -- a dictionary of Log instances, as returned by parse_log().
420 symbols -- a dictionary of symbols used for string interpolation
421 in path names.
423 _wc -- the basename of the svn working copy (within tmp_dir).
425 _wc_path -- the path to the svn working copy, if it has already
426 been created; otherwise, None. (The working copy is created
427 lazily when get_wc() is called.)
429 _wc_tree -- the tree built from the svn working copy, if it has
430 already been created; otherwise, None. The tree is created
431 lazily when get_wc_tree() is called.)
433 _svnrepos -- the basename of the svn repository (within tmp_dir)."""
435 def __init__(self, conv_id, name, error_re, passbypass, symbols, args):
436 self.conv_id = conv_id
437 self.name = name
438 self.symbols = symbols
439 if not os.path.isdir(tmp_dir):
440 os.mkdir(tmp_dir)
442 cvsrepos = os.path.join('..', test_data_dir, '%s-cvsrepos' % self.name)
444 saved_wd = os.getcwd()
445 try:
446 os.chdir(tmp_dir)
448 self._svnrepos = '%s-svnrepos' % self.conv_id
449 self.repos = os.path.join(tmp_dir, self._svnrepos)
450 self._wc = '%s-wc' % self.conv_id
451 self._wc_path = None
452 self._wc_tree = None
454 # Clean up from any previous invocations of this script.
455 erase(self._svnrepos)
456 erase(self._wc)
458 try:
459 args.extend( [ '--bdb-txn-nosync', '-s', self._svnrepos, cvsrepos ] )
460 if passbypass:
461 for p in range(1, 9):
462 run_cvs2svn(error_re, '-p', str(p), *args)
463 else:
464 run_cvs2svn(error_re, *args)
465 except RunProgramException:
466 raise svntest.Failure
467 except MissingErrorException:
468 raise svntest.Failure("Test failed because no error matched '%s'"
469 % error_re)
471 if not os.path.isdir(self._svnrepos):
472 raise svntest.Failure("Repository not created: '%s'"
473 % os.path.join(os.getcwd(), self._svnrepos))
475 self.logs = parse_log(self._svnrepos, self.symbols)
476 finally:
477 os.chdir(saved_wd)
479 def find_tag_log(self, tagname):
480 """Search LOGS for a log message containing 'TAGNAME' and return the
481 log in which it was found."""
482 for i in xrange(len(self.logs), 0, -1):
483 if self.logs[i].msg.find("'"+tagname+"'") != -1:
484 return self.logs[i]
485 raise ValueError("Tag %s not found in logs" % tagname)
487 def get_wc(self):
488 """Return the path to the svn working copy. If it has not been
489 created yet, create it now."""
490 if self._wc_path is None:
491 saved_wd = os.getcwd()
492 try:
493 os.chdir(tmp_dir)
494 run_svn('co', repos_to_url(self._svnrepos), self._wc)
495 self._wc_path = os.path.join(tmp_dir, self._wc)
496 finally:
497 os.chdir(saved_wd)
498 return self._wc_path
500 def get_wc_tree(self):
501 if self._wc_tree is None:
502 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
503 return self._wc_tree
505 def check_props(self, keys, checks):
506 """Helper function for checking lots of properties. For a list of
507 files in the conversion, check that the values of the properties
508 listed in KEYS agree with those listed in CHECKS. CHECKS is a
509 list of tuples: [ (filename, [value, value, ...]), ...], where the
510 values are listed in the same order as the key names are listed in
511 KEYS."""
513 for (file, values) in checks:
514 assert len(values) == len(keys)
515 props = props_for_path(self.get_wc_tree(), file)
516 for i in range(len(keys)):
517 if props.get(keys[i]) != values[i]:
518 raise svntest.Failure(
519 "File %s has property %s set to \"%s\" "
520 "(it should have been \"%s\").\n"
521 % (file, keys[i], props.get(keys[i]), values[i],)
525 # Cache of conversions that have already been done. Keys are conv_id;
526 # values are Conversion instances.
527 already_converted = { }
529 def ensure_conversion(name, error_re=None, passbypass=None,
530 trunk=None, branches=None, tags=None, args=None):
531 """Convert CVS repository NAME to Subversion, but only if it has not
532 been converted before by this invocation of this script. If it has
533 been converted before, return the Conversion object from the
534 previous invocation.
536 If no error, return a Conversion instance.
538 If ERROR_RE is a string, it is a regular expression expected to
539 match some line of stderr printed by the conversion. If there is an
540 error and ERROR_RE is not set, then raise svntest.Failure.
542 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
543 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
545 NAME is just one word. For example, 'main' would mean to convert
546 './test-data/main-cvsrepos', and after the conversion, the resulting
547 Subversion repository would be in './tmp/main-svnrepos', and a
548 checked out head working copy in './tmp/main-wc'.
550 Any other options to pass to cvs2svn should be in ARGS, each element
551 being one option, e.g., '--trunk-only'. If the option takes an
552 argument, include it directly, e.g., '--mime-types=PATH'. The order
553 of elements in ARGS does not matter.
556 # Copy args into a list, then sort them, so we can construct a
557 # reliable conv_id.
558 if args is None:
559 args = []
560 else:
561 args = list(args)
562 args.sort()
564 if trunk is None:
565 trunk = 'trunk'
566 else:
567 args.append('--trunk=%s' % (trunk,))
569 if branches is None:
570 branches = 'branches'
571 else:
572 args.append('--branches=%s' % (branches,))
574 if tags is None:
575 tags = 'tags'
576 else:
577 args.append('--tags=%s' % (tags,))
579 conv_id = make_conversion_id(name, args, passbypass)
581 if not already_converted.has_key(conv_id):
582 try:
583 # Run the conversion and store the result for the rest of this
584 # session:
585 already_converted[conv_id] = Conversion(
586 conv_id, name, error_re, passbypass,
587 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
588 args)
589 except svntest.Failure:
590 # Remember the failure so that a future attempt to run this conversion
591 # does not bother to retry, but fails immediately.
592 already_converted[conv_id] = None
593 raise
595 conv = already_converted[conv_id]
596 if conv is None:
597 raise svntest.Failure
598 return conv
601 #----------------------------------------------------------------------
602 # Tests.
603 #----------------------------------------------------------------------
606 def show_usage():
607 "cvs2svn with no arguments shows usage"
608 saved_wd = os.getcwd()
609 try:
610 os.chdir(tmp_dir)
611 out = run_cvs2svn(None)
612 finally:
613 os.chdir(saved_wd)
615 if (len(out) > 2 and out[0].find('ERROR:') == 0
616 and out[1].find('DBM module')):
617 print 'cvs2svn cannot execute due to lack of proper DBM module.'
618 print 'Exiting without running any further tests.'
619 sys.exit(1)
620 if out[0].find('USAGE') < 0:
621 raise svntest.Failure('Basic cvs2svn invocation failed.')
624 def attr_exec():
625 "detection of the executable flag"
626 if sys.platform == 'win32':
627 raise svntest.Skip
628 conv = ensure_conversion('main')
629 st = os.stat(
630 os.path.join(conv.get_wc(), 'trunk', 'single-files', 'attr-exec'))
631 if not st[0] & stat.S_IXUSR:
632 raise svntest.Failure
635 def space_fname():
636 "conversion of filename with a space"
637 conv = ensure_conversion('main')
638 if not os.path.exists(
639 os.path.join(conv.get_wc(), 'trunk', 'single-files', 'space fname')):
640 raise svntest.Failure
643 def two_quick():
644 "two commits in quick succession"
645 conv = ensure_conversion('main')
646 logs = parse_log(
647 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
648 if len(logs) != 2:
649 raise svntest.Failure
652 def prune_with_care(**kw):
653 "prune, but never too much"
654 # Robert Pluim encountered this lovely one while converting the
655 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
656 # repository (see issue #1302). Step 4 is the doozy:
658 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
659 # revision 2: adds trunk/blah/NEWS
660 # revision 3: deletes trunk/blah/cookie
661 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
662 # revision 5: does nothing
664 # After fixing cvs2svn, the sequence (correctly) looks like this:
666 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
667 # revision 2: adds trunk/blah/NEWS
668 # revision 3: deletes trunk/blah/cookie
669 # revision 4: does nothing [because trunk/blah/cookie already deleted]
670 # revision 5: deletes blah
672 # The difference is in 4 and 5. In revision 4, it's not correct to
673 # prune blah/, because NEWS is still in there, so revision 4 does
674 # nothing now. But when we delete NEWS in 5, that should bubble up
675 # and prune blah/ instead.
677 # ### Note that empty revisions like 4 are probably going to become
678 # ### at least optional, if not banished entirely from cvs2svn's
679 # ### output. Hmmm, or they may stick around, with an extra
680 # ### revision property explaining what happened. Need to think
681 # ### about that. In some sense, it's a bug in Subversion itself,
682 # ### that such revisions don't show up in 'svn log' output.
684 # In the test below, 'trunk/full-prune/first' represents
685 # cookie, and 'trunk/full-prune/second' represents NEWS.
687 conv = ensure_conversion('main', **kw)
689 # Confirm that revision 4 removes '/trunk/full-prune/first',
690 # and that revision 6 removes '/trunk/full-prune'.
692 # Also confirm similar things about '/full-prune-reappear/...',
693 # which is similar, except that later on it reappears, restored
694 # from pruneland, because a file gets added to it.
696 # And finally, a similar thing for '/partial-prune/...', except that
697 # in its case, a permanent file on the top level prevents the
698 # pruning from going farther than the subdirectory containing first
699 # and second.
701 rev = 11
702 for path in ('/%(trunk)s/full-prune/first',
703 '/%(trunk)s/full-prune-reappear/sub/first',
704 '/%(trunk)s/partial-prune/sub/first'):
705 conv.logs[rev].check_change(path, 'D')
707 rev = 13
708 for path in ('/%(trunk)s/full-prune',
709 '/%(trunk)s/full-prune-reappear',
710 '/%(trunk)s/partial-prune/sub'):
711 conv.logs[rev].check_change(path, 'D')
713 rev = 47
714 for path in ('/%(trunk)s/full-prune-reappear',
715 '/%(trunk)s/full-prune-reappear/appears-later'):
716 conv.logs[rev].check_change(path, 'A')
719 def prune_with_care_variants():
720 "prune, with alternate repo layout"
721 prune_with_care(trunk='a', branches='b', tags='c')
722 prune_with_care(trunk='a/1', branches='b/1', tags='c/1')
723 prune_with_care(trunk='a/1', branches='a/2', tags='a/3')
726 def interleaved_commits():
727 "two interleaved trunk commits, different log msgs"
728 # See test-data/main-cvsrepos/proj/README.
729 conv = ensure_conversion('main')
731 # The initial import.
732 rev = 37
733 conv.logs[rev].check('Initial revision', (
734 ('/%(trunk)s/interleaved', 'A'),
735 ('/%(trunk)s/interleaved/1', 'A'),
736 ('/%(trunk)s/interleaved/2', 'A'),
737 ('/%(trunk)s/interleaved/3', 'A'),
738 ('/%(trunk)s/interleaved/4', 'A'),
739 ('/%(trunk)s/interleaved/5', 'A'),
740 ('/%(trunk)s/interleaved/a', 'A'),
741 ('/%(trunk)s/interleaved/b', 'A'),
742 ('/%(trunk)s/interleaved/c', 'A'),
743 ('/%(trunk)s/interleaved/d', 'A'),
744 ('/%(trunk)s/interleaved/e', 'A'),
747 # This PEP explains why we pass the 'log' parameter to these two
748 # nested functions, instead of just inheriting it from the enclosing
749 # scope: http://www.python.org/peps/pep-0227.html
751 def check_letters(log):
752 """Check if REV is the rev where only letters were committed."""
753 log.check('Committing letters only.', (
754 ('/%(trunk)s/interleaved/a', 'M'),
755 ('/%(trunk)s/interleaved/b', 'M'),
756 ('/%(trunk)s/interleaved/c', 'M'),
757 ('/%(trunk)s/interleaved/d', 'M'),
758 ('/%(trunk)s/interleaved/e', 'M'),
761 def check_numbers(log):
762 """Check if REV is the rev where only numbers were committed."""
763 log.check('Committing numbers only.', (
764 ('/%(trunk)s/interleaved/1', 'M'),
765 ('/%(trunk)s/interleaved/2', 'M'),
766 ('/%(trunk)s/interleaved/3', 'M'),
767 ('/%(trunk)s/interleaved/4', 'M'),
768 ('/%(trunk)s/interleaved/5', 'M'),
771 # One of the commits was letters only, the other was numbers only.
772 # But they happened "simultaneously", so we don't assume anything
773 # about which commit appeared first, so we just try both ways.
774 rev = rev + 3
775 try:
776 check_letters(conv.logs[rev])
777 check_numbers(conv.logs[rev + 1])
778 except svntest.Failure:
779 check_numbers(conv.logs[rev])
780 check_letters(conv.logs[rev + 1])
783 def simple_commits():
784 "simple trunk commits"
785 # See test-data/main-cvsrepos/proj/README.
786 conv = ensure_conversion('main')
788 # The initial import.
789 rev = 23
790 conv.logs[rev].check('Initial revision', (
791 ('/%(trunk)s/proj', 'A'),
792 ('/%(trunk)s/proj/default', 'A'),
793 ('/%(trunk)s/proj/sub1', 'A'),
794 ('/%(trunk)s/proj/sub1/default', 'A'),
795 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
796 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
797 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
798 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
799 ('/%(trunk)s/proj/sub2', 'A'),
800 ('/%(trunk)s/proj/sub2/default', 'A'),
801 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
802 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
803 ('/%(trunk)s/proj/sub3', 'A'),
804 ('/%(trunk)s/proj/sub3/default', 'A'),
807 # The first commit.
808 rev = 30
809 conv.logs[rev].check('First commit to proj, affecting two files.', (
810 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
811 ('/%(trunk)s/proj/sub3/default', 'M'),
814 # The second commit.
815 rev = 31
816 conv.logs[rev].check('Second commit to proj, affecting all 7 files.', (
817 ('/%(trunk)s/proj/default', 'M'),
818 ('/%(trunk)s/proj/sub1/default', 'M'),
819 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
820 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
821 ('/%(trunk)s/proj/sub2/default', 'M'),
822 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
823 ('/%(trunk)s/proj/sub3/default', 'M')
827 def simple_tags(**kw):
828 "simple tags and branches with no commits"
829 # See test-data/main-cvsrepos/proj/README.
830 conv = ensure_conversion('main', **kw)
832 # Verify the copy source for the tags we are about to check
833 # No need to verify the copyfrom revision, as simple_commits did that
834 conv.logs[24].check(sym_log_msg('vendorbranch'), (
835 ('/%(branches)s/vendorbranch/proj (from /%(trunk)s/proj:23)', 'A'),
838 fromstr = ' (from /%(branches)s/vendorbranch:25)'
840 # Tag on rev 1.1.1.1 of all files in proj
841 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
842 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
843 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr, 'A'),
844 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
845 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
848 # The same, as a branch
849 conv.logs[26].check(sym_log_msg('B_FROM_INITIALS'), (
850 ('/%(branches)s/B_FROM_INITIALS'+fromstr, 'A'),
851 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
852 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
855 # Tag on rev 1.1.1.1 of all files in proj, except one
856 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
857 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
858 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr, 'A'),
859 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
860 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
861 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
864 # The same, as a branch
865 conv.logs[27].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
866 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr, 'A'),
867 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
868 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
869 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
873 def simple_tags_variants():
874 "simple tags, with alternate repo layout"
875 simple_tags(trunk='a', branches='b', tags='c')
876 simple_tags(trunk='a/1', branches='b/1', tags='c/1')
877 simple_tags(trunk='a/1', branches='a/2', tags='a/3')
880 def simple_branch_commits():
881 "simple branch commits"
882 # See test-data/main-cvsrepos/proj/README.
883 conv = ensure_conversion('main')
885 rev = 35
886 conv.logs[rev].check('Modify three files, on branch B_MIXED.', (
887 ('/%(branches)s/B_MIXED/proj/default', 'M'),
888 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
889 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
893 def mixed_time_tag():
894 "mixed-time tag"
895 # See test-data/main-cvsrepos/proj/README.
896 conv = ensure_conversion('main')
898 log = conv.find_tag_log('T_MIXED')
899 expected = (
900 ('/%(tags)s/T_MIXED (from /%(trunk)s:31)', 'A'),
901 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
902 ('/%(tags)s/T_MIXED/single-files', 'D'),
903 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
904 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
905 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
907 if log.revision == 16:
908 expected.append(('/%(tags)s', 'A'))
909 log.check_changes(expected)
912 def mixed_time_branch_with_added_file():
913 "mixed-time branch, and a file added to the branch"
914 # See test-data/main-cvsrepos/proj/README.
915 conv = ensure_conversion('main')
917 # A branch from the same place as T_MIXED in the previous test,
918 # plus a file added directly to the branch
919 conv.logs[32].check(sym_log_msg('B_MIXED'), (
920 ('/%(branches)s/B_MIXED (from /%(trunk)s:31)', 'A'),
921 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
922 ('/%(branches)s/B_MIXED/single-files', 'D'),
923 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
924 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
925 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
928 conv.logs[34].check('Add a file on branch B_MIXED.', (
929 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
933 def mixed_commit():
934 "a commit affecting both trunk and a branch"
935 # See test-data/main-cvsrepos/proj/README.
936 conv = ensure_conversion('main')
938 conv.logs[36].check(
939 'A single commit affecting one file on branch B_MIXED '
940 'and one on trunk.', (
941 ('/%(trunk)s/proj/sub2/default', 'M'),
942 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
946 def split_time_branch():
947 "branch some trunk files, and later branch the rest"
948 # See test-data/main-cvsrepos/proj/README.
949 conv = ensure_conversion('main')
951 rev = 42
952 # First change on the branch, creating it
953 conv.logs[rev].check(sym_log_msg('B_SPLIT'), (
954 ('/%(branches)s/B_SPLIT (from /%(trunk)s:36)', 'A'),
955 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
956 ('/%(branches)s/B_SPLIT/single-files', 'D'),
957 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
960 conv.logs[rev + 1].check('First change on branch B_SPLIT.', (
961 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
962 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
963 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
964 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
965 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
968 # A trunk commit for the file which was not branched
969 conv.logs[rev + 2].check('A trunk change to sub1/subsubB/default. '
970 'This was committed about an', (
971 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
974 # Add the file not already branched to the branch, with modification:w
975 conv.logs[rev + 3].check(sym_log_msg('B_SPLIT'), (
976 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
977 '(from /%(trunk)s/proj/sub1/subsubB:44)', 'A'),
980 conv.logs[rev + 4].check('This change affects sub3/default and '
981 'sub1/subsubB/default, on branch', (
982 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
983 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
987 def bogus_tag():
988 "conversion of invalid symbolic names"
989 conv = ensure_conversion('bogus-tag')
992 def overlapping_branch():
993 "ignore a file with a branch with two names"
994 conv = ensure_conversion('overlapping-branch',
995 error_re='.*cannot also have name \'vendorB\'')
996 rev = 4
997 conv.logs[rev].check_change('/%(branches)s/vendorA (from /%(trunk)s:3)',
998 'A')
999 # We don't know what order the first two commits would be in, since
1000 # they have different log messages but the same timestamps. As only
1001 # one of the files would be on the vendorB branch in the regression
1002 # case being tested here, we allow for either order.
1003 if (conv.logs[rev].get_path_op(
1004 '/%(branches)s/vendorB (from /%(trunk)s:2)') == 'A'
1005 or conv.logs[rev].get_path_op(
1006 '/%(branches)s/vendorB (from /%(trunk)s:3)') == 'A'):
1007 raise svntest.Failure
1008 conv.logs[rev + 1].check_changes(())
1009 if len(conv.logs) != rev + 1:
1010 raise svntest.Failure
1013 def phoenix_branch(**kw):
1014 "convert a branch file rooted in a 'dead' revision"
1015 conv = ensure_conversion('phoenix', **kw)
1016 conv.logs[8].check(sym_log_msg('volsung_20010721'), (
1017 ('/%(branches)s/volsung_20010721 (from /%(trunk)s:7)', 'A'),
1018 ('/%(branches)s/volsung_20010721/file.txt', 'D'),
1020 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1021 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1025 def phoenix_branch_variants():
1026 "'dead' revision, with alternate repo layout"
1027 phoenix_branch(trunk='a/1', branches='b/1', tags='c/1')
1030 ###TODO: We check for 4 changed paths here to accomodate creating tags
1031 ###and branches in rev 1, but that will change, so this will
1032 ###eventually change back.
1033 def ctrl_char_in_log():
1034 "handle a control char in a log message"
1035 # This was issue #1106.
1036 rev = 2
1037 conv = ensure_conversion('ctrl-char-in-log')
1038 conv.logs[rev].check_changes((
1039 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1041 if conv.logs[rev].msg.find('\x04') < 0:
1042 raise svntest.Failure(
1043 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1046 def overdead():
1047 "handle tags rooted in a redeleted revision"
1048 conv = ensure_conversion('overdead')
1051 def no_trunk_prune(**kw):
1052 "ensure that trunk doesn't get pruned"
1053 conv = ensure_conversion('overdead', **kw)
1054 for rev in conv.logs.keys():
1055 rev_logs = conv.logs[rev]
1056 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1057 raise svntest.Failure
1060 def no_trunk_prune_variants():
1061 "no trunk pruning, with alternate repo layout"
1062 no_trunk_prune(trunk='a', branches='b', tags='c')
1063 no_trunk_prune(trunk='a/1', branches='b/1', tags='c/1')
1064 no_trunk_prune(trunk='a/1', branches='a/2', tags='a/3')
1067 def double_delete():
1068 "file deleted twice, in the root of the repository"
1069 # This really tests several things: how we handle a file that's
1070 # removed (state 'dead') in two successive revisions; how we
1071 # handle a file in the root of the repository (there were some
1072 # bugs in cvs2svn's svn path construction for top-level files); and
1073 # the --no-prune option.
1074 conv = ensure_conversion(
1075 'double-delete', args=['--trunk-only', '--no-prune'])
1077 path = '/%(trunk)s/twice-removed'
1078 rev = 2
1079 conv.logs[rev].check_change(path, 'A')
1080 conv.logs[rev].check_msg('Initial revision')
1082 conv.logs[rev + 1].check_change(path, 'D')
1083 conv.logs[rev + 1].check_msg('Remove this file for the first time.')
1085 if conv.logs[rev + 1].get_path_op('/%(trunk)s') is not None:
1086 raise svntest.Failure
1089 def split_branch():
1090 "branch created from both trunk and another branch"
1091 # See test-data/split-branch-cvsrepos/README.
1093 # The conversion will fail if the bug is present, and
1094 # ensure_conversion will raise svntest.Failure.
1095 conv = ensure_conversion('split-branch')
1098 def resync_misgroups():
1099 "resyncing should not misorder commit groups"
1100 # See test-data/resync-misgroups-cvsrepos/README.
1102 # The conversion will fail if the bug is present, and
1103 # ensure_conversion will raise svntest.Failure.
1104 conv = ensure_conversion('resync-misgroups')
1107 def tagged_branch_and_trunk(**kw):
1108 "allow tags with mixed trunk and branch sources"
1109 conv = ensure_conversion('tagged-branch-n-trunk', **kw)
1111 tags = kw.get('tags', 'tags')
1113 a_path = os.path.join(conv.get_wc(), tags, 'some-tag', 'a.txt')
1114 b_path = os.path.join(conv.get_wc(), tags, 'some-tag', 'b.txt')
1115 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1116 raise svntest.Failure
1117 if (open(a_path, 'r').read().find('1.24') == -1) \
1118 or (open(b_path, 'r').read().find('1.5') == -1):
1119 raise svntest.Failure
1122 def tagged_branch_and_trunk_variants():
1123 "mixed tags, with alternate repo layout"
1124 tagged_branch_and_trunk(trunk='a/1', branches='a/2', tags='a/3')
1127 def enroot_race():
1128 "never use the rev-in-progress as a copy source"
1129 # See issue #1427 and r8544.
1130 conv = ensure_conversion('enroot-race')
1131 rev = 8
1132 conv.logs[rev].check_changes((
1133 ('/%(branches)s/mybranch (from /%(trunk)s:7)', 'A'),
1134 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1135 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1137 conv.logs[rev + 1].check_changes((
1138 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1139 ('/%(trunk)s/proj/a.txt', 'M'),
1140 ('/%(trunk)s/proj/b.txt', 'M'),
1144 def enroot_race_obo():
1145 "do use the last completed rev as a copy source"
1146 conv = ensure_conversion('enroot-race-obo')
1147 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1148 if not len(conv.logs) == 3:
1149 raise svntest.Failure
1152 def branch_delete_first(**kw):
1153 "correctly handle deletion as initial branch action"
1154 # See test-data/branch-delete-first-cvsrepos/README.
1156 # The conversion will fail if the bug is present, and
1157 # ensure_conversion would raise svntest.Failure.
1158 conv = ensure_conversion('branch-delete-first', **kw)
1160 branches = kw.get('branches', 'branches')
1162 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1163 if os.path.exists(
1164 os.path.join(conv.get_wc(), branches, 'branch-1', 'file')):
1165 raise svntest.Failure
1166 if os.path.exists(
1167 os.path.join(conv.get_wc(), branches, 'branch-2', 'file')):
1168 raise svntest.Failure
1169 if not os.path.exists(
1170 os.path.join(conv.get_wc(), branches, 'branch-3', 'file')):
1171 raise svntest.Failure
1174 def branch_delete_first_variants():
1175 "initial delete, with alternate repo layout"
1176 branch_delete_first(trunk='a/1', branches='a/2', tags='a/3')
1179 def nonascii_filenames():
1180 "non ascii files converted incorrectly"
1181 # see issue #1255
1183 # on a en_US.iso-8859-1 machine this test fails with
1184 # svn: Can't recode ...
1186 # as described in the issue
1188 # on a en_US.UTF-8 machine this test fails with
1189 # svn: Malformed XML ...
1191 # which means at least it fails. Unfortunately it won't fail
1192 # with the same error...
1194 # mangle current locale settings so we know we're not running
1195 # a UTF-8 locale (which does not exhibit this problem)
1196 current_locale = locale.getlocale()
1197 new_locale = 'en_US.ISO8859-1'
1198 locale_changed = None
1200 # From http://docs.python.org/lib/module-sys.html
1202 # getfilesystemencoding():
1204 # Return the name of the encoding used to convert Unicode filenames
1205 # into system file names, or None if the system default encoding is
1206 # used. The result value depends on the operating system:
1208 # - On Windows 9x, the encoding is ``mbcs''.
1209 # - On Mac OS X, the encoding is ``utf-8''.
1210 # - On Unix, the encoding is the user's preference according to the
1211 # result of nl_langinfo(CODESET), or None if the
1212 # nl_langinfo(CODESET) failed.
1213 # - On Windows NT+, file names are Unicode natively, so no conversion is
1214 # performed.
1216 # So we're going to skip this test on Mac OS X for now.
1217 if sys.platform == "darwin":
1218 raise svntest.Skip
1220 try:
1221 # change locale to non-UTF-8 locale to generate latin1 names
1222 locale.setlocale(locale.LC_ALL, # this might be too broad?
1223 new_locale)
1224 locale_changed = 1
1225 except locale.Error:
1226 raise svntest.Skip
1228 try:
1229 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
1230 dstrepos_path = os.path.join(test_data_dir,'non-ascii-cvsrepos')
1231 if not os.path.exists(dstrepos_path):
1232 # create repos from existing main repos
1233 shutil.copytree(srcrepos_path, dstrepos_path)
1234 base_path = os.path.join(dstrepos_path, 'single-files')
1235 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1236 os.path.join(base_path, 'two\366uick,v'))
1237 new_path = os.path.join(dstrepos_path, 'single\366files')
1238 os.rename(base_path, new_path)
1240 # if ensure_conversion can generate a
1241 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1242 finally:
1243 if locale_changed:
1244 locale.setlocale(locale.LC_ALL, current_locale)
1245 svntest.main.safe_rmtree(dstrepos_path)
1248 def vendor_branch_sameness():
1249 "avoid spurious changes for initial revs"
1250 conv = ensure_conversion('vendor-branch-sameness')
1252 # There are four files in the repository:
1254 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1255 # the same contents, the file's default branch is 1.1.1,
1256 # and both revisions are in state 'Exp'.
1258 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1259 # 1.1 (the addition of a line of text).
1261 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1263 # d.txt: This file was created by 'cvs add' instead of import, so
1264 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1265 # The timestamp on the add is exactly the same as for the
1266 # imports of the other files.
1268 # (Log messages for the same revisions are the same in all files.)
1270 # What we expect to see is everyone added in r1, then trunk/proj
1271 # copied in r2. In the copy, only a.txt should be left untouched;
1272 # b.txt should be 'M'odified, and (for different reasons) c.txt and
1273 # d.txt should be 'D'eleted.
1275 rev = 2
1276 conv.logs[rev].check('Initial revision', (
1277 ('/%(trunk)s/proj', 'A'),
1278 ('/%(trunk)s/proj/a.txt', 'A'),
1279 ('/%(trunk)s/proj/b.txt', 'A'),
1280 ('/%(trunk)s/proj/c.txt', 'A'),
1281 ('/%(trunk)s/proj/d.txt', 'A'),
1284 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1285 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1286 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1289 conv.logs[rev + 2].check('First vendor branch revision.', (
1290 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1291 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1295 def default_branches():
1296 "handle default branches correctly"
1297 conv = ensure_conversion('default-branches')
1299 # There are seven files in the repository:
1301 # a.txt:
1302 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1303 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1304 # committed (thus losing the default branch "1.1.1"), then
1305 # 1.1.1.4 was imported. All vendor import release tags are
1306 # still present.
1308 # b.txt:
1309 # Like a.txt, but without rev 1.2.
1311 # c.txt:
1312 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1314 # d.txt:
1315 # Same as the previous two, but 1.1.1 branch is unlabeled.
1317 # e.txt:
1318 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1320 # deleted-on-vendor-branch.txt,v:
1321 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1323 # added-then-imported.txt,v:
1324 # Added with 'cvs add' to create 1.1, then imported with
1325 # completely different contents to create 1.1.1.1, therefore
1326 # never had a default branch.
1329 conv.logs[18].check(sym_log_msg('vtag-4',1), (
1330 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:16)', 'A'),
1331 ('/%(tags)s/vtag-4/proj/d.txt '
1332 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'A'),
1335 conv.logs[6].check(sym_log_msg('vtag-1',1), (
1336 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:5)', 'A'),
1337 ('/%(tags)s/vtag-1/proj/d.txt '
1338 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1341 conv.logs[9].check(sym_log_msg('vtag-2',1), (
1342 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:7)', 'A'),
1343 ('/%(tags)s/vtag-2/proj/d.txt '
1344 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'A'),
1347 conv.logs[12].check(sym_log_msg('vtag-3',1), (
1348 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:10)', 'A'),
1349 ('/%(tags)s/vtag-3/proj/d.txt '
1350 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'A'),
1351 ('/%(tags)s/vtag-3/proj/e.txt '
1352 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'A'),
1355 conv.logs[17].check("This commit was generated by cvs2svn "
1356 "to compensate for changes in r16,", (
1357 ('/%(trunk)s/proj/b.txt '
1358 '(from /%(branches)s/vbranchA/proj/b.txt:16)', 'R'),
1359 ('/%(trunk)s/proj/c.txt '
1360 '(from /%(branches)s/vbranchA/proj/c.txt:16)', 'R'),
1361 ('/%(trunk)s/proj/d.txt '
1362 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'R'),
1363 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1364 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:16)',
1365 'A'),
1366 ('/%(trunk)s/proj/e.txt '
1367 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:16)', 'R'),
1370 conv.logs[16].check("Import (vbranchA, vtag-4).", (
1371 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1372 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1373 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1374 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1375 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1376 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1377 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1380 conv.logs[15].check(sym_log_msg('vbranchA'), (
1381 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1382 '(from /%(trunk)s/proj/added-then-imported.txt:14)', 'A'),
1385 conv.logs[14].check("Add a file to the working copy.", (
1386 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1389 conv.logs[13].check("First regular commit, to a.txt, on vtag-3.", (
1390 ('/%(trunk)s/proj/a.txt', 'M'),
1393 conv.logs[11].check("This commit was generated by cvs2svn "
1394 "to compensate for changes in r10,", (
1395 ('/%(trunk)s/proj/a.txt '
1396 '(from /%(branches)s/vbranchA/proj/a.txt:10)', 'R'),
1397 ('/%(trunk)s/proj/b.txt '
1398 '(from /%(branches)s/vbranchA/proj/b.txt:10)', 'R'),
1399 ('/%(trunk)s/proj/c.txt '
1400 '(from /%(branches)s/vbranchA/proj/c.txt:10)', 'R'),
1401 ('/%(trunk)s/proj/d.txt '
1402 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'R'),
1403 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1404 ('/%(trunk)s/proj/e.txt '
1405 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'R'),
1408 conv.logs[10].check("Import (vbranchA, vtag-3).", (
1409 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1410 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1411 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1412 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1413 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1414 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1417 conv.logs[8].check("This commit was generated by cvs2svn "
1418 "to compensate for changes in r7,", (
1419 ('/%(trunk)s/proj/a.txt '
1420 '(from /%(branches)s/vbranchA/proj/a.txt:7)', 'R'),
1421 ('/%(trunk)s/proj/b.txt '
1422 '(from /%(branches)s/vbranchA/proj/b.txt:7)', 'R'),
1423 ('/%(trunk)s/proj/c.txt '
1424 '(from /%(branches)s/vbranchA/proj/c.txt:7)', 'R'),
1425 ('/%(trunk)s/proj/d.txt '
1426 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'R'),
1427 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1428 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:7)',
1429 'R'),
1430 ('/%(trunk)s/proj/e.txt '
1431 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:7)', 'R'),
1434 conv.logs[7].check("Import (vbranchA, vtag-2).", (
1435 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1436 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1437 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1438 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1439 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1440 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1443 conv.logs[5].check("Import (vbranchA, vtag-1).", ())
1445 conv.logs[4].check(sym_log_msg('vbranchA'), (
1446 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1447 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1448 ('/%(branches)s/vbranchA/proj/e.txt', 'D'),
1451 conv.logs[3].check(sym_log_msg('unlabeled-1.1.1'), (
1452 ('/%(branches)s/unlabeled-1.1.1 (from /%(trunk)s:2)', 'A'),
1453 ('/%(branches)s/unlabeled-1.1.1/proj/a.txt', 'D'),
1454 ('/%(branches)s/unlabeled-1.1.1/proj/b.txt', 'D'),
1455 ('/%(branches)s/unlabeled-1.1.1/proj/c.txt', 'D'),
1456 ('/%(branches)s/unlabeled-1.1.1/proj/deleted-on-vendor-branch.txt', 'D'),
1459 conv.logs[2].check("Initial revision", (
1460 ('/%(trunk)s/proj', 'A'),
1461 ('/%(trunk)s/proj/a.txt', 'A'),
1462 ('/%(trunk)s/proj/b.txt', 'A'),
1463 ('/%(trunk)s/proj/c.txt', 'A'),
1464 ('/%(trunk)s/proj/d.txt', 'A'),
1465 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1466 ('/%(trunk)s/proj/e.txt', 'A'),
1470 def compose_tag_three_sources():
1471 "compose a tag from three sources"
1472 conv = ensure_conversion('compose-tag-three-sources')
1474 conv.logs[2].check("Add on trunk", (
1475 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
1476 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
1477 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
1478 ('/%(trunk)s/tagged-on-b1', 'A'),
1479 ('/%(trunk)s/tagged-on-b2', 'A'),
1482 conv.logs[3].check(sym_log_msg('b1'), (
1483 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
1486 conv.logs[4].check(sym_log_msg('b2'), (
1487 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
1490 conv.logs[5].check("Commit on branch b1", (
1491 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
1492 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
1493 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
1494 ('/%(branches)s/b1/tagged-on-b1', 'M'),
1495 ('/%(branches)s/b1/tagged-on-b2', 'M'),
1498 conv.logs[6].check("Commit on branch b2", (
1499 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
1500 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
1501 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
1502 ('/%(branches)s/b2/tagged-on-b1', 'M'),
1503 ('/%(branches)s/b2/tagged-on-b2', 'M'),
1506 conv.logs[7].check("Commit again on trunk", (
1507 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
1508 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
1509 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
1510 ('/%(trunk)s/tagged-on-b1', 'M'),
1511 ('/%(trunk)s/tagged-on-b2', 'M'),
1514 conv.logs[8].check(sym_log_msg('T',1), (
1515 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
1516 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:7)', 'R'),
1517 ('/%(tags)s/T/tagged-on-trunk-1.1 '
1518 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
1519 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:7)', 'R'),
1523 def pass5_when_to_fill():
1524 "reserve a svn revnum for a fill only when required"
1525 # The conversion will fail if the bug is present, and
1526 # ensure_conversion would raise svntest.Failure.
1527 conv = ensure_conversion('pass5-when-to-fill')
1530 def empty_trunk(**kw):
1531 "don't break when the trunk is empty"
1532 # The conversion will fail if the bug is present, and
1533 # ensure_conversion would raise svntest.Failure.
1534 conv = ensure_conversion('empty-trunk', **kw)
1537 def empty_trunk_variants():
1538 "empty trunk, with alternate repo layout"
1539 empty_trunk(trunk='a', branches='b', tags='c')
1540 empty_trunk(trunk='a/1', branches='a/2', tags='a/3')
1543 def no_spurious_svn_commits():
1544 "ensure that we don't create any spurious commits"
1545 conv = ensure_conversion('phoenix')
1547 # Check spurious commit that could be created in CVSCommit._pre_commit
1548 # (When you add a file on a branch, CVS creates a trunk revision
1549 # in state 'dead'. If the log message of that commit is equal to
1550 # the one that CVS generates, we do not ever create a 'fill'
1551 # SVNCommit for it.)
1553 # and spurious commit that could be created in CVSCommit._commit
1554 # (When you add a file on a branch, CVS creates a trunk revision
1555 # in state 'dead'. If the log message of that commit is equal to
1556 # the one that CVS generates, we do not create a primary SVNCommit
1557 # for it.)
1558 conv.logs[18].check('File added on branch xiphophorus', (
1559 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
1562 # Check to make sure that a commit *is* generated:
1563 # (When you add a file on a branch, CVS creates a trunk revision
1564 # in state 'dead'. If the log message of that commit is NOT equal
1565 # to the one that CVS generates, we create a primary SVNCommit to
1566 # serve as a home for the log message in question.
1567 conv.logs[19].check('file added-on-branch2.txt was initially added on '
1568 + 'branch xiphophorus,\nand this log message was tweaked', ())
1570 # Check spurious commit that could be created in
1571 # CVSRevisionAggregator.attempt_to_commit_symbols
1572 # (We shouldn't consider a CVSRevision whose op is OP_DEAD as a
1573 # candidate for the LastSymbolicNameDatabase.
1574 conv.logs[20].check('This file was also added on branch xiphophorus,', (
1575 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
1579 def peer_path_pruning(**kw):
1580 "make sure that filling prunes paths correctly"
1581 conv = ensure_conversion('peer-path-pruning', **kw)
1582 conv.logs[8].check(sym_log_msg('BRANCH'), (
1583 ('/%(branches)s/BRANCH (from /%(trunk)s:6)', 'A'),
1584 ('/%(branches)s/BRANCH/bar', 'D'),
1585 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:7)', 'R'),
1589 def peer_path_pruning_variants():
1590 "filling prune paths, with alternate repo layout"
1591 peer_path_pruning(trunk='a/1', branches='a/2', tags='a/3')
1594 def invalid_closings_on_trunk():
1595 "verify correct revs are copied to default branches"
1596 # The conversion will fail if the bug is present, and
1597 # ensure_conversion would raise svntest.Failure.
1598 conv = ensure_conversion('invalid-closings-on-trunk')
1601 def individual_passes():
1602 "run each pass individually"
1603 conv = ensure_conversion('main')
1604 conv2 = ensure_conversion('main', passbypass=1)
1606 if conv.logs != conv2.logs:
1607 raise svntest.Failure
1610 def resync_bug():
1611 "reveal a big bug in our resync algorithm"
1612 # This will fail if the bug is present
1613 conv = ensure_conversion('resync-bug')
1616 def branch_from_default_branch():
1617 "reveal a bug in our default branch detection code"
1618 conv = ensure_conversion('branch-from-default-branch')
1620 # This revision will be a default branch synchronization only
1621 # if cvs2svn is correctly determining default branch revisions.
1623 # The bug was that cvs2svn was treating revisions on branches off of
1624 # default branches as default branch revisions, resulting in
1625 # incorrectly regarding the branch off of the default branch as a
1626 # non-trunk default branch. Crystal clear? I thought so. See
1627 # issue #42 for more incoherent blathering.
1628 conv.logs[6].check("This commit was generated by cvs2svn", (
1629 ('/%(trunk)s/proj/file.txt '
1630 '(from /%(branches)s/upstream/proj/file.txt:5)', 'R'),
1633 def file_in_attic_too():
1634 "die if a file exists in and out of the attic"
1635 try:
1636 ensure_conversion('file-in-attic-too')
1637 raise MissingErrorException
1638 except svntest.Failure:
1639 pass
1641 def symbolic_name_filling_guide():
1642 "reveal a big bug in our SymbolicNameFillingGuide"
1643 # This will fail if the bug is present
1644 conv = ensure_conversion('symbolic-name-overfill')
1647 # Helpers for tests involving file contents and properties.
1649 class NodeTreeWalkException:
1650 "Exception class for node tree traversals."
1651 pass
1653 def node_for_path(node, path):
1654 "In the tree rooted under SVNTree NODE, return the node at PATH."
1655 if node.name != '__SVN_ROOT_NODE':
1656 raise NodeTreeWalkException
1657 path = path.strip('/')
1658 components = path.split('/')
1659 for component in components:
1660 node = svntest.tree.get_child(node, component)
1661 return node
1663 # Helper for tests involving properties.
1664 def props_for_path(node, path):
1665 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
1666 return node_for_path(node, path).props
1669 def eol_mime():
1670 "test eol settings and mime types together"
1671 ###TODO: It's a bit klugey to construct this path here. But so far
1672 ### there's only one test with a mime.types file. If we have more,
1673 ### we should abstract this into some helper, which would be located
1674 ### near ensure_conversion(). Note that it is a convention of this
1675 ### test suite for a mime.types file to be located in the top level
1676 ### of the CVS repository to which it applies.
1677 mime_path = os.path.join('..', test_data_dir, 'eol-mime-cvsrepos',
1678 'mime.types')
1680 # We do four conversions. Each time, we pass --mime-types=FILE with
1681 # the same FILE, but vary --no-default-eol and --eol-from-mime-type.
1682 # Thus there's one conversion with neither flag, one with just the
1683 # former, one with just the latter, and one with both.
1685 # In two of the four conversions, we pass --cvs-revnums to make
1686 # certain that there are no bad interactions.
1688 # The files are as follows:
1690 # trunk/foo.txt: no -kb, mime file says nothing.
1691 # trunk/foo.xml: no -kb, mime file says text.
1692 # trunk/foo.zip: no -kb, mime file says non-text.
1693 # trunk/foo.bin: has -kb, mime file says nothing.
1694 # trunk/foo.csv: has -kb, mime file says text.
1695 # trunk/foo.dbf: has -kb, mime file says non-text.
1697 ## Neither --no-default-eol nor --eol-from-mime-type. ##
1698 conv = ensure_conversion(
1699 'eol-mime', args=['--mime-types=%s' % mime_path, '--cvs-revnums'])
1700 conv.check_props(
1701 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1703 ('trunk/foo.txt', ['native', None, '1.2']),
1704 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1705 ('trunk/foo.zip', ['native', 'application/zip', '1.2']),
1706 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1707 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1708 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1712 ## Just --no-default-eol, not --eol-from-mime-type. ##
1713 conv = ensure_conversion(
1714 'eol-mime', args=['--mime-types=%s' % mime_path, '--no-default-eol'])
1715 conv.check_props(
1716 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1718 ('trunk/foo.txt', [None, None, None]),
1719 ('trunk/foo.xml', [None, 'text/xml', None]),
1720 ('trunk/foo.zip', [None, 'application/zip', None]),
1721 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1722 ('trunk/foo.csv', [None, 'text/csv', None]),
1723 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1727 ## Just --eol-from-mime-type, not --no-default-eol. ##
1728 conv = ensure_conversion('eol-mime', args=[
1729 '--mime-types=%s' % mime_path, '--eol-from-mime-type', '--cvs-revnums'
1731 conv.check_props(
1732 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1734 ('trunk/foo.txt', ['native', None, '1.2']),
1735 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1736 ('trunk/foo.zip', [None, 'application/zip', '1.2']),
1737 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1738 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1739 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1743 ## Both --no-default-eol and --eol-from-mime-type. ##
1744 conv = ensure_conversion('eol-mime', args=[
1745 '--mime-types=%s' % mime_path, '--eol-from-mime-type',
1746 '--no-default-eol'])
1747 conv.check_props(
1748 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1750 ('trunk/foo.txt', [None, None, None]),
1751 ('trunk/foo.xml', ['native', 'text/xml', None]),
1752 ('trunk/foo.zip', [None, 'application/zip', None]),
1753 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1754 ('trunk/foo.csv', [None, 'text/csv', None]),
1755 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1760 def keywords():
1761 "test setting of svn:keywords property among others"
1762 conv = ensure_conversion('keywords')
1763 conv.check_props(
1764 ['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
1766 ('trunk/foo.default', ['Author Date Id Revision', 'native', None]),
1767 ('trunk/foo.kkvl', ['Author Date Id Revision', 'native', None]),
1768 ('trunk/foo.kkv', ['Author Date Id Revision', 'native', None]),
1769 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
1770 ('trunk/foo.kk', [None, 'native', None]),
1771 ('trunk/foo.ko', [None, 'native', None]),
1772 ('trunk/foo.kv', [None, 'native', None]),
1777 def ignore():
1778 "test setting of svn:ignore property"
1779 conv = ensure_conversion('cvsignore')
1780 wc_tree = conv.get_wc_tree()
1781 topdir_props = props_for_path(wc_tree, 'trunk/proj')
1782 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
1784 if topdir_props['svn:ignore'] != \
1785 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1786 raise svntest.Failure
1788 if subdir_props['svn:ignore'] != \
1789 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1790 raise svntest.Failure
1793 def requires_cvs():
1794 "test that CVS can still do what RCS can't"
1795 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
1796 conv = ensure_conversion('requires-cvs', args=["--use-cvs"])
1798 atsign_contents = file(
1799 os.path.join(conv.get_wc(), "trunk", "atsign-add")).read()
1800 cl_contents = file(
1801 os.path.join(conv.get_wc(), "trunk", "client_lock.idl")).read()
1803 if atsign_contents[-1:] == "@":
1804 raise svntest.Failure
1805 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
1806 raise svntest.Failure
1808 if not (conv.logs[21].author == "William Lyon Phelps III" and
1809 conv.logs[20].author == "j random"):
1810 raise svntest.Failure
1813 def questionable_branch_names():
1814 "test that we can handle weird branch names"
1815 conv = ensure_conversion('questionable-symbols')
1816 # If the conversion succeeds, then we're okay. We could check the
1817 # actual branch paths, too, but the main thing is to know that the
1818 # conversion doesn't fail.
1821 def questionable_tag_names():
1822 "test that we can handle weird tag names"
1823 conv = ensure_conversion('questionable-symbols')
1824 for tag_name in ['Tag_A', 'TagWith--Backslash_E', 'TagWith++Slash_Z']:
1825 conv.find_tag_log(tag_name).check(sym_log_msg(tag_name,1), (
1826 ('/%(tags)s/' + tag_name + ' (from /trunk:8)', 'A'),
1830 def revision_reorder_bug():
1831 "reveal a bug that reorders file revisions"
1832 conv = ensure_conversion('revision-reorder-bug')
1833 # If the conversion succeeds, then we're okay. We could check the
1834 # actual revisions, too, but the main thing is to know that the
1835 # conversion doesn't fail.
1838 def exclude():
1839 "test that exclude really excludes everything"
1840 conv = ensure_conversion('main', args=['--exclude=.*'])
1841 for log in conv.logs.values():
1842 for item in log.changed_paths.keys():
1843 if item.startswith('/branches/') or item.startswith('/tags/'):
1844 raise svntest.Failure
1847 def vendor_branch_delete_add():
1848 "add trunk file that was deleted on vendor branch"
1849 # This will error if the bug is present
1850 conv = ensure_conversion('vendor-branch-delete-add')
1853 def resync_pass2_pull_forward():
1854 "ensure pass2 doesn't pull rev too far forward"
1855 conv = ensure_conversion('resync-pass2-pull-forward')
1856 # If the conversion succeeds, then we're okay. We could check the
1857 # actual revisions, too, but the main thing is to know that the
1858 # conversion doesn't fail.
1861 def native_eol():
1862 "only LFs for svn:eol-style=native files"
1863 conv = ensure_conversion('native-eol')
1864 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
1865 conv.repos)
1866 # Verify that all files in the dump have LF EOLs. We're actually
1867 # testing the whole dump file, but the dump file itself only uses
1868 # LF EOLs, so we're safe.
1869 for line in lines:
1870 if line[-1] != '\n' or line[:-1].find('\r') != -1:
1871 raise svntest.Failure
1874 def double_fill():
1875 "reveal a bug that created a branch twice"
1876 conv = ensure_conversion('double-fill')
1877 # If the conversion succeeds, then we're okay. We could check the
1878 # actual revisions, too, but the main thing is to know that the
1879 # conversion doesn't fail.
1882 def resync_pass2_push_backward():
1883 "ensure pass2 doesn't push rev too far backward"
1884 conv = ensure_conversion('resync-pass2-push-backward')
1885 # If the conversion succeeds, then we're okay. We could check the
1886 # actual revisions, too, but the main thing is to know that the
1887 # conversion doesn't fail.
1890 def double_add():
1891 "reveal a bug that added a branch file twice"
1892 conv = ensure_conversion('double-add')
1893 # If the conversion succeeds, then we're okay. We could check the
1894 # actual revisions, too, but the main thing is to know that the
1895 # conversion doesn't fail.
1898 def bogus_branch_copy():
1899 "reveal a bug that copies a branch file wrongly"
1900 conv = ensure_conversion('bogus-branch-copy')
1901 # If the conversion succeeds, then we're okay. We could check the
1902 # actual revisions, too, but the main thing is to know that the
1903 # conversion doesn't fail.
1906 def nested_ttb_directories():
1907 "require error if ttb directories are not disjoint"
1908 opts_list = [
1909 {'trunk' : 'a', 'branches' : 'a',},
1910 {'trunk' : 'a', 'tags' : 'a',},
1911 {'branches' : 'a', 'tags' : 'a',},
1912 # This option conflicts with the default trunk path:
1913 {'branches' : 'trunk',},
1914 # Try some nested directories:
1915 {'trunk' : 'a', 'branches' : 'a/b',},
1916 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
1917 {'branches' : 'a', 'tags' : 'a/b',},
1920 for opts in opts_list:
1921 try:
1922 ensure_conversion(
1923 'main', error_re=r'.*paths .* and .* are not disjoint\.', **opts
1925 raise MissingErrorException
1926 except svntest.Failure:
1927 pass
1930 def auto_props_ignore_case():
1931 "test auto-props (case-insensitive)"
1932 ### TODO: It's a bit klugey to construct this path here. See also
1933 ### the comment in eol_mime().
1934 auto_props_path = os.path.join('..', test_data_dir, 'eol-mime-cvsrepos',
1935 'auto-props')
1937 # The files are as follows:
1939 # trunk/foo.txt: no -kb, mime auto-prop says nothing.
1940 # trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
1941 # trunk/foo.zip: no -kb, mime auto-prop says non-text.
1942 # trunk/foo.bin: has -kb, mime auto-prop says nothing.
1943 # trunk/foo.csv: has -kb, mime auto-prop says text.
1944 # trunk/foo.dbf: has -kb, mime auto-prop says non-text.
1945 # trunk/foo.UPCASE1: no -kb, no mime type.
1946 # trunk/foo.UPCASE2: no -kb, no mime type.
1948 conv = ensure_conversion(
1949 'eol-mime',
1950 args=['--auto-props=%s' % auto_props_path, '--auto-props-ignore-case'])
1951 conv.check_props(
1952 ['myprop', 'svn:eol-style', 'svn:mime-type'],
1954 ('trunk/foo.txt', ['txt', 'native', None]),
1955 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml']),
1956 ('trunk/foo.zip', ['zip', 'native', 'application/zip']),
1957 ('trunk/foo.bin', ['bin', None, 'application/octet-stream']),
1958 ('trunk/foo.csv', ['csv', None, 'text/csv']),
1959 ('trunk/foo.dbf', ['dbf', None, 'application/what-is-dbf']),
1960 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None]),
1961 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None]),
1966 def auto_props():
1967 "test auto-props (case-sensitive)"
1968 # See auto_props for comments.
1969 auto_props_path = os.path.join('..', test_data_dir, 'eol-mime-cvsrepos',
1970 'auto-props')
1972 conv = ensure_conversion(
1973 'eol-mime', args=['--auto-props=%s' % auto_props_path])
1974 conv.check_props(
1975 ['myprop', 'svn:eol-style', 'svn:mime-type'],
1977 ('trunk/foo.txt', ['txt', 'native', None]),
1978 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml']),
1979 ('trunk/foo.zip', ['zip', 'native', 'application/zip']),
1980 ('trunk/foo.bin', ['bin', None, 'application/octet-stream']),
1981 ('trunk/foo.csv', ['csv', None, 'text/csv']),
1982 ('trunk/foo.dbf', ['dbf', None, 'application/what-is-dbf']),
1983 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None]),
1984 ('trunk/foo.UPCASE2', [None, 'native', None]),
1989 #----------------------------------------------------------------------
1991 ########################################################################
1992 # Run the tests
1994 # list all tests here, starting with None:
1995 test_list = [ None,
1996 show_usage, # 1
1997 attr_exec,
1998 space_fname,
1999 two_quick,
2000 prune_with_care,
2001 interleaved_commits,
2002 simple_commits,
2003 simple_tags,
2004 simple_branch_commits,
2005 mixed_time_tag, # 10
2006 mixed_time_branch_with_added_file,
2007 mixed_commit,
2008 split_time_branch,
2009 bogus_tag,
2010 overlapping_branch,
2011 phoenix_branch,
2012 ctrl_char_in_log,
2013 overdead,
2014 no_trunk_prune,
2015 double_delete, # 20
2016 split_branch,
2017 resync_misgroups,
2018 tagged_branch_and_trunk,
2019 enroot_race,
2020 enroot_race_obo,
2021 branch_delete_first,
2022 nonascii_filenames,
2023 vendor_branch_sameness,
2024 default_branches,
2025 compose_tag_three_sources, # 30
2026 pass5_when_to_fill,
2027 peer_path_pruning,
2028 empty_trunk,
2029 no_spurious_svn_commits,
2030 invalid_closings_on_trunk,
2031 individual_passes,
2032 resync_bug,
2033 branch_from_default_branch,
2034 file_in_attic_too,
2035 symbolic_name_filling_guide, # 40
2036 eol_mime,
2037 keywords,
2038 ignore,
2039 requires_cvs,
2040 questionable_branch_names,
2041 questionable_tag_names,
2042 revision_reorder_bug,
2043 exclude,
2044 vendor_branch_delete_add,
2045 resync_pass2_pull_forward, # 50
2046 native_eol,
2047 double_fill,
2048 resync_pass2_push_backward,
2049 double_add,
2050 bogus_branch_copy,
2051 nested_ttb_directories,
2052 prune_with_care_variants,
2053 simple_tags_variants,
2054 phoenix_branch_variants,
2055 no_trunk_prune_variants, # 60
2056 tagged_branch_and_trunk_variants,
2057 branch_delete_first_variants,
2058 empty_trunk_variants,
2059 peer_path_pruning_variants,
2060 auto_props_ignore_case,
2062 if sys.hexversion < 0x02010000:
2063 test_list.append(XFail(auto_props))
2064 else:
2065 test_list.append(auto_props)
2067 if __name__ == '__main__':
2069 # The Subversion test suite code assumes it's being invoked from
2070 # within a working copy of the Subversion sources, and tries to use
2071 # the binaries in that tree. Since the cvs2svn tree never contains
2072 # a Subversion build, we just use the system's installed binaries.
2073 svntest.main.svn_binary = 'svn'
2074 svntest.main.svnlook_binary = 'svnlook'
2075 svntest.main.svnadmin_binary = 'svnadmin'
2076 svntest.main.svnversion_binary = 'svnversion'
2078 svntest.main.run_tests(test_list)
2079 # NOTREACHED
2082 ### End of file.